diff --git a/bridges/bridge_builtin_features.c b/bridges/bridge_builtin_features.c
index 3a081c83dc55f065bca3195fc84cb9251890b860..2d8a68a1a605fae071af4061957f2293a0774124 100644
--- a/bridges/bridge_builtin_features.c
+++ b/bridges/bridge_builtin_features.c
@@ -218,7 +218,7 @@ static int feature_blind_transfer(struct ast_bridge *bridge, struct ast_bridge_c
 		ast_after_bridge_set_go_on(bridge_channel->chan, NULL, NULL, 0, goto_on_blindxfr);
 	}
 
-	if (ast_bridge_transfer_blind(bridge_channel->chan, exten, context, blind_transfer_cb,
+	if (ast_bridge_transfer_blind(0, bridge_channel->chan, exten, context, blind_transfer_cb,
 			bridge_channel->chan) != AST_BRIDGE_TRANSFER_SUCCESS &&
 			!ast_strlen_zero(goto_on_blindxfr)) {
 		ast_after_bridge_goto_discard(bridge_channel->chan);
diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c
index 0f5c8fc2b8229b88266f891850830a075ff8cdcf..46d1f7d06da93d3830e61882ec877b8b381ad826 100644
--- a/channels/chan_iax2.c
+++ b/channels/chan_iax2.c
@@ -10684,7 +10684,7 @@ static int socket_process_helper(struct iax2_thread *thread)
 					ast_channel_unlock(owner);
 					ast_mutex_unlock(&iaxsl[fr->callno]);
 
-					if (ast_bridge_transfer_blind(owner, ies.called_number,
+					if (ast_bridge_transfer_blind(1, owner, ies.called_number,
 								context, NULL, NULL) != AST_BRIDGE_TRANSFER_SUCCESS) {
 						ast_log(LOG_WARNING, "Blind transfer of '%s' to '%s@%s' failed\n",
 							ast_channel_name(owner), ies.called_number,
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 916fd5ebe5ade2b1e9118842678e0046d7b14bab..4b795521e067cce3afc5a1ed1dc9184da6cd0a05 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -26191,7 +26191,7 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint
 	}
 
 	sip_pvt_unlock(p);
-	transfer_res = ast_bridge_transfer_blind(transferer, refer_to, refer_to_context, blind_transfer_cb, &cb_data);
+	transfer_res = ast_bridge_transfer_blind(1, transferer, refer_to, refer_to_context, blind_transfer_cb, &cb_data);
 	sip_pvt_lock(p);
 
 	switch (transfer_res) {
diff --git a/channels/chan_skinny.c b/channels/chan_skinny.c
index 9bdc0f2d3287ed866d15dd9627d876983ad90fbc..647213228e6037ea6c45e54741b2f0ed64508714 100644
--- a/channels/chan_skinny.c
+++ b/channels/chan_skinny.c
@@ -5288,7 +5288,7 @@ static void skinny_transfer_blind(struct skinny_subchannel *sub)
 	xferee->related = NULL;
 
 	ast_queue_control(xferee->owner, AST_CONTROL_UNHOLD);
-	res = ast_bridge_transfer_blind(xferee->owner, sub->exten, sub->line->context, NULL, NULL);
+	res = ast_bridge_transfer_blind(1, xferee->owner, sub->exten, sub->line->context, NULL, NULL);
 
 	if (res != AST_BRIDGE_TRANSFER_SUCCESS) {
 		SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %d failed to blind transfer %d to '%s'@'%s' - %d\n",
diff --git a/include/asterisk/bridging.h b/include/asterisk/bridging.h
index eac4b499c936f838da614fc04b2460d6d44759b0..2fde0488b93d30717cd62e1cd24a8b6ba3fbbba4 100644
--- a/include/asterisk/bridging.h
+++ b/include/asterisk/bridging.h
@@ -1499,6 +1499,7 @@ typedef void (*transfer_channel_cb)(struct ast_channel *chan, void *user_data,
  * \note Absolutely _NO_ channel locks should be held before
  * calling this function.
  *
+ * \param is_external Indicates that transfer was initiated externally
  * \param transferer The channel performing the blind transfer
  * \param exten The dialplan extension to send the call to
  * \param context The dialplan context to send the call to
@@ -1507,8 +1508,8 @@ typedef void (*transfer_channel_cb)(struct ast_channel *chan, void *user_data,
  * \param user_data Argument for new_channel_cb
  * \return The success or failure result of the blind transfer
  */
-enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transferer,
-		const char *exten, const char *context,
+enum ast_transfer_result ast_bridge_transfer_blind(int is_external,
+		struct ast_channel *transferer, const char *exten, const char *context,
 		transfer_channel_cb new_channel_cb, void *user_data);
 
 /*!
diff --git a/include/asterisk/stasis_bridging.h b/include/asterisk/stasis_bridging.h
index 76f4a9fed38796d61cb1dda9f27e85d561a3dd61..f2e5acfd6dd734608f53cf3e68077f0b4193d64e 100644
--- a/include/asterisk/stasis_bridging.h
+++ b/include/asterisk/stasis_bridging.h
@@ -29,6 +29,7 @@ extern "C" {
 #include "asterisk/linkedlists.h"
 #include "asterisk/channel.h"
 #include "asterisk/bridging.h"
+#include "asterisk/pbx.h"
 
 /*!
  * \brief Structure that contains a snapshot of information about a bridge
@@ -210,6 +211,185 @@ void ast_bridge_publish_leave(struct ast_bridge *bridge, struct ast_channel *cha
  */
 struct ast_json *ast_bridge_snapshot_to_json(const struct ast_bridge_snapshot *snapshot);
 
+/*!
+ * \brief Pair showing a bridge snapshot and a specific channel snapshot belonging to the bridge
+ */
+struct ast_bridge_channel_snapshot_pair {
+	struct ast_bridge_snapshot *bridge_snapshot;
+	struct ast_channel_snapshot *channel_snapshot;
+};
+
+/*!
+ * \brief Pair showing a bridge and a specific channel belonging to the bridge
+ */
+struct ast_bridge_channel_pair {
+	struct ast_bridge *bridge;
+	struct ast_channel *channel;
+};
+
+/*!
+ * \brief Message representing blind transfer
+ */
+struct ast_blind_transfer_message {
+	AST_DECLARE_STRING_FIELDS(
+		/*! The destination context for the blind transfer */
+		AST_STRING_FIELD(context);
+		/*! The destination extension for the blind transfer */
+		AST_STRING_FIELD(exten);
+	);
+	/*! Result of the blind transfer */
+	enum ast_transfer_result result;
+	/*! If 0, was core DTMF transfer, otherwise occurred externally*/
+	int is_external;
+	/*! The transferer and its bridge before starting the transfer*/
+	struct ast_bridge_channel_snapshot_pair transferer;
+};
+
+/*!
+ * \since 12
+ * \brief Message type for \ref ast_blind_transfer_message.
+ *
+ * \retval Message type for \ref ast_blind_transfer_message.
+ */
+struct stasis_message_type *ast_blind_transfer_type(void);
+
+/*!
+ * \brief Publish a blind transfer event
+ *
+ * \param is_external Whether the blind transfer was initiated externally (e.g. via AMI or native protocol)
+ * \param result The success or failure of the transfer
+ * \param to_transferee The bridge between the transferer and transferee plus the transferer channel
+ * \param context The destination context for the blind transfer
+ * \param exten The destination extension for the blind transfer
+ */
+void ast_bridge_publish_blind_transfer(int is_external, enum ast_transfer_result result,
+		struct ast_bridge_channel_pair *to_transferee, const char *context, const char *exten);
+
+enum ast_attended_transfer_dest_type {
+	/*! The transfer failed, so there is no appropriate final state */
+	AST_ATTENDED_TRANSFER_DEST_FAIL,
+	/*! The transfer results in a single bridge remaining due to a merge or swap */
+	AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE,
+	/*! The transfer results in a channel or bridge running an application */
+	AST_ATTENDED_TRANSFER_DEST_APP,
+	/*! The transfer results in both bridges remaining with a local channel linking them */
+	AST_ATTENDED_TRANSFER_DEST_LINK,
+};
+
+/*!
+ * \brief Message representing attended transfer
+ */
+struct ast_attended_transfer_message {
+	/*! Result of the attended transfer */
+	enum ast_transfer_result result;
+	/*! Indicates if the transfer was initiated externally*/
+	int is_external;
+	/*! Bridge between transferer <-> transferee and the transferer channel in that bridge. May be NULL */
+	struct ast_bridge_channel_snapshot_pair to_transferee;
+	/*! Bridge between transferer <-> transfer target and the transferer channel in that bridge. May be NULL */
+	struct ast_bridge_channel_snapshot_pair to_transfer_target;
+	/*! Indicates the final state of the transfer */
+	enum ast_attended_transfer_dest_type dest_type;
+	union {
+		/*! ID of the surviving bridge. Applicable for AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE */
+		char bridge[AST_UUID_STR_LEN];
+		/*! Destination application of transfer. Applicable for AST_ATTENDED_TRANSFER_DEST_APP */
+		char app[AST_MAX_APP];
+		/*! Pair of local channels linking the bridges. Applicable for AST_ATTENDED_TRANSFER_DEST_LINK */
+		struct ast_channel_snapshot *links[2];
+	} dest;
+};
+
+/*!
+ * \since 12
+ * \brief Message type for \ref ast_attended_transfer_message.
+ *
+ * \retval Message type for \ref ast_attended_transfer_message.
+ */
+struct stasis_message_type *ast_attended_transfer_type(void);
+
+/*!
+ * \since 12
+ * \brief Publish an attended transfer failure
+ *
+ * Publish an \ref ast_attended_transfer_message with the dest_type set to
+ * \c AST_ATTENDED_TRANSFER_DEST_FAIL.
+ *
+ * \param is_external Indicates if the transfer was initiated externally
+ * \param result The result of the transfer. Will always be a type of failure.
+ * \param transferee The bridge between the transferer and transferees as well as the transferer channel from that bridge
+ * \param target The bridge between the transferer and transfer targets as well as the transferer channel from that bridge
+ */
+void ast_bridge_publish_attended_transfer_fail(int is_external, enum ast_transfer_result result,
+		struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target);
+
+/*!
+ * \since 12
+ * \brief Publish an attended transfer that results in two bridges becoming one.
+ *
+ * Publish an \ref ast_attended_transfer_message with the dest_type set to
+ * \c AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE. This type of attended transfer results from
+ * having two bridges involved and either
+ *
+ * \li Merging the two bridges together
+ * \li Moving a channel from one bridge to the other, thus emptying a bridge
+ *
+ * In either case, two bridges enter, one leaves.
+ *
+ * \param is_external Indicates if the transfer was initiated externally
+ * \param result The result of the transfer.
+ * \param transferee The bridge between the transferer and transferees as well as the transferer channel from that bridge
+ * \param target The bridge between the transferer and transfer targets as well as the transferer channel from that bridge
+ * \param final_bridge The bridge that the parties end up in. Will be a bridge from the transferee or target pair.
+ */
+void ast_bridge_publish_attended_transfer_bridge_merge(int is_external, enum ast_transfer_result result,
+		struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target,
+		struct ast_bridge *final_bridge);
+
+/*!
+ * \since 12
+ * \brief Publish an attended transfer that results in an application being run
+ *
+ * Publish an \ref ast_attended_transfer_message with the dest_type set to
+ * \c AST_ATTENDED_TRANSFER_DEST_APP. This occurs when an attended transfer
+ * results in either:
+ *
+ * \li A transferee channel leaving a bridge to run an app
+ * \li A bridge of transferees running an app (via a local channel)
+ *
+ * \param is_external Indicates if the transfer was initiated externally
+ * \param result The result of the transfer.
+ * \param transferee The bridge between the transferer and transferees as well as the transferer channel from that bridge
+ * \param target The bridge between the transferer and transfer targets as well as the transferer channel from that bridge
+ * \param dest_app The application that the channel or bridge is running upon transfer completion.
+ */
+void ast_bridge_publish_attended_transfer_app(int is_external, enum ast_transfer_result result,
+		struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target,
+		const char *dest_app);
+
+/*!
+ * \since 12
+ * \brief Publish an attended transfer that results in two bridges linked by a local channel
+ *
+ * Publish an \ref ast_attended_transfer_message with the dest_type set to
+ * \c AST_ATTENDED_TRANSFER_DEST_LINK. This occurs when two bridges are involved
+ * in an attended transfer, but their properties do not allow for the bridges to
+ * merge or to have channels moved off of the bridge. An example of this occurs when
+ * attempting to transfer a ConfBridge to another bridge.
+ *
+ * When this type of transfer occurs, the two bridges continue to exist after the
+ * transfer and a local channel is used to link the two bridges together.
+ *
+ * \param is_external Indicates if the transfer was initiated externally
+ * \param result The result of the transfer.
+ * \param transferee The bridge between the transferer and transferees as well as the transferer channel from that bridge
+ * \param target The bridge between the transferer and transfer targets as well as the transferer channel from that bridge
+ * \param locals The local channels linking the bridges together.
+ */
+void ast_bridge_publish_attended_transfer_link(int is_external, enum ast_transfer_result result,
+		struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target,
+		struct ast_channel *locals[2]);
+
 /*!
  * \brief Returns the most recent snapshot for the bridge.
  *
diff --git a/main/bridging.c b/main/bridging.c
index ee7f511cbcde25f3ab1cdba3b17360e264580d72..92c66ac5c7bbd1f4d8b47a1a28c7959f12f24218 100644
--- a/main/bridging.c
+++ b/main/bridging.c
@@ -5923,6 +5923,113 @@ static enum ast_transfer_result blind_transfer_bridge(struct ast_channel *transf
 	return AST_BRIDGE_TRANSFER_SUCCESS;
 }
 
+/*!
+ * \internal
+ * \brief Base data to publish for stasis attended transfer messages
+ */
+struct stasis_attended_transfer_publish_data {
+	/* The bridge between the transferer and transferee, and the transferer channel in this bridge */
+	struct ast_bridge_channel_pair to_transferee;
+	/* The bridge between the transferer and transfer target, and the transferer channel in this bridge */
+	struct ast_bridge_channel_pair to_transfer_target;
+};
+
+static void stasis_publish_data_cleanup(struct stasis_attended_transfer_publish_data *publication)
+{
+	ast_channel_unref(publication->to_transferee.channel);
+	ast_channel_unref(publication->to_transfer_target.channel);
+	ao2_cleanup(publication->to_transferee.bridge);
+	ao2_cleanup(publication->to_transfer_target.bridge);
+}
+
+/*!
+ * \internal
+ * \brief Set up base data for an attended transfer stasis publication
+ *
+ * \param to_transferee The original transferer channel, which may be bridged to a transferee
+ * \param to_transferee_bridge The bridge that to_transferee is in.
+ * \param to_transfer_target The second transferer channel, which may be bridged to a transfer target
+ * \param to_target_bridge The bridge that to_transfer_target_is in.
+ * \param[out] publication A structure to hold the other parameters
+ */
+static void stasis_publish_data_init(struct ast_channel *to_transferee,
+		struct ast_bridge *to_transferee_bridge, struct ast_channel *to_transfer_target,
+		struct ast_bridge *to_target_bridge,
+		struct stasis_attended_transfer_publish_data *publication)
+{
+	memset(publication, 0, sizeof(*publication));
+	publication->to_transferee.channel = ast_channel_ref(to_transferee);
+	if (to_transferee_bridge) {
+		ao2_ref(to_transferee_bridge, +1);
+		publication->to_transferee.bridge = to_transferee_bridge;
+	}
+
+	publication->to_transfer_target.channel = ast_channel_ref(to_transfer_target);
+	if (to_target_bridge) {
+		ao2_ref(to_target_bridge, +1);
+		publication->to_transfer_target.bridge = to_target_bridge;
+	}
+}
+
+/*
+ * \internal
+ * \brief Publish a stasis attended transfer resulting in a bridge merge
+ *
+ * \param publication Base data about the attended transfer
+ * \param final_bridge The surviving bridge of the attended transfer
+ */
+static void publish_attended_transfer_bridge_merge(struct stasis_attended_transfer_publish_data *publication,
+		struct ast_bridge *final_bridge)
+{
+	ast_bridge_publish_attended_transfer_bridge_merge(1, AST_BRIDGE_TRANSFER_SUCCESS,
+			&publication->to_transferee, &publication->to_transfer_target, final_bridge);
+}
+
+/*
+ * \internal
+ * \brief Publish a stasis attended transfer to an application
+ *
+ * \param publication Base data about the attended transfer
+ * \param app The app that is running at the conclusion of the transfer
+ */
+static void publish_attended_transfer_app(struct stasis_attended_transfer_publish_data *publication,
+		const char *app)
+{
+	ast_bridge_publish_attended_transfer_app(1, AST_BRIDGE_TRANSFER_SUCCESS,
+			&publication->to_transferee, &publication->to_transfer_target, app);
+}
+
+/*
+ * \internal
+ * \brief Publish a stasis attended transfer showing a link between bridges
+ *
+ * \param publication Base data about the attended transfer
+ * \param local_channel1 Local channel in the original bridge
+ * \param local_channel2 Local channel in the second bridge
+ */
+static void publish_attended_transfer_link(struct stasis_attended_transfer_publish_data *publication,
+		struct ast_channel *local_channel1, struct ast_channel *local_channel2)
+{
+	struct ast_channel *locals[2] = { local_channel1, local_channel2 };
+
+	ast_bridge_publish_attended_transfer_link(1, AST_BRIDGE_TRANSFER_SUCCESS,
+			&publication->to_transferee, &publication->to_transfer_target, locals);
+}
+
+/*
+ * \internal
+ * \brief Publish a stasis attended transfer failure
+ *
+ * \param publication Base data about the attended transfer
+ * \param result The transfer result
+ */
+static void publish_attended_transfer_fail(struct stasis_attended_transfer_publish_data *publication,
+		enum ast_transfer_result result)
+{
+	ast_bridge_publish_attended_transfer_fail(1, result, &publication->to_transferee,
+			&publication->to_transfer_target);
+}
+
 /*!
  * \brief Perform an attended transfer of a bridge
  *
@@ -5941,16 +6048,19 @@ static enum ast_transfer_result blind_transfer_bridge(struct ast_channel *transf
  * \param chan2 Other transferer channel. May or may not be bridged.
  * \param bridge1 Bridge that chan1 is in. Guaranteed to be non-NULL.
  * \param bridge2 Bridge that chan2 is in. If NULL, then chan2 is not bridged.
+ * \param publication Data to publish for a stasis attended transfer message.
  * \retval AST_BRIDGE_TRANSFER_FAIL Internal error occurred
  * \retval AST_BRIDGE_TRANSFER_SUCCESS Succesfully transferred the bridge
  */
 static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *chan1,
-		struct ast_channel *chan2, struct ast_bridge *bridge1, struct ast_bridge *bridge2)
+		struct ast_channel *chan2, struct ast_bridge *bridge1, struct ast_bridge *bridge2,
+		struct stasis_attended_transfer_publish_data *publication)
 {
 	static const char *dest = "_attended@transfer/m";
 	struct ast_channel *local_chan;
 	int cause;
 	int res;
+	const char *app = NULL;
 
 	local_chan = ast_request("Local", ast_channel_nativeformats(chan1), chan1,
 			dest, &cause);
@@ -5962,6 +6072,7 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
 	if (bridge2) {
 		res = ast_local_setup_bridge(local_chan, bridge2, chan2, NULL);
 	} else {
+		app = ast_strdupa(ast_channel_appl(chan2));
 		res = ast_local_setup_masquerade(local_chan, chan2);
 	}
 
@@ -5980,6 +6091,20 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
 		return AST_BRIDGE_TRANSFER_FAIL;
 	}
 
+	if (bridge2) {
+		RAII_VAR(struct ast_channel *, local_chan2, NULL, ao2_cleanup);
+
+		ast_channel_lock(local_chan);
+		local_chan2 = ast_local_get_peer(local_chan);
+		ast_channel_unlock(local_chan);
+
+		ast_assert(local_chan2 != NULL);
+
+		publish_attended_transfer_link(publication,
+				local_chan, local_chan2);
+	} else {
+		publish_attended_transfer_app(publication, app);
+	}
 	return AST_BRIDGE_TRANSFER_SUCCESS;
 }
 
@@ -6162,8 +6287,18 @@ static struct ast_bridge *acquire_bridge(struct ast_channel *chan)
 	return bridge;
 }
 
-enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transferer,
-		const char *exten, const char *context,
+static void publish_blind_transfer(int is_external, enum ast_transfer_result result,
+		struct ast_channel *transferer, struct ast_bridge *bridge,
+		const char *context, const char *exten)
+{
+	struct ast_bridge_channel_pair pair;
+	pair.channel = transferer;
+	pair.bridge = bridge;
+	ast_bridge_publish_blind_transfer(is_external, result, &pair, context, exten);
+}
+
+enum ast_transfer_result ast_bridge_transfer_blind(int is_external,
+		struct ast_channel *transferer, const char *exten, const char *context,
 		transfer_channel_cb new_channel_cb, void *user_data)
 {
 	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
@@ -6173,16 +6308,19 @@ enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transfere
 	int do_bridge_transfer;
 	int transfer_prohibited;
 	enum try_parking_result parking_result;
+	enum ast_transfer_result transfer_result;
 
 	bridge = acquire_bridge(transferer);
 	if (!bridge) {
-		return AST_BRIDGE_TRANSFER_INVALID;
+		transfer_result = AST_BRIDGE_TRANSFER_INVALID;
+		goto publish;
 	}
 	ast_channel_lock(transferer);
 	bridge_channel = ast_channel_get_bridge_channel(transferer);
 	ast_channel_unlock(transferer);
 	if (!bridge_channel) {
-		return AST_BRIDGE_TRANSFER_INVALID;
+		transfer_result = AST_BRIDGE_TRANSFER_INVALID;
+		goto publish;
 	}
 
 	/* Take off hold if they are on hold. */
@@ -6191,9 +6329,11 @@ enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transfere
 	parking_result = try_parking(bridge, transferer, exten, context);
 	switch (parking_result) {
 	case PARKING_SUCCESS:
-		return AST_BRIDGE_TRANSFER_SUCCESS;
+		transfer_result = AST_BRIDGE_TRANSFER_SUCCESS;
+		goto publish;
 	case PARKING_FAILURE:
-		return AST_BRIDGE_TRANSFER_FAIL;
+		transfer_result = AST_BRIDGE_TRANSFER_FAIL;
+		goto publish;
 	case PARKING_NOT_APPLICABLE:
 	default:
 		break;
@@ -6204,10 +6344,12 @@ enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transfere
 
 		channels = ast_bridge_peers_nolock(bridge);
 		if (!channels) {
-			return AST_BRIDGE_TRANSFER_FAIL;
+			transfer_result = AST_BRIDGE_TRANSFER_FAIL;
+			goto publish;
 		}
 		if (ao2_container_count(channels) <= 1) {
-			return AST_BRIDGE_TRANSFER_INVALID;
+			transfer_result = AST_BRIDGE_TRANSFER_INVALID;
+			goto publish;
 		}
 		transfer_prohibited = ast_test_flag(&bridge->feature_flags,
 				AST_BRIDGE_FLAG_TRANSFER_PROHIBITED);
@@ -6217,30 +6359,38 @@ enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transfere
 	}
 
 	if (transfer_prohibited) {
-		return AST_BRIDGE_TRANSFER_NOT_PERMITTED;
+		transfer_result = AST_BRIDGE_TRANSFER_NOT_PERMITTED;
+		goto publish;
 	}
 
 	set_blind_transfer_variables(transferer, channels);
 
 	if (do_bridge_transfer) {
-		return blind_transfer_bridge(transferer, bridge, exten, context,
+		transfer_result = blind_transfer_bridge(transferer, bridge, exten, context,
 				new_channel_cb, user_data);
+		goto publish;
 	}
 
 	/* Reaching this portion means that we're dealing with a two-party bridge */
 
 	transferee = get_transferee(channels, transferer);
 	if (!transferee) {
-		return AST_BRIDGE_TRANSFER_FAIL;
+		transfer_result = AST_BRIDGE_TRANSFER_FAIL;
+		goto publish;
 	}
 
 	if (bridge_channel_queue_blind_transfer(transferee, exten, context,
 				new_channel_cb, user_data)) {
-		return AST_BRIDGE_TRANSFER_FAIL;
+		transfer_result = AST_BRIDGE_TRANSFER_FAIL;
+		goto publish;
 	}
 
 	ast_bridge_remove(bridge, transferer);
-	return AST_BRIDGE_TRANSFER_SUCCESS;
+	transfer_result = AST_BRIDGE_TRANSFER_SUCCESS;
+
+publish:
+	publish_blind_transfer(is_external, transfer_result, transferer, bridge, context, exten);
+	return transfer_result;
 }
 
 /*!
@@ -6297,30 +6447,42 @@ static enum ast_transfer_result bridge_swap_attended_transfer(struct ast_bridge
  * \param to_target_bridge_channel to_transfer_target's bridge_channel
  * \param to_transferee_bridge The bridge between to_transferee and the transferee
  * \param to_target_bridge The bridge between to_transfer_target and the transfer_target
+ * \param publication Data to publish for a stasis attended transfer message
  * \return The success or failure of the attended transfer
  */
 static enum ast_transfer_result two_bridge_attended_transfer(struct ast_channel *to_transferee,
 		struct ast_bridge_channel *to_transferee_bridge_channel,
 		struct ast_channel *to_transfer_target,
 		struct ast_bridge_channel *to_target_bridge_channel,
-		struct ast_bridge *to_transferee_bridge, struct ast_bridge *to_target_bridge)
+		struct ast_bridge *to_transferee_bridge, struct ast_bridge *to_target_bridge,
+		struct stasis_attended_transfer_publish_data *publication)
 {
 	struct ast_bridge_channel *kick_me[] = {
 			to_transferee_bridge_channel,
 			to_target_bridge_channel,
 	};
+	enum ast_transfer_result res;
+	struct ast_bridge *final_bridge = NULL;
 
 	switch (ast_bridges_allow_optimization(to_transferee_bridge, to_target_bridge)) {
 	case AST_BRIDGE_OPTIMIZE_SWAP_TO_CHAN_BRIDGE:
-		return bridge_swap_attended_transfer(to_transferee_bridge, to_target_bridge_channel, to_transferee);
+		final_bridge = to_transferee_bridge;
+		res = bridge_swap_attended_transfer(to_transferee_bridge, to_target_bridge_channel, to_transferee);
+		goto end;
 	case AST_BRIDGE_OPTIMIZE_SWAP_TO_PEER_BRIDGE:
-		return bridge_swap_attended_transfer(to_target_bridge, to_transferee_bridge_channel, to_transfer_target);
+		final_bridge = to_target_bridge;
+		res = bridge_swap_attended_transfer(to_target_bridge, to_transferee_bridge_channel, to_transfer_target);
+		goto end;
 	case AST_BRIDGE_OPTIMIZE_MERGE_TO_CHAN_BRIDGE:
+		final_bridge = to_transferee_bridge;
 		bridge_merge_do(to_transferee_bridge, to_target_bridge, kick_me, ARRAY_LEN(kick_me));
-		return AST_BRIDGE_TRANSFER_SUCCESS;
+		res = AST_BRIDGE_TRANSFER_SUCCESS;
+		goto end;
 	case AST_BRIDGE_OPTIMIZE_MERGE_TO_PEER_BRIDGE:
+		final_bridge = to_target_bridge;
 		bridge_merge_do(to_target_bridge, to_transferee_bridge, kick_me, ARRAY_LEN(kick_me));
-		return AST_BRIDGE_TRANSFER_SUCCESS;
+		res = AST_BRIDGE_TRANSFER_SUCCESS;
+		goto end;
 	case AST_BRIDGE_OPTIMIZE_PROHIBITED:
 	default:
 		/* Just because optimization wasn't doable doesn't necessarily mean
@@ -6329,11 +6491,23 @@ static enum ast_transfer_result two_bridge_attended_transfer(struct ast_channel
 		 */
 		if (to_transferee_bridge->inhibit_merge || to_transferee_bridge->dissolved ||
 				to_target_bridge->inhibit_merge || to_target_bridge->dissolved) {
-			return AST_BRIDGE_TRANSFER_INVALID;
+			res = AST_BRIDGE_TRANSFER_INVALID;
+			goto end;
 		}
+
+		/* Don't goto end here. attended_transfer_bridge will publish its own
+		 * stasis message if it succeeds
+		 */
 		return attended_transfer_bridge(to_transferee, to_transfer_target,
-			to_transferee_bridge, to_target_bridge);
+			to_transferee_bridge, to_target_bridge, publication);
 	}
+
+end:
+	if (res == AST_BRIDGE_TRANSFER_SUCCESS) {
+		publish_attended_transfer_bridge_merge(publication, final_bridge);
+	}
+
+	return res;
 }
 
 enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_transferee,
@@ -6350,13 +6524,20 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra
 	struct ast_channel *chan_unbridged;
 	int transfer_prohibited;
 	int do_bridge_transfer;
+	enum ast_transfer_result res;
+	const char *app = NULL;
+	struct stasis_attended_transfer_publish_data publication;
 
 	to_transferee_bridge = acquire_bridge(to_transferee);
 	to_target_bridge = acquire_bridge(to_transfer_target);
 
+	stasis_publish_data_init(to_transferee, to_transferee_bridge,
+			to_transfer_target, to_target_bridge, &publication);
+
 	/* They can't both be unbridged, you silly goose! */
 	if (!to_transferee_bridge && !to_target_bridge) {
-		return AST_BRIDGE_TRANSFER_INVALID;
+		res = AST_BRIDGE_TRANSFER_INVALID;
+		goto end;
 	}
 
 	ast_channel_lock(to_transferee);
@@ -6407,20 +6588,20 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra
 
 	/* Let's get the easy one out of the way first */
 	if (to_transferee_bridge && to_target_bridge) {
-		enum ast_transfer_result res;
 
 		if (!to_transferee_bridge_channel || !to_target_bridge_channel) {
-			return AST_BRIDGE_TRANSFER_INVALID;
+			res = AST_BRIDGE_TRANSFER_INVALID;
+			goto end;
 		}
 
 		ast_bridge_lock_both(to_transferee_bridge, to_target_bridge);
 		res = two_bridge_attended_transfer(to_transferee, to_transferee_bridge_channel,
 				to_transfer_target, to_target_bridge_channel,
-				to_transferee_bridge, to_target_bridge);
+				to_transferee_bridge, to_target_bridge, &publication);
 		ast_bridge_unlock(to_transferee_bridge);
 		ast_bridge_unlock(to_target_bridge);
 
-		return res;
+		goto end;
 	}
 
 	the_bridge = to_transferee_bridge ?: to_target_bridge;
@@ -6433,11 +6614,13 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra
 
 		channels = ast_bridge_peers_nolock(the_bridge);
 		if (!channels) {
-			return AST_BRIDGE_TRANSFER_FAIL;
+			res = AST_BRIDGE_TRANSFER_FAIL;
+			goto end;
 		}
 		chan_count = ao2_container_count(channels);
 		if (chan_count <= 1) {
-			return AST_BRIDGE_TRANSFER_INVALID;
+			res = AST_BRIDGE_TRANSFER_INVALID;
+			goto end;
 		}
 		transfer_prohibited = ast_test_flag(&the_bridge->feature_flags,
 				AST_BRIDGE_FLAG_TRANSFER_PROHIBITED);
@@ -6447,24 +6630,41 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra
 	}
 
 	if (transfer_prohibited) {
-		return AST_BRIDGE_TRANSFER_NOT_PERMITTED;
+		res = AST_BRIDGE_TRANSFER_NOT_PERMITTED;
+		goto end;
 	}
 
 	if (do_bridge_transfer) {
-		return attended_transfer_bridge(chan_bridged, chan_unbridged, the_bridge, NULL);
+		 res = attended_transfer_bridge(chan_bridged, chan_unbridged, the_bridge, NULL, &publication);
+		 goto end;
 	}
 
 	transferee = get_transferee(channels, chan_bridged);
 	if (!transferee) {
-		return AST_BRIDGE_TRANSFER_FAIL;
+		res = AST_BRIDGE_TRANSFER_FAIL;
+		goto end;
 	}
 
+	app = ast_strdupa(ast_channel_appl(chan_unbridged));
 	if (bridge_channel_queue_attended_transfer(transferee, chan_unbridged)) {
-		return AST_BRIDGE_TRANSFER_FAIL;
+		res = AST_BRIDGE_TRANSFER_FAIL;
+		goto end;
 	}
 
 	ast_bridge_remove(the_bridge, chan_bridged);
+
+	publish_attended_transfer_app(&publication, app);
 	return AST_BRIDGE_TRANSFER_SUCCESS;
+
+end:
+	/* All successful transfer paths have published an appropriate stasis message.
+	 * All failure paths have deferred publishing a stasis message until this point
+	 */
+	if (res != AST_BRIDGE_TRANSFER_SUCCESS) {
+		publish_attended_transfer_fail(&publication, res);
+	}
+	stasis_publish_data_cleanup(&publication);
+	return res;
 }
 
 /*!
diff --git a/main/manager.c b/main/manager.c
index 8f4fffbf2fb74e94d7cce246cb984195947a0e36..7e5b1080c51a75e5b3d36bdfb822aacccb1a6952 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -4064,7 +4064,7 @@ static int action_blind_transfer(struct mansession *s, const struct message *m)
 		context = ast_channel_context(chan);
 	}
 
-	switch (ast_bridge_transfer_blind(chan, exten, context, NULL, NULL)) {
+	switch (ast_bridge_transfer_blind(1, chan, exten, context, NULL, NULL)) {
 	case AST_BRIDGE_TRANSFER_NOT_PERMITTED:
 		astman_send_error(s, m, "Transfer not permitted");
 		break;
diff --git a/main/manager_bridging.c b/main/manager_bridging.c
index 01ce68aaf55b850db29cef145649edca4057abc6..5e5f409ee0b8a3aa7e0393d915777c4c85c44df5 100644
--- a/main/manager_bridging.c
+++ b/main/manager_bridging.c
@@ -108,6 +108,11 @@ static struct stasis_message_router *bridge_state_router;
 	</manager>
  ***/
 
+/*! \brief The \ref stasis subscription returned by the forwarding of the channel topic
+ * to the manager topic
+ */
+static struct stasis_subscription *topic_forwarder;
+
 struct ast_str *ast_manager_build_bridge_state_string(
 	const struct ast_bridge_snapshot *snapshot,
 	const char *suffix)
@@ -409,10 +414,16 @@ static int manager_bridge_info(struct mansession *s, const struct message *m)
 	return 0;
 }
 
-static void manager_bridging_shutdown(void)
+static void manager_bridging_cleanup(void)
 {
 	stasis_message_router_unsubscribe(bridge_state_router);
 	bridge_state_router = NULL;
+	stasis_unsubscribe(topic_forwarder);
+	topic_forwarder = NULL;
+}
+
+static void manager_bridging_shutdown(void)
+{
 	ast_manager_unregister("BridgeList");
 	ast_manager_unregister("BridgeInfo");
 }
@@ -420,6 +431,8 @@ static void manager_bridging_shutdown(void)
 int manager_bridging_init(void)
 {
 	int ret = 0;
+	struct stasis_topic *manager_topic;
+	struct stasis_topic *bridge_topic;
 
 	if (bridge_state_router) {
 		/* Already initialized */
@@ -427,10 +440,29 @@ int manager_bridging_init(void)
 	}
 
 	ast_register_atexit(manager_bridging_shutdown);
+	ast_register_cleanup(manager_bridging_cleanup);
+
+	manager_topic = ast_manager_get_topic();
+	if (!manager_topic) {
+		return -1;
+	}
+
+	bridge_topic = stasis_caching_get_topic(ast_bridge_topic_all_cached());
+	if (!bridge_topic) {
+		return -1;
+	}
 
-	bridge_state_router = stasis_message_router_create(
-		stasis_caching_get_topic(ast_bridge_topic_all_cached()));
+	topic_forwarder = stasis_forward_all(bridge_topic, manager_topic);
+	if (!topic_forwarder) {
+		return -1;
+	}
 
+	/* BUGBUG - This should really route off of the manager_router, but
+	 * can't b/c manager_channels is already routing the
+	 * stasis_cache_update_type() messages. Having a separate router can
+	 * cause some message ordering issues with bridge and channel messages.
+	 */
+	bridge_state_router = stasis_message_router_create(bridge_topic);
 	if (!bridge_state_router) {
 		return -1;
 	}
diff --git a/main/stasis_bridging.c b/main/stasis_bridging.c
index 3c4ac1fb176d9475e42aac191da2461877d15efa..0b6411671613dc7a996983108d675f4e1317860e 100644
--- a/main/stasis_bridging.c
+++ b/main/stasis_bridging.c
@@ -41,6 +41,321 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #define SNAPSHOT_CHANNELS_BUCKETS 13
 
+/*** DOCUMENTATION
+	<managerEvent language="en_US" name="BlindTransfer">
+		<managerEventInstance class="EVENT_FLAG_CALL">
+			<synopsis>Raised when a blind transfer is complete.</synopsis>
+			<syntax>
+				<parameter name="Result">
+					<para>Indicates if the transfer was successful or if it failed.</para>
+					<enumlist>
+						<enum name="Fail"><para>An internal error occurred.</para></enum>
+						<enum name="Invalid"><para>Invalid configuration for transfer (e.g. Not bridged)</para></enum>
+						<enum name="Not Permitted"><para>Bridge does not permit transfers</para></enum>
+						<enum name="Success"><para>Transfer completed successfully</para></enum>
+					</enumlist>
+					<note><para>A result of <literal>Success</literal> does not necessarily mean that a target was succesfully
+					contacted. It means that a party was succesfully placed into the dialplan at the expected location.</para></note>
+				</parameter>
+				<parameter name="TransfererChannel">
+					<para>The name of the channel that performed the transfer</para>
+				</parameter>
+				<parameter name="TransfererChannelStateDesc">
+					<enumlist>
+						<enum name="Down"/>
+						<enum name="Rsrvd"/>
+						<enum name="OffHook"/>
+						<enum name="Dialing"/>
+						<enum name="Ring"/>
+						<enum name="Ringing"/>
+						<enum name="Up"/>
+						<enum name="Busy"/>
+						<enum name="Dialing Offhook"/>
+						<enum name="Pre-ring"/>
+						<enum name="Unknown"/>
+					</enumlist>
+				</parameter>
+				<parameter name="TransfererCallerIDNum">
+				</parameter>
+				<parameter name="TransfererCallerIDName">
+				</parameter>
+				<parameter name="TransfererConnectedLineNum">
+				</parameter>
+				<parameter name="TransfererConnectedLineName">
+				</parameter>
+				<parameter name="TransfererAccountCode">
+				</parameter>
+				<parameter name="TransfererContext">
+				</parameter>
+				<parameter name="TransfererExten">
+				</parameter>
+				<parameter name="TransfererPriority">
+				</parameter>
+				<parameter name="TransfererUniqueid">
+				</parameter>
+				<parameter name="BridgeUniqueid">
+					<para>The ID of the bridge where the Transferer performed the transfer</para>
+				</parameter>
+				<parameter name="BridgeType">
+					<para>The type of the bridge where the Transferer performed the transfer</para>
+				</parameter>
+				<parameter name="IsExternal">
+					<para>Indicates if the transfer was performed outside of Asterisk. For instance,
+					a channel protocol native transfer is external. A DTMF transfer is internal.</para>
+						<enumlist>
+							<enum name="Yes" />
+							<enum name="No" />
+						</enumlist>
+				</parameter>
+				<parameter name="Context">
+					<para>Destination context for the blind transfer.</para>
+				</parameter>
+				<parameter name="Extension">
+					<para>Destination extension for the blind transfer.</para>
+				</parameter>
+			</syntax>
+		</managerEventInstance>
+	</managerEvent>
+	<managerEvent language="en_US" name="AttendedTransfer">
+		<managerEventInstance class="EVENT_FLAG_CALL">
+			<synopsis>Raised when an attended transfer is complete.</synopsis>
+			<syntax>
+				<xi:include xpointer="xpointer(docs/managerEvent[@name='BlindTransfer']/managerEventInstance/syntax/parameter[@name='Result'])" />
+				<parameter name="OrigTransfererChannel">
+					<para>The original transferer channel that performed the attended transfer.</para>
+				</parameter>
+				<parameter name="OrigTransfererChannelState">
+					<para>A numeric code for the channel's current state, related to DestChannelStateDesc</para>
+				</parameter>
+				<parameter name="OrigTransfererChannelStateDesc">
+					<enumlist>
+						<enum name="Down"/>
+						<enum name="Rsrvd"/>
+						<enum name="OffHook"/>
+						<enum name="Dialing"/>
+						<enum name="Ring"/>
+						<enum name="Ringing"/>
+						<enum name="Up"/>
+						<enum name="Busy"/>
+						<enum name="Dialing Offhook"/>
+						<enum name="Pre-ring"/>
+						<enum name="Unknown"/>
+					</enumlist>
+				</parameter>
+				<parameter name="OrigTransfererCallerIDNum">
+				</parameter>
+				<parameter name="OrigTransfererCallerIDName">
+				</parameter>
+				<parameter name="OrigTransfererConnectedLineNum">
+				</parameter>
+				<parameter name="OrigTransfererConnectedLineName">
+				</parameter>
+				<parameter name="OrigTransfererAccountCode">
+				</parameter>
+				<parameter name="OrigTransfererContext">
+				</parameter>
+				<parameter name="OrigTransfererExten">
+				</parameter>
+				<parameter name="OrigTransfererPriority">
+				</parameter>
+				<parameter name="OrigTransfererUniqueid">
+				</parameter>
+				<parameter name="BridgeUniqueidOrig">
+					<para>The ID of the bridge where the Transferer performed the transfer</para>
+					<note><para>This header will not be present if the original transferer was not in a bridge.</para></note>
+				</parameter>
+				<parameter name="BridgeTypeOrig">
+					<para>The type of the bridge where the Transferer performed the transfer</para>
+					<note><para>This header will not be present if the original transferer was not in a bridge.</para></note>
+				</parameter>
+				<parameter name="SecondTransfererChannel">
+					<para>The second transferer channel involved in the attended transfer.</para>
+				</parameter>
+				<parameter name="SecondTransfererChannelState">
+					<para>A numeric code for the channel's current state, related to SecondTransfererChannelStateDesc</para>
+				</parameter>
+				<parameter name="SecondTransfererChannelStateDesc">
+					<enumlist>
+						<enum name="Down"/>
+						<enum name="Rsrvd"/>
+						<enum name="OffHook"/>
+						<enum name="Dialing"/>
+						<enum name="Ring"/>
+						<enum name="Ringing"/>
+						<enum name="Up"/>
+						<enum name="Busy"/>
+						<enum name="Dialing Offhook"/>
+						<enum name="Pre-ring"/>
+						<enum name="Unknown"/>
+					</enumlist>
+				</parameter>
+				<parameter name="SecondTransfererCallerIDNum">
+				</parameter>
+				<parameter name="SecondTransfererCallerIDName">
+				</parameter>
+				<parameter name="SecondTransfererConnectedLineNum">
+				</parameter>
+				<parameter name="SecondTransfererConnectedLineName">
+				</parameter>
+				<parameter name="SecondTransfererAccountCode">
+				</parameter>
+				<parameter name="SecondTransfererContext">
+				</parameter>
+				<parameter name="SecondTransfererExten">
+				</parameter>
+				<parameter name="SecondTransfererPriority">
+				</parameter>
+				<parameter name="SecondTransfererUniqueid">
+				</parameter>
+				<parameter name="BridgeUniqueidSecond">
+					<para>The unique ID of the bridge that the second transferer channel was in, or <literal>None</literal> if the second transferer channel was not bridged</para>
+					<note><para>This header will not be present if the second transferer was not in a bridge.</para></note>
+				</parameter>
+				<parameter name="BridgeTypeSecond">
+					<para>The type of the bridge where the Transferer performed the transfer</para>
+					<note><para>This header will not be present if the second transferer was not in a bridge.</para></note>
+				</parameter>
+				<parameter name="DestType">
+					<para>Indicates the method by which the attended transfer completed.</para>
+					<enumlist>
+						<enum name="Bridge"><para>The transfer was accomplished by merging two bridges into one.</para></enum>
+						<enum name="App"><para>The transfer was accomplished by having a channel or bridge run a dialplan application.</para></enum>
+						<enum name="Link"><para>The transfer was accomplished by linking two bridges together using a local channel pair.</para></enum>
+						<enum name="Fail"><para>The transfer failed.</para></enum>
+					</enumlist>
+				</parameter>
+				<parameter name="DestBridgeUniqueid">
+					<para>Indicates the surviving bridge when bridges were merged to complete the transfer</para>
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Bridge</literal></para></note>
+				</parameter>
+				<parameter name="DestApp">
+					<para>Indicates the application that is running when the transfer completes</para>
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>App</literal></para></note>
+				</parameter>
+				<parameter name="LocalOneChannel">
+					<para>The local channel that is bridged with the original bridge when forming a link between bridges</para>
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalOneChannelState">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalOneChannelStateDesc">
+					<enumlist>
+						<enum name="Down"/>
+						<enum name="Rsrvd"/>
+						<enum name="OffHook"/>
+						<enum name="Dialing"/>
+						<enum name="Ring"/>
+						<enum name="Ringing"/>
+						<enum name="Up"/>
+						<enum name="Busy"/>
+						<enum name="Dialing Offhook"/>
+						<enum name="Pre-ring"/>
+						<enum name="Unknown"/>
+					</enumlist>
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalOneCallerIDNum">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalOneCallerIDName">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalOneConnectedLineNum">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalOneConnectedLineName">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalOneAccountCode">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalOneContext">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalOneExten">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalOnePriority">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalOneUniqueid">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalTwoChannel">
+					<para>The local channel that is bridged with the second bridge when forming a link between bridges</para>
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalTwoChannelState">
+					<para>A numeric code for the channel's current state, related to LocalTwoChannelStateDesc</para>
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalTwoChannelStateDesc">
+					<enumlist>
+						<enum name="Down"/>
+						<enum name="Rsrvd"/>
+						<enum name="OffHook"/>
+						<enum name="Dialing"/>
+						<enum name="Ring"/>
+						<enum name="Ringing"/>
+						<enum name="Up"/>
+						<enum name="Busy"/>
+						<enum name="Dialing Offhook"/>
+						<enum name="Pre-ring"/>
+						<enum name="Unknown"/>
+					</enumlist>
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalTwoCallerIDNum">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalTwoCallerIDName">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalTwoConnectedLineNum">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalTwoConnectedLineName">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalTwoAccountCode">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalTwoContext">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalTwoExten">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalTwoPriority">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+				<parameter name="LocalTwoUniqueid">
+					<note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note>
+				</parameter>
+			</syntax>
+			<description>
+				<para>The headers in this event attempt to describe all the major details of the attended transfer. The two transferer channels
+				and the two bridges are determined based on their chronological establishment. So consider that Alice calls Bob, and then Alice
+				transfers the call to Voicemail. The transferer and bridge headers would be arranged as follows:</para>
+				<para>	<replaceable>OrigTransfererChannel</replaceable>: Alice's channel in the bridge with Bob.</para>
+				<para>	<replaceable>BridgeUniqueidOrig</replaceable>: The bridge between Alice and Bob.</para>
+				<para>	<replaceable>SecondTransfererChannel</replaceable>: Alice's channel that called Voicemail.</para>
+				<para>	<replaceable>BridgeUniqueidSecond</replaceable>: Not present, since a call to Voicemail has no bridge.</para>
+				<para>Now consider if the order were reversed; instead of having Alice call Bob and transfer him to Voicemail, Alice instead
+				calls her Voicemail and transfers that to Bob. The transferer and bridge headers would be arranged as follows:</para>
+				<para>	<replaceable>OrigTransfererChannel</replaceable>: Alice's channel that called Voicemail.</para>
+				<para>	<replaceable>BridgeUniqueidOrig</replaceable>: Not present, since a call to Voicemail has no bridge.</para>
+				<para>	<replaceable>SecondTransfererChannel</replaceable>: Alice's channel in the bridge with Bob.</para>
+				<para>	<replaceable>BridgeUniqueidSecond</replaceable>: The bridge between Alice and Bob.</para>
+			</description>
+		</managerEventInstance>
+	</managerEvent>
+ ***/
+
+static struct ast_manager_event_blob *attended_transfer_to_ami(struct stasis_message *message);
+static struct ast_manager_event_blob *blind_transfer_to_ami(struct stasis_message *message);
+
 /*!
  * @{ \brief Define bridge message types.
  */
@@ -48,6 +363,8 @@ STASIS_MESSAGE_TYPE_DEFN(ast_bridge_snapshot_type);
 STASIS_MESSAGE_TYPE_DEFN(ast_bridge_merge_message_type);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_entered_bridge_type);
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_left_bridge_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_blind_transfer_type, .to_ami = blind_transfer_to_ami);
+STASIS_MESSAGE_TYPE_DEFN(ast_attended_transfer_type, .to_ami = attended_transfer_to_ami);
 /*! @} */
 
 /*! \brief Aggregate topic for bridge messages */
