When the arrow is shot, summon an identical arrow with a marker riding it. Copy over the relevant nbt data, like motion. Now, you have replaced the original arrow with an arrow that has a marker riding it.

When the arrow hits the mob, the marker will be left over. You can use a predicate to check if your marker is no longer riding. Then, you can execute your commands with 100% certainty that it hit a target, and where.


A possible solution might look like this:

Repeating every tick:

#Replace arrow
execute as @e[type=arrow,tag=!new_arrow] at @s run summon arrow ~ ~ ~ {Tags:["new_arrow"],Passengers:[{id:"minecraft:marker",Tags:["arrow_rider"]}]}

execute as @e[type=arrow,tag=!new_arrow] at @s run data modify entity @e[type=arrow,tag=new_arrow,limit=1] Motion set from entity @s

kill @e[type=arrow,tag=!new_arrow]


#Event
execute as @e[type=marker,predicate=!<namespace>:is_riding_arrow] run summon lightning_bolt ~ ~ ~
execute as @e[type=marker,predicate=!<namespace>:is_riding_arrow] run playsound entity.wolf.hurt master @p

kill @e[type=marker,predicate=!<namespace>:is_riding_arrow]

Predicate file named is_riding_arrow.json located in <datapackname>/data/<namespace>/predicates/:

{
  "condition": "minecraft:entity_properties",
  "entity": "this",
  "predicate": {
    "nbt": "{Tags:[\"arrow_rider\"]}",
    "vehicle": {
      "type": "minecraft:arrow"
    }
  }
}