diff --git a/html_orig/js/app.js b/html_orig/js/app.js
index 892c35d..ab6411b 100644
--- a/html_orig/js/app.js
+++ b/html_orig/js/app.js
@@ -1582,6 +1582,14 @@ var Screen = (function () {
 		'Z': '\u2128',
 	};
 
+	// for BEL
+	var audioCtx = null;
+	try {
+		audioCtx = new (window.AudioContext || window.audioContext || window.webkitAudioContext)();
+	} catch (er) {
+		console.error("Browser does not support AudioContext, can't beep.", er);
+	}
+
 	/** Get cell under cursor */
 	function _curCell() {
 		return screen[cursor.y*W + cursor.x];
@@ -1837,6 +1845,34 @@ var Screen = (function () {
 		});
 	}
 
+	function _beep()
+	{
+		var osc, gain;
+		if (!audioCtx) return;
+
+		// Main beep
+		osc = audioCtx.createOscillator();
+		gain = audioCtx.createGain();
+		osc.connect(gain);
+		gain.connect(audioCtx.destination);
+		gain.gain.value = 0.5;
+		osc.frequency.value = 750;
+		osc.type = 'sine';
+		osc.start();
+		osc.stop(audioCtx.currentTime+0.05);
+
+		// Surrogate beep (making it sound like 'oops')
+		osc = audioCtx.createOscillator();
+		gain = audioCtx.createGain();
+		osc.connect(gain);
+		gain.connect(audioCtx.destination);
+		gain.gain.value = 0.2;
+		osc.frequency.value = 400;
+		osc.type = 'sine';
+		osc.start(audioCtx.currentTime+0.05);
+		osc.stop(audioCtx.currentTime+0.08);
+	}
+
 	/** Load screen content from a binary sequence (new) */
 	function load(str) {
 		var content = str.substr(1);
@@ -1847,6 +1883,9 @@ var Screen = (function () {
 			case 'T':
 				_load_labels(content);
 				break;
+			case 'B':
+				_beep();
+				break;
 			default:
 				console.warn("Bad data message type, ignoring.");
 		}
diff --git a/html_orig/jssrc/term.js b/html_orig/jssrc/term.js
index 5541ca3..214a5c2 100644
--- a/html_orig/jssrc/term.js
+++ b/html_orig/jssrc/term.js
@@ -25,6 +25,14 @@ var Screen = (function () {
 		'Z': '\u2128',
 	};
 
+	// for BEL
+	var audioCtx = null;
+	try {
+		audioCtx = new (window.AudioContext || window.audioContext || window.webkitAudioContext)();
+	} catch (er) {
+		console.error("Browser does not support AudioContext, can't beep.", er);
+	}
+
 	/** Get cell under cursor */
 	function _curCell() {
 		return screen[cursor.y*W + cursor.x];
@@ -280,6 +288,34 @@ var Screen = (function () {
 		});
 	}
 
+	function _beep()
+	{
+		var osc, gain;
+		if (!audioCtx) return;
+
+		// Main beep
+		osc = audioCtx.createOscillator();
+		gain = audioCtx.createGain();
+		osc.connect(gain);
+		gain.connect(audioCtx.destination);
+		gain.gain.value = 0.5;
+		osc.frequency.value = 750;
+		osc.type = 'sine';
+		osc.start();
+		osc.stop(audioCtx.currentTime+0.05);
+
+		// Surrogate beep (making it sound like 'oops')
+		osc = audioCtx.createOscillator();
+		gain = audioCtx.createGain();
+		osc.connect(gain);
+		gain.connect(audioCtx.destination);
+		gain.gain.value = 0.2;
+		osc.frequency.value = 400;
+		osc.type = 'sine';
+		osc.start(audioCtx.currentTime+0.05);
+		osc.stop(audioCtx.currentTime+0.08);
+	}
+
 	/** Load screen content from a binary sequence (new) */
 	function load(str) {
 		var content = str.substr(1);
@@ -290,6 +326,9 @@ var Screen = (function () {
 			case 'T':
 				_load_labels(content);
 				break;
+			case 'B':
+				_beep();
+				break;
 			default:
 				console.warn("Bad data message type, ignoring.");
 		}
diff --git a/user/ansi_parser_callbacks.c b/user/ansi_parser_callbacks.c
index e1b19ae..ddb3f92 100644
--- a/user/ansi_parser_callbacks.c
+++ b/user/ansi_parser_callbacks.c
@@ -10,6 +10,7 @@
 #include "ansi_parser.h"
 #include "uart_driver.h"
 #include "sgr.h"
+#include "cgi_sockets.h"
 
 static char utf_collect[4];
 static int utf_i = 0;
@@ -127,8 +128,7 @@ apars_handle_spaceCmd(char c)
 void ICACHE_FLASH_ATTR
 apars_handle_bel(void)
 {
-	ansi_warn("NOIMPL: BEEP");
-	// TODO pass to the browser somehow
+	send_beep();
 }
 
 /**
diff --git a/user/cgi_sockets.c b/user/cgi_sockets.c
index f505249..e3619c5 100644
--- a/user/cgi_sockets.c
+++ b/user/cgi_sockets.c
@@ -64,6 +64,14 @@ notifyLabelsTimCb(void *arg)
 	notify_available = true;
 }
 
+/** Beep */
+void ICACHE_FLASH_ATTR
+send_beep(void)
+{
+	// here's some potential for a race error with the other broadcast functions :C
+	cgiWebsockBroadcast(URL_WS_UPDATE, "B", 1, 0);
+}
+
 
 /**
  * Broadcast screen state to sockets.
diff --git a/user/cgi_sockets.h b/user/cgi_sockets.h
index 4b93046..75317b6 100644
--- a/user/cgi_sockets.h
+++ b/user/cgi_sockets.h
@@ -3,12 +3,15 @@
 
 #define URL_WS_UPDATE "/term/update.ws"
 
+#include 
 #include "screen.h"
 
 /** Update websocket connect callback */
 void updateSockConnect(Websock *ws);
 void screen_notifyChange(ScreenNotifyChangeTopic topic);
 
+void send_beep(void);
+
 // defined in the makefile
 #if DEBUG_INPUT
 #define ws_warn warn