diff --git a/CHANGES b/CHANGES
index 037001dafc424d8fa7f097d87565a3c3fa951f22..5819d7098da596b745dd3f20e0e14c90c5999eb3 100644
--- a/CHANGES
+++ b/CHANGES
@@ -270,6 +270,17 @@ AMI (Asterisk Manager Interface)
 
  * AMI events now contain a SystemName field, if available.
 
+ * Local channel optimization is now conveyed in two events:
+   LocalOptimizationBegin and LocalOptimizationEnd. The Begin event is sent
+   when the Local channel driver begins attempting to optimize itself out of
+   the media path; the End event is sent after the channel halves have
+   successfully optimized themselves out of the media path.
+
+ * Local channel information in events is now prefixed with "LocalOne" and
+   "LocalTwo". This replaces the suffix of "1" and "2" for the two halves of
+   the Local channel. This affects the LocalBridge, LocalOptimizationBegin,
+   and LocalOptimizationEnd events.
+
 AGI (Asterisk Gateway Interface)
 ------------------
  * The manager event AGIExec has been split into AGIExecStart and AGIExecEnd.
diff --git a/include/asterisk/bridging.h b/include/asterisk/bridging.h
index e03bfd0cdbb65e4d74b5dac237915aa27196d050..08d0023e5bdee7078bbe1b6ec153fa93f3cd53df 100644
--- a/include/asterisk/bridging.h
+++ b/include/asterisk/bridging.h
@@ -870,19 +870,23 @@ int ast_bridge_suspend(struct ast_bridge *bridge, struct ast_channel *chan);
  */
 int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan);
 
+struct ast_unreal_pvt;
+
 /*!
  * \brief Check and optimize out the unreal channels between bridges.
  * \since 12.0.0
  *
  * \param chan Unreal channel writing a frame into the channel driver.
  * \param peer Other unreal channel in the pair.
+ * \param pvt Private data provided by an implementation of the unreal driver that
+ * contains the callbacks that should be called when optimization begins/ends
  *
  * \note It is assumed that chan is already locked.
  *
  * \retval 0 if unreal channels were not optimized out.
  * \retval non-zero if unreal channels were optimized out.
  */
-int ast_bridge_unreal_optimized_out(struct ast_channel *chan, struct ast_channel *peer);
+int ast_bridge_unreal_optimize_out(struct ast_channel *chan, struct ast_channel *peer, struct ast_unreal_pvt *pvt);
 
 /*!
  * \brief Tells, if optimization is allowed, how the optimization would be performed
diff --git a/include/asterisk/core_local.h b/include/asterisk/core_local.h
index 693c93b4679ea936a4e300560269c157e3cd2f82..491112d2bf0a2514c0aa57c1aaf04348411bab3e 100644
--- a/include/asterisk/core_local.h
+++ b/include/asterisk/core_local.h
@@ -37,6 +37,7 @@ extern "C" {
 struct ast_channel;
 struct ast_bridge;
 struct ast_bridge_features;
+struct stasis_message_type;
 
 /* ------------------------------------------------------------------- */
 
@@ -91,6 +92,44 @@ int ast_local_setup_masquerade(struct ast_channel *ast, struct ast_channel *masq
 
 /* ------------------------------------------------------------------- */
 
+/*!
+ * \brief Message type for when two local channel halves are bridged together
+ * \since 12.0.0
+ *
+ * \note Payloads for the \ref ast_local_bridge_type are a \ref ast_multi_channel_blob.
+ * Roles for the channels in the \ref ast_multi_channel_blob are "1" and "2", reflecting
+ * the two halves. Unlike most other bridges, the 'bridge' between two local channels is
+ * not part of the bridge framework; as such, the message simply references the two local
+ * channel halves that are now bridged.
+ *
+ * \retval A \ref stasis message type
+ */
+struct stasis_message_type *ast_local_bridge_type(void);
+
+/*!
+ * \brief Message type for when a local channel optimization begins
+ * \since 12.0.0
+ *
+ * \note Payloads for the \ref ast_local_optimization_begin_type are a
+ * \ref ast_multi_channel_blob. Roles for the channels in the \ref ast_multi_channel_blob
+ * are "1" and "2", reflecting the two halves.
+ *
+ * \retval A \ref stasis message type
+ */
+struct stasis_message_type *ast_local_optimization_begin_type(void);
+
+/*!
+ * \brief Message type for when a local channel optimization completes
+ * \since 12.0.0
+ *
+ * \note Payloads for the \ref ast_local_optimization_end_type are a
+ * \ref ast_multi_channel_blob. Roles for the channels in the \ref ast_multi_channel_blob
+ * are "1" and "2", reflecting the two halves.
+ *
+ * \retval A \ref stasis message type
+ */
+struct stasis_message_type *ast_local_optimization_end_type(void);
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif
diff --git a/include/asterisk/core_unreal.h b/include/asterisk/core_unreal.h
index 7cb68f162307607d84df9f4d7c8c64ba575a9e62..a6e98895ac475d462c4b0119517ce7e546b60b2a 100644
--- a/include/asterisk/core_unreal.h
+++ b/include/asterisk/core_unreal.h
@@ -43,6 +43,30 @@ struct ast_callid;
 
 /* ------------------------------------------------------------------- */
 
+struct ast_unreal_pvt;
+
+/*!
+ * \brief Callbacks that can be provided by concrete implementations of the unreal
+ * channel driver that will be called when events occur in the unreal layer
+ */
+struct ast_unreal_pvt_callbacks {
+	/*!
+	 * \brief Called when an optimization attempt has started
+	 * \note p is locked when this callback is called
+	 * \param p The \ref ast_unreal_pvt object
+	 */
+	void (* const optimization_started)(struct ast_unreal_pvt *p);
+
+	/*!
+	 * \brief Called when an optimization attempt completed successfully
+	 * \note p is locked when this callback is called
+	 * \param p The \ref ast_unreal_pvt object
+	 * \param success Non-zero if the optimization succeeded, zero if the optimization
+	 * met with fatal and permanent error
+	 */
+	void (* const optimization_finished)(struct ast_unreal_pvt *p);
+};
+
 /*!
  * \brief The base pvt structure for local channel derivatives.
  *
@@ -51,11 +75,12 @@ struct ast_callid;
  * ast_chan owner -> ast_unreal_pvt -> ast_chan chan
  */
 struct ast_unreal_pvt {
-	struct ast_channel *owner;      /*!< Master Channel - ;1 side */
-	struct ast_channel *chan;       /*!< Outbound channel - ;2 side */
-	struct ast_format_cap *reqcap;  /*!< Requested format capabilities */
-	struct ast_jb_conf jb_conf;     /*!< jitterbuffer configuration */
-	unsigned int flags;             /*!< Private option flags */
+	struct ast_unreal_pvt_callbacks *callbacks; /*!< Event callbacks */
+	struct ast_channel *owner;                  /*!< Master Channel - ;1 side */
+	struct ast_channel *chan;                   /*!< Outbound channel - ;2 side */
+	struct ast_format_cap *reqcap;              /*!< Requested format capabilities */
+	struct ast_jb_conf jb_conf;                 /*!< jitterbuffer configuration */
+	unsigned int flags;                         /*!< Private option flags */
 	/*! Base name of the unreal channels.  exten@context or other name. */
 	char name[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2];
 };
@@ -65,6 +90,7 @@ struct ast_unreal_pvt {
 #define AST_UNREAL_CARETAKER_THREAD (1 << 0) /*!< The ;2 side launched a PBX, was pushed into a bridge, or was masqueraded into an application. */
 #define AST_UNREAL_NO_OPTIMIZATION  (1 << 1) /*!< Do not optimize out the unreal channels */
 #define AST_UNREAL_MOH_INTERCEPT    (1 << 2) /*!< Intercept and act on hold/unhold control frames */
+#define AST_UNREAL_OPTIMIZE_BEGUN   (1 << 3) /*!< Indicates that an optimization attempt has been started */
 
 /*!
  * \brief Send an unreal pvt in with no locks held and get all locks
diff --git a/main/bridging.c b/main/bridging.c
index 1dc8b6ea325035b82c19f854fb77280031b09787..d01a66ad7cd485b0ae64805ec5baa26defa14973 100644
--- a/main/bridging.c
+++ b/main/bridging.c
@@ -61,6 +61,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/cli.h"
 #include "asterisk/parking.h"
 #include "asterisk/core_local.h"
+#include "asterisk/core_unreal.h"
 #include "asterisk/features_config.h"
 
 /*! All bridges container. */
@@ -628,6 +629,12 @@ static void bridge_channel_pull(struct ast_bridge_channel *bridge_channel)
 	ast_debug(1, "Bridge %s: pulling %p(%s)\n",
 		bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
 
+	ast_verb(3, "Channel %s left '%s' %s-bridge <%s>\n",
+		ast_channel_name(bridge_channel->chan),
+		bridge->technology->name,
+		bridge->v_table->name,
+		bridge->uniqueid);
+
 /* BUGBUG This is where incoming HOLD/UNHOLD memory should write UNHOLD into bridge. (if not local optimizing) */
 /* BUGBUG This is where incoming DTMF begin/end memory should write DTMF end into bridge. (if not local optimizing) */
 	if (!bridge_channel->just_joined) {
@@ -713,6 +720,16 @@ static int bridge_channel_push(struct ast_bridge_channel *bridge_channel)
 	if (!bridge_channel->suspended) {
 		++bridge->num_active;
 	}
+
+	ast_verb(3, "Channel %s %s%s%s '%s' %s-bridge <%s>\n",
+		ast_channel_name(bridge_channel->chan),
+		swap ? "swapped with " : "joined",
+		swap ? ast_channel_name(swap->chan) : "",
+		swap ? " into" : "",
+		bridge->technology->name,
+		bridge->v_table->name,
+		bridge->uniqueid);
+
 	ast_bridge_publish_enter(bridge, bridge_channel->chan);
 	if (swap) {
 		ast_bridge_change_state(swap, AST_BRIDGE_CHANNEL_STATE_HANGUP);
@@ -1895,7 +1912,7 @@ static int smart_bridge_operation(struct ast_bridge *bridge)
 	 * must not release the bridge lock until we have installed the
 	 * new bridge technology.
 	 */
-	ast_debug(1, "Bridge %s: switching %s technology to %s\n",
+	ast_verb(4, "Bridge %s: switching from %s technology to %s\n",
 		bridge->uniqueid, old_technology->name, new_technology->name);
 
 	/*
@@ -4644,14 +4661,17 @@ static enum bridge_allow_swap bridges_allow_swap_optimization(struct ast_bridge
  * \param chan_bridge_channel
  * \param peer_bridge
  * \param peer_bridge_channel
+ * \param pvt Unreal data containing callbacks to call if the optimization actually
+ * happens
  *
  * \retval 1 if unreal channels failed to optimize out.
  * \retval 0 if unreal channels were not optimized out.
  * \retval -1 if unreal channels were optimized out.
  */
-static int check_swap_optimize_out(struct ast_bridge *chan_bridge,
+static int try_swap_optimize_out(struct ast_bridge *chan_bridge,
 	struct ast_bridge_channel *chan_bridge_channel, struct ast_bridge *peer_bridge,
-	struct ast_bridge_channel *peer_bridge_channel)
+	struct ast_bridge_channel *peer_bridge_channel,
+	struct ast_unreal_pvt *pvt)
 {
 	struct ast_bridge *dst_bridge;
 	struct ast_bridge_channel *dst_bridge_channel;
@@ -4696,10 +4716,18 @@ static int check_swap_optimize_out(struct ast_bridge *chan_bridge,
 			ast_channel_name(dst_bridge_channel->chan),
 			ast_channel_name(other->chan));
 
+		if (pvt && !ast_test_flag(pvt, AST_UNREAL_OPTIMIZE_BEGUN) && pvt->callbacks
+				&& pvt->callbacks->optimization_started) {
+			pvt->callbacks->optimization_started(pvt);
+			ast_set_flag(pvt, AST_UNREAL_OPTIMIZE_BEGUN);
+		}
 		other->swap = dst_bridge_channel->chan;
 		if (!bridge_move_do(dst_bridge, other, 1)) {
 			ast_bridge_change_state(src_bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
 			res = -1;
+			if (pvt && pvt->callbacks && pvt->callbacks->optimization_finished) {
+				pvt->callbacks->optimization_finished(pvt);
+			}
 		}
 	}
 	return res;
@@ -4761,13 +4789,16 @@ static enum bridge_allow_merge bridges_allow_merge_optimization(struct ast_bridg
  * \param chan_bridge_channel
  * \param peer_bridge
  * \param peer_bridge_channel
+ * \param pvt Unreal data containing callbacks to call if the optimization actually
+ * happens
  *
  * \retval 0 if unreal channels were not optimized out.
  * \retval -1 if unreal channels were optimized out.
  */
-static int check_merge_optimize_out(struct ast_bridge *chan_bridge,
+static int try_merge_optimize_out(struct ast_bridge *chan_bridge,
 	struct ast_bridge_channel *chan_bridge_channel, struct ast_bridge *peer_bridge,
-	struct ast_bridge_channel *peer_bridge_channel)
+	struct ast_bridge_channel *peer_bridge_channel,
+	struct ast_unreal_pvt *pvt)
 {
 	struct merge_direction merge;
 	struct ast_bridge_channel *kick_me[] = {
@@ -4798,12 +4829,20 @@ static int check_merge_optimize_out(struct ast_bridge *chan_bridge,
 		ast_channel_name(chan_bridge_channel->chan),
 		ast_channel_name(peer_bridge_channel->chan));
 
+	if (pvt && !ast_test_flag(pvt, AST_UNREAL_OPTIMIZE_BEGUN) && pvt->callbacks
+			&& pvt->callbacks->optimization_started) {
+		pvt->callbacks->optimization_started(pvt);
+		ast_set_flag(pvt, AST_UNREAL_OPTIMIZE_BEGUN);
+	}
 	bridge_merge_do(merge.dest, merge.src, kick_me, ARRAY_LEN(kick_me));
+	if (pvt && pvt->callbacks && pvt->callbacks->optimization_finished) {
+		pvt->callbacks->optimization_finished(pvt);
+	}
 
 	return -1;
 }
 
-int ast_bridge_unreal_optimized_out(struct ast_channel *chan, struct ast_channel *peer)
+int ast_bridge_unreal_optimize_out(struct ast_channel *chan, struct ast_channel *peer, struct ast_unreal_pvt *pvt)
 {
 	struct ast_bridge *chan_bridge;
 	struct ast_bridge *peer_bridge;
@@ -4821,11 +4860,11 @@ int ast_bridge_unreal_optimized_out(struct ast_channel *chan, struct ast_channel
 	if (peer_bridge) {
 		peer_bridge_channel = ast_channel_internal_bridge_channel(peer);
 
-		res = check_swap_optimize_out(chan_bridge, chan_bridge_channel,
-			peer_bridge, peer_bridge_channel);
+		res = try_swap_optimize_out(chan_bridge, chan_bridge_channel,
+			peer_bridge, peer_bridge_channel, pvt);
 		if (!res) {
-			res = check_merge_optimize_out(chan_bridge, chan_bridge_channel,
-				peer_bridge, peer_bridge_channel);
+			res = try_merge_optimize_out(chan_bridge, chan_bridge_channel,
+				peer_bridge, peer_bridge_channel, pvt);
 		} else if (0 < res) {
 			res = 0;
 		}
diff --git a/main/core_local.c b/main/core_local.c
index 16abc428cfc217c1306a2180d06880d7f00a5282..b35e03110b7d265c00bb07e15f6d9e7cf804417c 100644
--- a/main/core_local.c
+++ b/main/core_local.c
@@ -46,6 +46,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/bridging.h"
 #include "asterisk/core_unreal.h"
 #include "asterisk/core_local.h"
+#include "asterisk/stasis.h"
+#include "asterisk/stasis_channels.h"
 #include "asterisk/_private.h"
 #include "asterisk/stasis_channels.h"
 
@@ -161,6 +163,34 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 			</syntax>
 		</managerEventInstance>
 	</managerEvent>
+	<managerEvent language="en_US" name="LocalOptimizationBegin">
+		<managerEventInstance class="EVENT_FLAG_CALL">
+			<synopsis>Raised when two halves of a Local Channel begin to optimize
+			themselves out of the media path.</synopsis>
+			<syntax>
+				<xi:include xpointer="xpointer(/docs/managerEvent[@name='LocalBridge']/managerEventInstance/syntax/parameter[contains(@name, 'LocalOne')])" />
+				<xi:include xpointer="xpointer(/docs/managerEvent[@name='LocalBridge']/managerEventInstance/syntax/parameter[contains(@name, 'LocalTwo')])" />
+			</syntax>
+			<see-also>
+				<ref type="managerEvent">LocalOptimizationEnd</ref>
+				<ref type="manager">LocalOptimizeAway</ref>
+			</see-also>
+		</managerEventInstance>
+	</managerEvent>
+	<managerEvent language="en_US" name="LocalOptimizationEnd">
+		<managerEventInstance class="EVENT_FLAG_CALL">
+			<synopsis>Raised when two halves of a Local Channel have finished optimizing
+			themselves out of the media path.</synopsis>
+			<syntax>
+				<xi:include xpointer="xpointer(/docs/managerEvent[@name='LocalBridge']/managerEventInstance/syntax/parameter[contains(@name, 'LocalOne')])" />
+				<xi:include xpointer="xpointer(/docs/managerEvent[@name='LocalBridge']/managerEventInstance/syntax/parameter[contains(@name, 'LocalTwo')])" />
+			</syntax>
+			<see-also>
+				<ref type="managerEvent">LocalOptimizationBegin</ref>
+				<ref type="manager">LocalOptimizeAway</ref>
+			</see-also>
+		</managerEventInstance>
+	</managerEvent>
  ***/
 
 static const char tdesc[] = "Local Proxy Channel Driver";
@@ -171,6 +201,30 @@ static struct ast_channel *local_request(const char *type, struct ast_format_cap
 static int local_call(struct ast_channel *ast, const char *dest, int timeout);
 static int local_hangup(struct ast_channel *ast);
 static int local_devicestate(const char *data);
+static void local_optimization_started_cb(struct ast_unreal_pvt *base);
+static void local_optimization_finished_cb(struct ast_unreal_pvt *base);
+
+static struct ast_manager_event_blob *local_message_to_ami(struct stasis_message *msg);
+
+/*!
+ * @{ \brief Define local channel message types.
+ */
+STASIS_MESSAGE_TYPE_DEFN(ast_local_bridge_type,
+	.to_ami = local_message_to_ami,
+	);
+STASIS_MESSAGE_TYPE_DEFN(ast_local_optimization_begin_type,
+	.to_ami = local_message_to_ami,
+	);
+STASIS_MESSAGE_TYPE_DEFN(ast_local_optimization_end_type,
+	.to_ami = local_message_to_ami,
+	);
+/*! @} */
+
+/*! \brief Callbacks from the unreal core when channel optimization occurs */
+struct ast_unreal_pvt_callbacks local_unreal_callbacks = {
+	.optimization_started = local_optimization_started_cb,
+	.optimization_finished = local_optimization_finished_cb,
+};
 
 /* PBX interface structure for channel registration */
 static struct ast_channel_tech local_tech = {
@@ -326,59 +380,115 @@ static int local_devicestate(const char *data)
 	return res;
 }
 
-static struct ast_manager_event_blob *local_bridge_to_ami(struct stasis_message *msg)
+static void publish_local_optimization(struct local_pvt *p, int complete)
 {
-	RAII_VAR(struct ast_str *, channel_one_string, NULL, ast_free);
-	RAII_VAR(struct ast_str *, channel_two_string, NULL, ast_free);
-	struct ast_multi_channel_blob *obj = stasis_message_data(msg);
-	struct ast_json *blob, *context, *exten, *optimize;
-	struct ast_channel_snapshot *chan_one, *chan_two;
-
-	chan_one = ast_multi_channel_blob_get_channel(obj, "1");
-	chan_two = ast_multi_channel_blob_get_channel(obj, "2");
-	blob = ast_multi_channel_blob_get_json(obj);
-
-	channel_one_string = ast_manager_build_channel_state_string_prefix(chan_one, "LocalOne");
-	if (!channel_one_string) {
+	RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_json *, blob, ast_json_null(), ast_json_unref);
+	RAII_VAR(struct ast_channel_snapshot *, local_one_snapshot, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_channel_snapshot *, local_two_snapshot, NULL, ao2_cleanup);
+
+	if (!blob) {
+		return;
+	}
+
+	local_one_snapshot = ast_channel_snapshot_create(p->base.owner);
+	if (!local_one_snapshot) {
+		return;
+	}
+
+	local_two_snapshot = ast_channel_snapshot_create(p->base.chan);
+	if (!local_two_snapshot) {
+		return;
+	}
+
+	payload = ast_multi_channel_blob_create(blob);
+	if (!payload) {
+		return;
+	}
+	ast_multi_channel_blob_add_channel(payload, "1", local_one_snapshot);
+	ast_multi_channel_blob_add_channel(payload, "2", local_two_snapshot);
+
+	msg = stasis_message_create(
+			complete ? ast_local_optimization_end_type() : ast_local_optimization_begin_type(),
+			payload);
+	if (!msg) {
+		return;
+	}
+
+	stasis_publish(ast_channel_topic(p->base.owner), msg);
+
+}
+
+/*! \brief Callback for \ref ast_unreal_pvt_callbacks \ref optimization_started_cb */
+static void local_optimization_started_cb(struct ast_unreal_pvt *base)
+{
+	struct local_pvt *p = (struct local_pvt *)base;
+	publish_local_optimization(p, 0);
+}
+
+/*! \brief Callback for \ref ast_unreal_pvt_callbacks \ref optimization_finished_cb */
+static void local_optimization_finished_cb(struct ast_unreal_pvt *base)
+{
+	struct local_pvt *p = (struct local_pvt *)base;
+	publish_local_optimization(p, 1);
+}
+
+static struct ast_manager_event_blob *local_message_to_ami(struct stasis_message *message)
+{
+	struct ast_multi_channel_blob *obj = stasis_message_data(message);
+	struct ast_json *blob = ast_multi_channel_blob_get_json(obj);
+	struct ast_channel_snapshot *local_snapshot_one;
+	struct ast_channel_snapshot *local_snapshot_two;
+	RAII_VAR(struct ast_str *, local_channel_one, NULL, ast_free);
+	RAII_VAR(struct ast_str *, local_channel_two, NULL, ast_free);
+	struct ast_str *event_buffer = ast_str_alloca(128);
+	const char *event;
+
+	local_snapshot_one = ast_multi_channel_blob_get_channel(obj, "1");
+	local_snapshot_two = ast_multi_channel_blob_get_channel(obj, "2");
+	if (!local_snapshot_one || !local_snapshot_two) {
 		return NULL;
 	}
 
-	channel_two_string = ast_manager_build_channel_state_string_prefix(chan_two, "LocalTwo");
-	if (!channel_two_string) {
+	local_channel_one = ast_manager_build_channel_state_string_prefix(local_snapshot_one, "LocalOne");
+	local_channel_two = ast_manager_build_channel_state_string_prefix(local_snapshot_two, "LocalTwo");
+	if (!local_channel_one || !local_channel_two) {
 		return NULL;
 	}
 
-	context = ast_json_object_get(blob, "context");
-	exten = ast_json_object_get(blob, "exten");
-	optimize = ast_json_object_get(blob, "optimize");
+	if (stasis_message_type(message) == ast_local_optimization_begin_type()) {
+		event = "LocalOptimizationBegin";
+	} else if (stasis_message_type(message) == ast_local_optimization_end_type()) {
+		event = "LocalOptimizationEnd";
+	} else if (stasis_message_type(message) == ast_local_bridge_type()) {
+		event = "LocalBridge";
+		ast_str_append(&event_buffer, 0, "Context: %s\r\n", ast_json_string_get(ast_json_object_get(blob, "context")));
+		ast_str_append(&event_buffer, 0, "Exten: %s\r\n", ast_json_string_get(ast_json_object_get(blob, "exten")));
+		ast_str_append(&event_buffer, 0, "LocalOptimization: %s\r\n", ast_json_is_true(ast_json_object_get(blob, "can_optimize")) ? "Yes" : "No");
+	} else {
+		return NULL;
+	}
 
-	return ast_manager_event_blob_create(EVENT_FLAG_CALL, "LocalBridge",
+	return ast_manager_event_blob_create(EVENT_FLAG_CALL, event,
 		"%s"
 		"%s"
-		"Context: %s\r\n"
-		"Exten: %s\r\n"
-		"LocalOptimization: %s\r\n",
-		ast_str_buffer(channel_one_string),
-		ast_str_buffer(channel_two_string),
-		ast_json_string_get(context),
-		ast_json_string_get(exten),
-		ast_json_is_true(optimize) ? "Yes" : "No");
+		"%s",
+		ast_str_buffer(local_channel_one),
+		ast_str_buffer(local_channel_two),
+		ast_str_buffer(event_buffer));
 }
 
-STASIS_MESSAGE_TYPE_DEFN_LOCAL(local_bridge_type,
-	.to_ami = local_bridge_to_ami,
-	);
-
 /*!
  * \internal
- * \brief Post the LocalBridge AMI event.
+ * \brief Post the \ref ast_local_bridge_type \ref stasis message
  * \since 12.0.0
  *
- * \param p local_pvt to raise the bridge event.
+ * \param p local_pvt to raise the local bridge message
  *
  * \return Nothing
  */
-static void local_bridge_event(struct local_pvt *p)
+static void publish_local_bridge_message(struct local_pvt *p)
 {
 	RAII_VAR(struct ast_multi_channel_blob *, multi_blob, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
@@ -390,7 +500,7 @@ static void local_bridge_event(struct local_pvt *p)
 	blob = ast_json_pack("{s: s, s: s, s: b}",
 		"context", p->context,
 		"exten", p->exten,
-		"optimize", ast_test_flag(&p->base, AST_UNREAL_NO_OPTIMIZATION));
+		"can_optimize", !ast_test_flag(&p->base, AST_UNREAL_NO_OPTIMIZATION));
 	if (!blob) {
 		return;
 	}
@@ -413,7 +523,7 @@ static void local_bridge_event(struct local_pvt *p)
 	ast_multi_channel_blob_add_channel(multi_blob, "1", one_snapshot);
 	ast_multi_channel_blob_add_channel(multi_blob, "2", two_snapshot);
 
-	msg = stasis_message_create(local_bridge_type(), multi_blob);
+	msg = stasis_message_create(ast_local_bridge_type(), multi_blob);
 	if (!msg) {
 		return;
 	}
@@ -564,14 +674,14 @@ static int local_call(struct ast_channel *ast, const char *dest, int timeout)
 			ast_log(LOG_NOTICE, "No such extension/context %s@%s while calling Local channel\n",
 				p->exten, p->context);
 		} else {
-			local_bridge_event(p);
+			publish_local_bridge_message(p);
 
 			/* Start switch on sub channel */
 			res = ast_pbx_start(chan);
 		}
 		break;
 	case LOCAL_CALL_ACTION_BRIDGE:
-		local_bridge_event(p);
+		publish_local_bridge_message(p);
 		ast_answer(chan);
 		res = ast_bridge_impart(p->action.bridge.join, chan, p->action.bridge.swap,
 			p->action.bridge.features, 1);
@@ -582,7 +692,7 @@ static int local_call(struct ast_channel *ast, const char *dest, int timeout)
 		p->action.bridge.features = NULL;
 		break;
 	case LOCAL_CALL_ACTION_MASQUERADE:
-		local_bridge_event(p);
+		publish_local_bridge_message(p);
 		ast_answer(chan);
 		res = ast_channel_move(p->action.masq, chan);
 		if (!res) {
@@ -699,6 +809,7 @@ static struct local_pvt *local_alloc(const char *data, struct ast_format_cap *ca
 	if (!pvt) {
 		return NULL;
 	}
+	pvt->base.callbacks = &local_unreal_callbacks;
 
 	parse = ast_strdupa(data);
 
@@ -883,12 +994,24 @@ static void local_shutdown(void)
 	locals = NULL;
 
 	ast_format_cap_destroy(local_tech.capabilities);
-	STASIS_MESSAGE_TYPE_CLEANUP(local_bridge_type);
+
+	STASIS_MESSAGE_TYPE_CLEANUP(ast_local_optimization_begin_type);
+	STASIS_MESSAGE_TYPE_CLEANUP(ast_local_optimization_end_type);
+	STASIS_MESSAGE_TYPE_CLEANUP(ast_local_bridge_type);
 }
 
 int ast_local_init(void)
 {
-	if (STASIS_MESSAGE_TYPE_INIT(local_bridge_type)) {
+
+	if (STASIS_MESSAGE_TYPE_INIT(ast_local_optimization_begin_type)) {
+		return -1;
+	}
+
+	if (STASIS_MESSAGE_TYPE_INIT(ast_local_optimization_end_type)) {
+		return -1;
+	}
+
+	if (STASIS_MESSAGE_TYPE_INIT(ast_local_bridge_type)) {
 		return -1;
 	}
 
diff --git a/main/core_unreal.c b/main/core_unreal.c
index d5e5881117df2eeb6e2d47236805b73757a6a1f5..71d0f6c8f51ba1a880af08fe50b1c2eb9c935448 100644
--- a/main/core_unreal.c
+++ b/main/core_unreal.c
@@ -281,18 +281,20 @@ int ast_unreal_answer(struct ast_channel *ast)
  */
 static int got_optimized_out(struct ast_channel *ast, struct ast_unreal_pvt *p)
 {
+	int res = 0;
+
 	/* Do a few conditional checks early on just to see if this optimization is possible */
 	if (ast_test_flag(p, AST_UNREAL_NO_OPTIMIZATION) || !p->chan || !p->owner) {
-		return 0;
+		return res;
 	}
+
 	if (ast == p->owner) {
-		return ast_bridge_unreal_optimized_out(p->owner, p->chan);
+		res = ast_bridge_unreal_optimize_out(p->owner, p->chan, p);
+	} else if (ast == p->chan) {
+		res = ast_bridge_unreal_optimize_out(p->chan, p->owner, p);
 	}
-	if (ast == p->chan) {
-		return ast_bridge_unreal_optimized_out(p->chan, p->owner);
-	}
-	/* ast is not valid to optimize. */
-	return 0;
+
+	return res;
 }
 
 struct ast_frame  *ast_unreal_read(struct ast_channel *ast)