diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm
index c0b28cc1e2b..27efccce71d 100644
--- a/code/modules/mob/living/say.dm
+++ b/code/modules/mob/living/say.dm
@@ -363,6 +363,12 @@ proc/get_radio_key_from_channel(var/channel)
spawn(0) //Using spawns to queue all the messages for AFTER this proc is done, and stop runtimes
if(M && src) //If we still exist, when the spawn processes
+ //VOREStation Add - Ghosts don't hear whispers
+ if(whispering && isobserver(M) && !M.client?.holder)
+ M.show_message("[src.name] [w_not_heard].", 2)
+ return
+ //VOREStation Add End
+
var/dst = get_dist(get_turf(M),get_turf(src))
if(dst <= message_range || (M.stat == DEAD && !forbid_seeing_deadchat)) //Inside normal message range, or dead with ears (handled in the view proc)
diff --git a/code/modules/mob/say_vr.dm b/code/modules/mob/say_vr.dm
index 5dbf8537452..5543f48e2e2 100644
--- a/code/modules/mob/say_vr.dm
+++ b/code/modules/mob/say_vr.dm
@@ -42,6 +42,7 @@
return
if (message)
+ var/undisplayed_message = "[src] does something too subtle for you to see."
message = encode_html_emphasis(message)
var/list/vis = get_mobs_and_objs_in_view_fast(get_turf(src),1,2) //Turf, Range, and type 2 is emote
@@ -50,8 +51,12 @@
for(var/vismob in vis_mobs)
var/mob/M = vismob
- spawn(0)
- M.show_message(message, 2)
+ if(isobserver(M) && !M.client?.holder)
+ spawn(0)
+ M.show_message(undisplayed_message, 2)
+ else
+ spawn(0)
+ M.show_message(message, 2)
for(var/visobj in vis_objs)
var/obj/O = visobj