@@ -352,6 +669,330 @@ struct ast_json *ast_bridge_snapshot_to_json(const struct ast_bridge_snapshot *s
 	return ast_json_ref(json_bridge);
 }
 
+/*!
+ * \internal
+ * \brief Allocate the fields of an \ref ast_bridge_channel_snapshot_pair.
+ *
+ * \param pair A bridge and channel to get snapshots of
+ * \param[out] snapshot_pair An allocated snapshot pair.
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+static int bridge_channel_snapshot_pair_init(struct ast_bridge_channel_pair *pair, struct ast_bridge_channel_snapshot_pair *snapshot_pair)
+{
+	if (pair->bridge) {
+		snapshot_pair->bridge_snapshot = ast_bridge_snapshot_create(pair->bridge);
+		if (!snapshot_pair->bridge_snapshot) {
+			return -1;
+		}
+	}
+
+	snapshot_pair->channel_snapshot = ast_channel_snapshot_create(pair->channel);
+	if (!snapshot_pair->channel_snapshot) {
+		return -1;
+	}
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Free the fields of an \ref ast_bridge_channel_snapshot_pair.
+ *
+ * \param pair The snapshot pair whose fields are to be cleaned up
+ */
+static void bridge_channel_snapshot_pair_cleanup(struct ast_bridge_channel_snapshot_pair *pair)
+{
+	ao2_cleanup(pair->bridge_snapshot);
+	ao2_cleanup(pair->channel_snapshot);
+}
+
+static const char *result_strs[] = {
+	[AST_BRIDGE_TRANSFER_FAIL] = "Fail",
+	[AST_BRIDGE_TRANSFER_INVALID] = "Invalid",
+	[AST_BRIDGE_TRANSFER_NOT_PERMITTED] = "Not Permitted",
+	[AST_BRIDGE_TRANSFER_SUCCESS] = "Success",
+};
+
+static struct ast_manager_event_blob *blind_transfer_to_ami(struct stasis_message *msg)
+{
+	RAII_VAR(struct ast_str *, channel_state, NULL, ast_free_ptr);
+	RAII_VAR(struct ast_str *, bridge_state, NULL, ast_free_ptr);
+	struct ast_bridge_blob *blob = stasis_message_data(msg);
+	const char *exten;
+	const char *context;
+	enum ast_transfer_result result;
+	int is_external;
+
+	if (!blob) {
+		return NULL;
+	}
+
+	channel_state = ast_manager_build_channel_state_string_prefix(blob->channel, "Transferer");
+	bridge_state = ast_manager_build_bridge_state_string(blob->bridge, "");
+
+	if (!channel_state || !bridge_state) {
+		return NULL;
+	}
+
+	exten = ast_json_string_get(ast_json_object_get(blob->blob, "exten"));
+	context = ast_json_string_get(ast_json_object_get(blob->blob, "context"));
+	result = ast_json_integer_get(ast_json_object_get(blob->blob, "result"));
+	is_external = ast_json_integer_get(ast_json_object_get(blob->blob, "is_external"));
+
+	return ast_manager_event_blob_create(EVENT_FLAG_CALL, "BlindTransfer",
+			"Result: %s\r\n"
+			"%s"
+			"%s"
+			"IsExternal: %s\r\n"
+			"Context: %s\r\n"
+			"Extension: %s\r\n",
+			result_strs[result],
+			ast_str_buffer(channel_state),
+			ast_str_buffer(bridge_state),
+			is_external ? "Yes" : "No",
+			context,
+			exten);
+}
+
+void ast_bridge_publish_blind_transfer(int is_external, enum ast_transfer_result result,
+		struct ast_bridge_channel_pair *transferer, const char *context, const char *exten)
+{
+	RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref);
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+	json_object = ast_json_pack("{s: s, s: s, s: i, s: i}",
+			"context", context, "exten", exten, "result", result, "is_external", is_external);
+
+	if (!json_object) {
+		ast_log(LOG_NOTICE, "Failed to create json bridge blob\n");
+		return;
+	}
+
+	msg = ast_bridge_blob_create(ast_blind_transfer_type(),
+			transferer->bridge, transferer->channel, json_object);
+
+	if (!msg) {
+		ast_log(LOG_NOTICE, "Failed to create blob msg\n");
+		return;
+	}
+
+	stasis_publish(ast_bridge_topic_all(), msg);
+}
+
+static struct ast_manager_event_blob *attended_transfer_to_ami(struct stasis_message *msg)
+{
+	RAII_VAR(struct ast_str *, variable_data, ast_str_create(64), ast_free_ptr);
+	RAII_VAR(struct ast_str *, transferer1_state, NULL, ast_free_ptr);
+	RAII_VAR(struct ast_str *, bridge1_state, NULL, ast_free_ptr);
+	RAII_VAR(struct ast_str *, transferer2_state, NULL, ast_free_ptr);
+	RAII_VAR(struct ast_str *, bridge2_state, NULL, ast_free_ptr);
+	RAII_VAR(struct ast_str *, local1_state, NULL, ast_free_ptr);
+	RAII_VAR(struct ast_str *, local2_state, NULL, ast_free_ptr);
+	struct ast_attended_transfer_message *transfer_msg = stasis_message_data(msg);
+
+	if (!variable_data) {
+		return NULL;
+	}
+
+	transferer1_state = ast_manager_build_channel_state_string_prefix(transfer_msg->to_transferee.channel_snapshot, "OrigTransferer");
+	transferer2_state = ast_manager_build_channel_state_string_prefix(transfer_msg->to_transfer_target.channel_snapshot, "SecondTransferer");
+
+	if (!transferer1_state || !transferer2_state) {
+		return NULL;
+	}
+
+	if (transfer_msg->to_transferee.bridge_snapshot) {
+		bridge1_state = ast_manager_build_bridge_state_string(transfer_msg->to_transferee.bridge_snapshot, "Orig");
+		if (!bridge1_state) {
+			return NULL;
+		}
+	}
+
+	if (transfer_msg->to_transfer_target.bridge_snapshot) {
+		bridge2_state = ast_manager_build_bridge_state_string(transfer_msg->to_transfer_target.bridge_snapshot, "Second");
+		if (!bridge2_state) {
+			return NULL;
+		}
+	}
+
+	switch (transfer_msg->dest_type) {
+	case AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE:
+		ast_str_append(&variable_data, 0, "DestType: Bridge\r\n");
+		ast_str_append(&variable_data, 0, "DestBridgeUniqueid: %s\r\n", transfer_msg->dest.bridge);
+		break;
+	case AST_ATTENDED_TRANSFER_DEST_APP:
+		ast_str_append(&variable_data, 0, "DestType: App\r\n");
+		ast_str_append(&variable_data, 0, "DestApp: %s\r\n", transfer_msg->dest.app);
+		break;
+	case AST_ATTENDED_TRANSFER_DEST_LINK:
+		local1_state = ast_manager_build_channel_state_string_prefix(transfer_msg->dest.links[0], "LocalOne");
+		local2_state = ast_manager_build_channel_state_string_prefix(transfer_msg->dest.links[1], "LocalTwo");
+		if (!local1_state || !local2_state) {
+			return NULL;
+		}
+		ast_str_append(&variable_data, 0, "DestType: Link\r\n");
+		ast_str_append(&variable_data, 0, "%s", ast_str_buffer(local1_state));
+		ast_str_append(&variable_data, 0, "%s", ast_str_buffer(local2_state));
+		break;
+	case AST_ATTENDED_TRANSFER_DEST_FAIL:
+		ast_str_append(&variable_data, 0, "DestType: Fail\r\n");
+		break;
+	}
+
+	return ast_manager_event_blob_create(EVENT_FLAG_CALL, "AttendedTransfer",
+			"Result: %s\r\n"
+			"%s"
+			"%s"
+			"%s"
+			"%s"
+			"IsExternal: %s\r\n"
+			"%s\r\n",
+			result_strs[transfer_msg->result],
+			ast_str_buffer(transferer1_state),
+			bridge1_state ? ast_str_buffer(bridge1_state) : "",
+			ast_str_buffer(transferer2_state),
+			bridge2_state ? ast_str_buffer(bridge2_state) : "",
+			transfer_msg->is_external ? "Yes" : "No",
+			ast_str_buffer(variable_data));
+}
+
+static void attended_transfer_dtor(void *obj)
+{
+	struct ast_attended_transfer_message *msg = obj;
+	int i;
+
+	bridge_channel_snapshot_pair_cleanup(&msg->to_transferee);
+	bridge_channel_snapshot_pair_cleanup(&msg->to_transfer_target);
+
+	if (msg->dest_type != AST_ATTENDED_TRANSFER_DEST_LINK) {
+		return;
+	}
+
+	for (i = 0; i < ARRAY_LEN(msg->dest.links); ++i) {
+		ao2_cleanup(msg->dest.links[i]);
+	}
+}
+
+static struct ast_attended_transfer_message *attended_transfer_message_create(int is_external, enum ast_transfer_result result,
+		struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target)
+{
+	RAII_VAR(struct ast_attended_transfer_message *, msg, NULL, ao2_cleanup);
+
+	msg = ao2_alloc(sizeof(*msg), attended_transfer_dtor);
+	if (!msg) {
+		return NULL;
+	}
+
+	if (bridge_channel_snapshot_pair_init(transferee, &msg->to_transferee) ||
+			bridge_channel_snapshot_pair_init(target, &msg->to_transfer_target)) {
+		return NULL;
+	}
+
+	msg->is_external = is_external;
+	msg->result = result;
+
+	ao2_ref(msg, +1);
+	return msg;
+}
+
+void ast_bridge_publish_attended_transfer_fail(int is_external, enum ast_transfer_result result,
+		struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target)
+{
+	RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+	transfer_msg = attended_transfer_message_create(is_external, result, transferee, target);
+	if (!transfer_msg) {
+		return;
+	}
+
+	transfer_msg->dest_type = AST_ATTENDED_TRANSFER_DEST_FAIL;
+
+	msg = stasis_message_create(ast_attended_transfer_type(), transfer_msg);
+	if (!msg) {
+		return;
+	}
+
+	stasis_publish(ast_bridge_topic_all(), msg);
+}
+
+void ast_bridge_publish_attended_transfer_bridge_merge(int is_external, enum ast_transfer_result result,
+		struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target,
+		struct ast_bridge *final_bridge)
+{
+	RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+	transfer_msg = attended_transfer_message_create(is_external, result, transferee, target);
+	if (!transfer_msg) {
+		return;
+	}
+
+	transfer_msg->dest_type = AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE;
+	ast_copy_string(transfer_msg->dest.bridge, final_bridge->uniqueid,
+			sizeof(transfer_msg->dest.bridge));
+
+	msg = stasis_message_create(ast_attended_transfer_type(), transfer_msg);
+	if (!msg) {
+		return;
+	}
+
+	stasis_publish(ast_bridge_topic_all(), msg);
+}
+
+void ast_bridge_publish_attended_transfer_app(int is_external, enum ast_transfer_result result,
+		struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target,
+		const char *dest_app)
+{
+	RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+	transfer_msg = attended_transfer_message_create(is_external, result, transferee, target);
+	if (!transfer_msg) {
+		return;
+	}
+
+	transfer_msg->dest_type = AST_ATTENDED_TRANSFER_DEST_APP;
+	ast_copy_string(transfer_msg->dest.app, dest_app, sizeof(transfer_msg->dest.app));
+
+	msg = stasis_message_create(ast_attended_transfer_type(), transfer_msg);
+	if (!msg) {
+		return;
+	}
+
+	stasis_publish(ast_bridge_topic_all(), msg);
+}
+
+void ast_bridge_publish_attended_transfer_link(int is_external, enum ast_transfer_result result,
+		struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target,
+		struct ast_channel *locals[2])
+{
+	RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+	int i;
+
+	transfer_msg = attended_transfer_message_create(is_external, result, transferee, target);
+	if (!transfer_msg) {
+		return;
+	}
+
+	transfer_msg->dest_type = AST_ATTENDED_TRANSFER_DEST_LINK;
+	for (i = 0; i < 2; ++i) {
+		transfer_msg->dest.links[i] = ast_channel_snapshot_create(locals[i]);
+		if (!transfer_msg->dest.links[i]) {
+			return;
+		}
+	}
+
+	msg = stasis_message_create(ast_attended_transfer_type(), transfer_msg);
+	if (!msg) {
+		return;
+	}
+
+	stasis_publish(ast_bridge_topic_all(), msg);
+}
+
 struct ast_bridge_snapshot *ast_bridge_snapshot_get_latest(const char *uniqueid)
 {
 	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
@@ -387,6 +1028,8 @@ static void stasis_bridging_cleanup(void)
 	STASIS_MESSAGE_TYPE_CLEANUP(ast_bridge_merge_message_type);
 	STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_entered_bridge_type);
 	STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_left_bridge_type);
+	STASIS_MESSAGE_TYPE_CLEANUP(ast_blind_transfer_type);
+	STASIS_MESSAGE_TYPE_CLEANUP(ast_attended_transfer_type);
 }
 
 /*! \brief snapshot ID getter for caching topic */
@@ -408,9 +1051,12 @@ int ast_stasis_bridging_init(void)
 	STASIS_MESSAGE_TYPE_INIT(ast_bridge_merge_message_type);
 	STASIS_MESSAGE_TYPE_INIT(ast_channel_entered_bridge_type);
 	STASIS_MESSAGE_TYPE_INIT(ast_channel_left_bridge_type);
+	STASIS_MESSAGE_TYPE_INIT(ast_blind_transfer_type);
+	STASIS_MESSAGE_TYPE_INIT(ast_attended_transfer_type);
 	bridge_topic_all = stasis_topic_create("ast_bridge_topic_all");
 	bridge_topic_all_cached = stasis_caching_topic_create(bridge_topic_all, bridge_snapshot_get_id);
 	bridge_topic_pool = stasis_topic_pool_create(bridge_topic_all);
+
 	return !bridge_topic_all
 		|| !bridge_topic_all_cached
 		|| !bridge_topic_pool ? -1 : 0;
diff --git a/res/res_sip_refer.c b/res/res_sip_refer.c
index dfc35a3f4b3520ff6734b1661eae7c14acdc201c..9cc46442d5647349377b1c71380e198c99051ffd 100644
--- a/res/res_sip_refer.c
+++ b/res/res_sip_refer.c
@@ -552,7 +552,7 @@ static int refer_incoming_attended_request(struct ast_sip_session *session, pjsi
 		refer.replaces = replaces;
 		refer.refer_to = target_uri;
 
-		switch (ast_bridge_transfer_blind(session->channel, "external_replaces", context, refer_blind_callback, &refer)) {
+		switch (ast_bridge_transfer_blind(1, session->channel, "external_replaces", context, refer_blind_callback, &refer)) {
 		case AST_BRIDGE_TRANSFER_INVALID:
 			return 400;
 		case AST_BRIDGE_TRANSFER_NOT_PERMITTED:
@@ -594,7 +594,7 @@ static int refer_incoming_blind_request(struct ast_sip_session *session, pjsip_r
 	refer.progress = progress;
 	refer.rdata = rdata;
 
-	switch (ast_bridge_transfer_blind(session->channel, exten, context, refer_blind_callback, &refer)) {
+	switch (ast_bridge_transfer_blind(1, session->channel, exten, context, refer_blind_callback, &refer)) {
 	case AST_BRIDGE_TRANSFER_INVALID:
 		return 400;
 	case AST_BRIDGE_TRANSFER_NOT_PERMITTED: