diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c
index 424f5add70894cbd0bbcda37c0501b34384b32cb..59f794454a578f0da48dfe25749fb63e4c558c67 100644
--- a/channels/chan_iax2.c
+++ b/channels/chan_iax2.c
@@ -5879,12 +5879,14 @@ static int iax2_getpeertrunk(struct ast_sockaddr addr)
 /*! \brief  Create new call, interface with the PBX core */
 static struct ast_channel *ast_iax2_new(int callno, int state, iax2_format capability, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, unsigned int cachable)
 {
-	struct ast_channel *tmp;
+	struct ast_channel *tmp = NULL;
 	struct chan_iax2_pvt *i;
+	struct iax2_peer *peer;
 	struct ast_variable *v = NULL;
 	struct ast_format_cap *native;
 	struct ast_format *tmpfmt;
 	struct ast_callid *callid;
+	char *peer_name = NULL;
 
 	if (!(i = iaxs[callno])) {
 		ast_log(LOG_WARNING, "No IAX2 pvt found for callno '%d' !\n", callno);
@@ -5896,9 +5898,27 @@ static struct ast_channel *ast_iax2_new(int callno, int state, iax2_format capab
 		return NULL;
 	}
 
-	/* Don't hold call lock */
+	if (!ast_strlen_zero(i->peer)) {
+		peer_name = ast_strdupa(i->peer);
+	} else if (!ast_strlen_zero(i->host)) {
+		peer_name = ast_strdupa(i->host);
+	}
+
+	/* Don't hold call lock while making a channel or looking up a peer */
 	ast_mutex_unlock(&iaxsl[callno]);
-	tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, assignedids, requestor, i->amaflags, "IAX2/%s-%d", i->host, i->callno);
+
+	if (!ast_strlen_zero(peer_name)) {
+		peer = find_peer(peer_name, 1);
+		if (peer && peer->endpoint) {
+			tmp = ast_channel_alloc_with_endpoint(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, assignedids, requestor, i->amaflags, peer->endpoint, "IAX2/%s-%d", i->host, i->callno);
+		}
+		ao2_cleanup(peer);
+	}
+
+	if (!tmp) {
+		tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, assignedids, requestor, i->amaflags, "IAX2/%s-%d", i->host, i->callno);
+	}
+
 	ast_mutex_lock(&iaxsl[callno]);
 	if (i != iaxs[callno]) {
 		if (tmp) {
diff --git a/channels/chan_motif.c b/channels/chan_motif.c
index 1bdc8aa6b708518f54da97bff837da61ccc64020..e29485206f2e8d26882fc1b9ff27031d4fd566ee 100644
--- a/channels/chan_motif.c
+++ b/channels/chan_motif.c
@@ -75,6 +75,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/causes.h"
 #include "asterisk/abstract_jb.h"
 #include "asterisk/xmpp.h"
+#include "asterisk/endpoints.h"
 #include "asterisk/stasis_channels.h"
 #include "asterisk/format_cache.h"
 
@@ -783,7 +784,7 @@ static struct ast_channel *jingle_new(struct jingle_endpoint *endpoint, struct j
 		return NULL;
 	}
 
-	if (!(chan = ast_channel_alloc(1, state, S_OR(title, ""), S_OR(cid_name, ""), "", "", "", assignedids, requestor, 0, "Motif/%s-%04lx", str, (unsigned long)(ast_random() & 0xffff)))) {
+	if (!(chan = ast_channel_alloc_with_endpoint(1, state, S_OR(title, ""), S_OR(cid_name, ""), "", "", "", assignedids, requestor, 0, endpoint->connection->endpoint, "Motif/%s-%04lx", str, (unsigned long)(ast_random() & 0xffff)))) {
 		ao2_ref(caps, -1);
 		return NULL;
 	}
diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index 5812360c52822f17ae08e08e20d4439a6de6a86c..f638a1e35d51e177f2c738fe8519d5dda3795207 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -371,8 +371,12 @@ static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int s
 		return NULL;
 	}
 
-	if (!(chan = ast_channel_alloc(1, state, S_OR(session->id.number.str, ""), S_OR(session->id.name.str, ""), session->endpoint->accountcode, "", "", assignedids, requestor, 0, "PJSIP/%s-%08x", ast_sorcery_object_get_id(session->endpoint),
-		(unsigned)ast_atomic_fetchadd_int((int *)&chan_idx, +1)))) {
+	chan = ast_channel_alloc_with_endpoint(1, state, S_OR(session->id.number.str, ""),
+	                         S_OR(session->id.name.str, ""), session->endpoint->accountcode, "",
+	                         "", assignedids, requestor, 0, session->endpoint->persistent,
+	                         "PJSIP/%s-%08x", ast_sorcery_object_get_id(session->endpoint),
+	                         (unsigned)ast_atomic_fetchadd_int((int *)&chan_idx, +1));
+	if (!chan) {
 		ao2_ref(caps, -1);
 		return NULL;
 	}
@@ -455,8 +459,6 @@ static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int s
 		ast_rtp_instance_set_channel_id(pvt->media[SIP_MEDIA_VIDEO]->rtp, ast_channel_uniqueid(chan));
 	}
 
-	ast_endpoint_add_channel(session->endpoint->persistent, chan);
-
 	return chan;
 }
 
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 44629dc3d85c871c4cc3f407253deac2f5e738c6..2a32971dced91fd43aabd28be831617925c9f9f4 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -8071,9 +8071,14 @@ static struct ast_channel *sip_new(struct sip_pvt *i, int state, const char *tit
 			my_name = ast_strdupa(i->fromdomain);
 		}
 
-		sip_pvt_unlock(i);
 		/* Don't hold a sip pvt lock while we allocate a channel */
-		tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, assignedids, requestor, i->amaflags, "SIP/%s-%08x", my_name, (unsigned)ast_atomic_fetchadd_int((int *)&chan_idx, +1));
+		sip_pvt_unlock(i);
+
+		if (i->relatedpeer && i->relatedpeer->endpoint) {
+			tmp = ast_channel_alloc_with_endpoint(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, assignedids, requestor, i->amaflags, i->relatedpeer->endpoint, "SIP/%s-%08x", my_name, (unsigned)ast_atomic_fetchadd_int((int *)&chan_idx, +1));
+		} else {
+			tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, assignedids, requestor, i->amaflags, "SIP/%s-%08x", my_name, (unsigned)ast_atomic_fetchadd_int((int *)&chan_idx, +1));
+		}
 	}
 	if (!tmp) {
 		ast_log(LOG_WARNING, "Unable to allocate AST channel structure for SIP channel\n");
@@ -8082,16 +8087,6 @@ static struct ast_channel *sip_new(struct sip_pvt *i, int state, const char *tit
 		return NULL;
 	}
 
-	if (i->relatedpeer && i->relatedpeer->endpoint) {
-		if (ast_endpoint_add_channel(i->relatedpeer->endpoint, tmp)) {
-			ast_channel_unlock(tmp);
-			ast_channel_unref(tmp);
-			ao2_ref(caps, -1);
-			sip_pvt_lock(i);
-			return NULL;
-		}
-	}
-
 	ast_channel_stage_snapshot(tmp);
 
 	/* If we sent in a callid, bind it to the channel. */
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index d118bc81f41db8bf60dfd2857f29bd49d3ef530c..d5d32c92185bd229e82be35758f98f4b389c35e4 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -1158,11 +1158,12 @@ struct ast_datastore *ast_channel_datastore_find(struct ast_channel *chan, const
  *       and "default" context.
  * \note Since 12.0.0 this function returns with the newly created channel locked.
  */
-struct ast_channel * attribute_malloc __attribute__((format(printf, 14, 15)))
+struct ast_channel * attribute_malloc __attribute__((format(printf, 15, 16)))
 	__ast_channel_alloc(int needqueue, int state, const char *cid_num,
 		const char *cid_name, const char *acctcode,
 		const char *exten, const char *context, const struct ast_assigned_ids *assignedids,
 		const struct ast_channel *requestor, enum ama_flags amaflag,
+		struct ast_endpoint *endpoint,
 		const char *file, int line, const char *function,
 		const char *name_fmt, ...);
 
@@ -1178,9 +1179,14 @@ struct ast_channel * attribute_malloc __attribute__((format(printf, 14, 15)))
  * \note Since 12.0.0 this function returns with the newly created channel locked.
  */
 #define ast_channel_alloc(needqueue, state, cid_num, cid_name, acctcode, exten, context, assignedids, requestor, amaflag, ...) \
-	__ast_channel_alloc(needqueue, state, cid_num, cid_name, acctcode, exten, context, assignedids, requestor, amaflag, \
+	__ast_channel_alloc(needqueue, state, cid_num, cid_name, acctcode, exten, context, assignedids, requestor, amaflag, NULL, \
 		__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
 
+#define ast_channel_alloc_with_endpoint(needqueue, state, cid_num, cid_name, acctcode, exten, context, assignedids, requestor, amaflag, endpoint, ...) \
+	__ast_channel_alloc((needqueue), (state), (cid_num), (cid_name), (acctcode), (exten), (context), (assignedids), (requestor), (amaflag), (endpoint), \
+		__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
+
+
 #if defined(REF_DEBUG) || defined(__AST_DEBUG_MALLOC)
 /*!
  * \brief Create a fake channel structure
diff --git a/include/asterisk/endpoints.h b/include/asterisk/endpoints.h
index 7a7a3f6b446197ed7dbd2fa163a4224644427ab2..663dd94d9c80fcb5211201010f8c61ffcdd12b74 100644
--- a/include/asterisk/endpoints.h
+++ b/include/asterisk/endpoints.h
@@ -77,11 +77,17 @@ const char *ast_endpoint_state_to_string(enum ast_endpoint_state state);
 struct ast_endpoint;
 
 /*!
- * \brief Finds the endpoint with the given tech/resource id.
+ * \brief Finds the endpoint with the given tech[/resource] id.
  *
  * Endpoints are refcounted, so ao2_cleanup() when you're done.
  *
- * \param id Tech/resource id to look for.
+ * \note The resource portion of an ID is optional. If not provided,
+ *       an aggregate endpoint for the entire technology is returned.
+ *       These endpoints must not be modified, but can be subscribed
+ *       to in order to receive updates for all endpoints of a given
+ *       technology.
+ *
+ * \param id Tech[/resource] id to look for.
  * \return Associated endpoint.
  * \return \c NULL if not found.
  *
@@ -131,6 +137,9 @@ const char *ast_endpoint_get_tech(const struct ast_endpoint *endpoint);
  *
  * This is unique for the endpoint's technology, and immutable.
  *
+ * \note If the endpoint being queried is a technology aggregate
+ *       endpoint, this will be an empty string.
+ *
  * \param endpoint The endpoint.
  * \return Resource name of the endpoint.
  * \return \c NULL if endpoint is \c NULL.
diff --git a/include/asterisk/xmpp.h b/include/asterisk/xmpp.h
index 58b14e4d3916d6620dc51f7e24157e72799606eb..294b4fdae60a25f74766446cad41069613dbdb84 100644
--- a/include/asterisk/xmpp.h
+++ b/include/asterisk/xmpp.h
@@ -106,6 +106,8 @@ struct ast_xmpp_message {
 	AST_LIST_ENTRY(ast_xmpp_message) list; /*!< Linked list information */
 };
 
+struct ast_endpoint;
+
 /*! \brief XMPP Buddy */
 struct ast_xmpp_buddy {
 	char id[XMPP_MAX_JIDLEN];        /*!< JID of the buddy */
@@ -116,9 +118,11 @@ struct ast_xmpp_buddy {
 /*! \brief XMPP Client Connection */
 struct ast_xmpp_client {
 	AST_DECLARE_STRING_FIELDS(
-		AST_STRING_FIELD(name); /*!< Name of the client configuration */
+		/*! Name of the client configuration */
+		AST_STRING_FIELD(name);
 		);
-	char mid[6]; /* Message ID */
+	/*! Message ID */
+	char mid[6];
 	iksid *jid;
 	iksparser *parser;
 	iksfilter *filter;
@@ -134,9 +138,14 @@ struct ast_xmpp_client {
 	AST_LIST_HEAD(, ast_xmpp_message) messages;
 	pthread_t thread;
 	int timeout;
-	unsigned int reconnect:1; /*!< Reconnect this client */
-	struct stasis_subscription *mwi_sub; /*!< If distributing event information the MWI subscription */
-	struct stasis_subscription *device_state_sub; /*!< If distributing event information the device state subscription */
+	/*! Reconnect this client */
+	unsigned int reconnect:1;
+	/*! If distributing event information the MWI subscription */
+	struct stasis_subscription *mwi_sub;
+	/*! If distributing event information the device state subscription */
+	struct stasis_subscription *device_state_sub;
+	/*! The endpoint associated with this client */
+	struct ast_endpoint *endpoint;
 };
 
 /*!
diff --git a/main/channel.c b/main/channel.c
index b17dddeefe910011a807e9a8090d9f3bd99a05e4..e9e37c0fda6c59bf91656327224a9c0a2722067c 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -783,10 +783,11 @@ static void ast_channel_destructor(void *obj);
 static void ast_dummy_channel_destructor(void *obj);
 
 /*! \brief Create a new channel structure */
-static struct ast_channel * attribute_malloc __attribute__((format(printf, 13, 0)))
+static struct ast_channel * attribute_malloc __attribute__((format(printf, 15, 0)))
 __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char *cid_name,
 		       const char *acctcode, const char *exten, const char *context, const struct ast_assigned_ids *assignedids,
-		       const struct ast_channel *requestor, enum ama_flags amaflag, const char *file, int line,
+		       const struct ast_channel *requestor, enum ama_flags amaflag, struct ast_endpoint *endpoint,
+		       const char *file, int line,
 		       const char *function, const char *name_fmt, va_list ap)
 {
 	struct ast_channel *tmp;
@@ -963,6 +964,10 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
 
 	ao2_link(channels, tmp);
 
+	if (endpoint) {
+		ast_endpoint_add_channel(endpoint, tmp);
+	}
+
 	/*
 	 * And now, since the channel structure is built, and has its name, let
 	 * the world know of its existance
@@ -975,6 +980,7 @@ struct ast_channel *__ast_channel_alloc(int needqueue, int state, const char *ci
 					const char *cid_name, const char *acctcode,
 					const char *exten, const char *context, const struct ast_assigned_ids *assignedids,
 					const struct ast_channel *requestor, enum ama_flags amaflag,
+					struct ast_endpoint *endpoint,
 					const char *file, int line, const char *function,
 					const char *name_fmt, ...)
 {
@@ -983,7 +989,7 @@ struct ast_channel *__ast_channel_alloc(int needqueue, int state, const char *ci
 
 	va_start(ap, name_fmt);
 	result = __ast_channel_alloc_ap(needqueue, state, cid_num, cid_name, acctcode, exten, context,
-					assignedids, requestor, amaflag, file, line, function, name_fmt, ap);
+					assignedids, requestor, amaflag, endpoint, file, line, function, name_fmt, ap);
 	va_end(ap);
 
 	return result;
diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c
index 4ad0ef3a0716941faf3c4029199638b71588bdd4..8a9e18eb0061e149815efd192dfd56361a021468 100644
--- a/main/channel_internal_api.c
+++ b/main/channel_internal_api.c
@@ -219,6 +219,7 @@ struct ast_channel {
 	struct timeval sending_dtmf_tv;		/*!< The time this channel started sending the current digit. (Invalid if sending_dtmf_digit is zero.) */
 	struct stasis_cp_single *topics;		/*!< Topic for all channel's events */
 	struct stasis_forward *endpoint_forward;	/*!< Subscription for event forwarding to endpoint's topic */
+	struct stasis_forward *endpoint_cache_forward; /*!< Subscription for cache updates to endpoint's topic */
 };
 
 /*! \brief The monotonically increasing integer counter for channel uniqueids */
@@ -1528,6 +1529,7 @@ void ast_channel_internal_cleanup(struct ast_channel *chan)
 	ast_string_field_free_memory(chan);
 
 	chan->endpoint_forward = stasis_forward_cancel(chan->endpoint_forward);
+	chan->endpoint_cache_forward = stasis_forward_cancel(chan->endpoint_cache_forward);
 
 	stasis_cp_single_unsubscribe(chan->topics);
 	chan->topics = NULL;
@@ -1570,8 +1572,14 @@ int ast_channel_forward_endpoint(struct ast_channel *chan,
 	chan->endpoint_forward =
 		stasis_forward_all(ast_channel_topic(chan),
 			ast_endpoint_topic(endpoint));
+	if (!chan->endpoint_forward) {
+		return -1;
+	}
 
-	if (chan->endpoint_forward == NULL) {
+	chan->endpoint_cache_forward = stasis_forward_all(ast_channel_topic_cached(chan),
+		ast_endpoint_topic(endpoint));
+	if (!chan->endpoint_cache_forward) {
+		chan->endpoint_forward = stasis_forward_cancel(chan->endpoint_forward);
 		return -1;
 	}
 
diff --git a/main/endpoints.c b/main/endpoints.c
index 10b32e268d4899823e894fd5432a7db90f5b5b8b..985f6e634eebfcdae7322d748ac224cc660d05a7 100644
--- a/main/endpoints.c
+++ b/main/endpoints.c
@@ -46,8 +46,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 /*! Buckets for endpoint hash. Keep it prime! */
 #define ENDPOINT_BUCKETS 127
 
+/*! Buckets for technology endpoints. */
+#define TECH_ENDPOINT_BUCKETS 11
+
 static struct ao2_container *endpoints;
 
+static struct ao2_container *tech_endpoints;
+
 struct ast_endpoint {
 	AST_DECLARE_STRING_FIELDS(
 		AST_STRING_FIELD(tech);	/*!< Technology (SIP, IAX2, etc.). */
@@ -69,6 +74,8 @@ struct ast_endpoint {
 	struct stasis_message_router *router;
 	/*! ast_str_container of channels associated with this endpoint */
 	struct ao2_container *channel_ids;
+	/*! Forwarding subscription from an endpoint to its tech endpoint */
+	struct stasis_forward *tech_forward;
 };
 
 static int endpoint_hash(const void *obj, int flags)
@@ -121,7 +128,13 @@ static int endpoint_cmp(void *obj, void *arg, int flags)
 
 struct ast_endpoint *ast_endpoint_find_by_id(const char *id)
 {
-	return ao2_find(endpoints, id, OBJ_KEY);
+	struct ast_endpoint *endpoint = ao2_find(endpoints, id, OBJ_KEY);
+
+	if (!endpoint) {
+		endpoint = ao2_find(tech_endpoints, id, OBJ_KEY);
+	}
+
+	return endpoint;
 }
 
 struct stasis_topic *ast_endpoint_topic(struct ast_endpoint *endpoint)
@@ -181,6 +194,8 @@ static void endpoint_dtor(void *obj)
 	ao2_cleanup(endpoint->router);
 	endpoint->router = NULL;
 
+	endpoint->tech_forward = stasis_forward_cancel(endpoint->tech_forward);
+
 	stasis_cp_single_unsubscribe(endpoint->topics);
 	endpoint->topics = NULL;
 
@@ -196,6 +211,7 @@ int ast_endpoint_add_channel(struct ast_endpoint *endpoint,
 {
 	ast_assert(chan != NULL);
 	ast_assert(endpoint != NULL);
+	ast_assert(!ast_strlen_zero(endpoint->resource));
 
 	ast_channel_forward_endpoint(chan, endpoint);
 
@@ -242,19 +258,21 @@ static void endpoint_default(void *data,
 	}
 }
 
-struct ast_endpoint *ast_endpoint_create(const char *tech, const char *resource)
+static struct ast_endpoint *endpoint_internal_create(const char *tech, const char *resource)
 {
 	RAII_VAR(struct ast_endpoint *, endpoint, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_endpoint *, tech_endpoint, NULL, ao2_cleanup);
 	int r = 0;
 
-	if (ast_strlen_zero(tech)) {
-		ast_log(LOG_ERROR, "Endpoint tech cannot be empty\n");
-		return NULL;
-	}
-
-	if (ast_strlen_zero(resource)) {
-		ast_log(LOG_ERROR, "Endpoint resource cannot be empty\n");
-		return NULL;
+	/* Get/create the technology endpoint */
+	if (!ast_strlen_zero(resource)) {
+		tech_endpoint = ao2_find(tech_endpoints, tech, OBJ_KEY);
+		if (!tech_endpoint) {
+			tech_endpoint = endpoint_internal_create(tech, NULL);
+			if (!tech_endpoint) {
+				return NULL;
+			}
+		}
 	}
 
 	endpoint = ao2_alloc(sizeof(*endpoint), endpoint_dtor);
@@ -268,10 +286,12 @@ struct ast_endpoint *ast_endpoint_create(const char *tech, const char *resource)
 	if (ast_string_field_init(endpoint, 80) != 0) {
 		return NULL;
 	}
-
 	ast_string_field_set(endpoint, tech, tech);
-	ast_string_field_set(endpoint, resource, resource);
-	ast_string_field_build(endpoint, id, "%s/%s", tech, resource);
+	ast_string_field_set(endpoint, resource, S_OR(resource, ""));
+	ast_string_field_build(endpoint, id, "%s%s%s",
+		tech,
+		!ast_strlen_zero(resource) ? "/" : "",
+		S_OR(resource, ""));
 
 	/* All access to channel_ids should be covered by the endpoint's
 	 * lock; no extra lock needed. */
@@ -287,24 +307,47 @@ struct ast_endpoint *ast_endpoint_create(const char *tech, const char *resource)
 		return NULL;
 	}
 
-	endpoint->router = stasis_message_router_create(ast_endpoint_topic(endpoint));
-	if (!endpoint->router) {
-		return NULL;
-	}
-	r |= stasis_message_router_add(endpoint->router,
-		stasis_cache_clear_type(), endpoint_cache_clear,
-		endpoint);
-	r |= stasis_message_router_set_default(endpoint->router,
-		endpoint_default, endpoint);
-
-	endpoint_publish_snapshot(endpoint);
+	if (!ast_strlen_zero(resource)) {
+		endpoint->router = stasis_message_router_create(ast_endpoint_topic(endpoint));
+		if (!endpoint->router) {
+			return NULL;
+		}
+		r |= stasis_message_router_add(endpoint->router,
+			stasis_cache_clear_type(), endpoint_cache_clear,
+			endpoint);
+		r |= stasis_message_router_set_default(endpoint->router,
+			endpoint_default, endpoint);
+		if (r) {
+			return NULL;
+		}
 
-	ao2_link(endpoints, endpoint);
+		endpoint->tech_forward = stasis_forward_all(stasis_cp_single_topic(endpoint->topics),
+			stasis_cp_single_topic(tech_endpoint->topics));
+		endpoint_publish_snapshot(endpoint);
+		ao2_link(endpoints, endpoint);
+	} else {
+		ao2_link(tech_endpoints, endpoint);
+	}
 
 	ao2_ref(endpoint, +1);
 	return endpoint;
 }
 
+struct ast_endpoint *ast_endpoint_create(const char *tech, const char *resource)
+{
+	if (ast_strlen_zero(tech)) {
+		ast_log(LOG_ERROR, "Endpoint tech cannot be empty\n");
+		return NULL;
+	}
+
+	if (ast_strlen_zero(resource)) {
+		ast_log(LOG_ERROR, "Endpoint resource cannot be empty\n");
+		return NULL;
+	}
+
+	return endpoint_internal_create(tech, resource);
+}
+
 static struct stasis_message *create_endpoint_snapshot_message(struct ast_endpoint *endpoint)
 {
 	RAII_VAR(struct ast_endpoint_snapshot *, snapshot, NULL, ao2_cleanup);
@@ -368,6 +411,8 @@ void ast_endpoint_set_state(struct ast_endpoint *endpoint,
 	enum ast_endpoint_state state)
 {
 	ast_assert(endpoint != NULL);
+	ast_assert(!ast_strlen_zero(endpoint->resource));
+
 	ao2_lock(endpoint);
 	endpoint->state = state;
 	ao2_unlock(endpoint);
@@ -378,6 +423,8 @@ void ast_endpoint_set_max_channels(struct ast_endpoint *endpoint,
 	int max_channels)
 {
 	ast_assert(endpoint != NULL);
+	ast_assert(!ast_strlen_zero(endpoint->resource));
+
 	ao2_lock(endpoint);
 	endpoint->max_channels = max_channels;
 	ao2_unlock(endpoint);
@@ -407,6 +454,9 @@ struct ast_endpoint_snapshot *ast_endpoint_snapshot_create(
 	void *obj;
 	SCOPED_AO2LOCK(lock, endpoint);
 
+	ast_assert(endpoint != NULL);
+	ast_assert(!ast_strlen_zero(endpoint->resource));
+
 	channel_count = ao2_container_count(endpoint->channel_ids);
 
 	snapshot = ao2_alloc(
@@ -440,6 +490,9 @@ static void endpoint_cleanup(void)
 {
 	ao2_cleanup(endpoints);
 	endpoints = NULL;
+
+	ao2_cleanup(tech_endpoints);
+	tech_endpoints = NULL;
 }
 
 int ast_endpoint_init(void)
@@ -448,10 +501,15 @@ int ast_endpoint_init(void)
 
 	endpoints = ao2_container_alloc(ENDPOINT_BUCKETS, endpoint_hash,
 		endpoint_cmp);
-
 	if (!endpoints) {
 		return -1;
 	}
 
+	tech_endpoints = ao2_container_alloc(TECH_ENDPOINT_BUCKETS, endpoint_hash,
+		endpoint_cmp);
+	if (!tech_endpoints) {
+		return -1;
+	}
+
 	return 0;
 }
diff --git a/res/ari/resource_applications.h b/res/ari/resource_applications.h
index 888f513de990a2b80cbf57ba0805135e9bbb83ed..be62e9d5fd275511e5fbfab8756ffdf11e5aabda 100644
--- a/res/ari/resource_applications.h
+++ b/res/ari/resource_applications.h
@@ -67,7 +67,7 @@ void ast_ari_applications_get(struct ast_variable *headers, struct ast_ari_appli
 struct ast_ari_applications_subscribe_args {
 	/*! Application's name */
 	const char *application_name;
-	/*! Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}, deviceState:{deviceName} */
+	/*! Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}[/{resource}], deviceState:{deviceName} */
 	const char **event_source;
 	/*! Length of event_source array. */
 	size_t event_source_count;
@@ -99,7 +99,7 @@ void ast_ari_applications_subscribe(struct ast_variable *headers, struct ast_ari
 struct ast_ari_applications_unsubscribe_args {
 	/*! Application's name */
 	const char *application_name;
-	/*! Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}, deviceState:{deviceName} */
+	/*! Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}[/{resource}], deviceState:{deviceName} */
 	const char **event_source;
 	/*! Length of event_source array. */
 	size_t event_source_count;
diff --git a/res/ari/resource_endpoints.c b/res/ari/resource_endpoints.c
index 16b7ebd8d23e9c8bce0b80cc7e472b011356649f..ff2b150ddc3a2a97eccacb86e87ff57a3c44cab5 100644
--- a/res/ari/resource_endpoints.c
+++ b/res/ari/resource_endpoints.c
@@ -34,6 +34,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/stasis_app.h"
 #include "asterisk/stasis_endpoints.h"
 #include "asterisk/channel.h"
+#include "asterisk/message.h"
 
 void ast_ari_endpoints_list(struct ast_variable *headers,
 	struct ast_ari_endpoints_list_args *args,
@@ -82,6 +83,7 @@ void ast_ari_endpoints_list(struct ast_variable *headers,
 
 	ast_ari_response_ok(response, ast_json_ref(json));
 }
+
 void ast_ari_endpoints_list_by_tech(struct ast_variable *headers,
 	struct ast_ari_endpoints_list_by_tech_args *args,
 	struct ast_ari_response *response)
@@ -89,14 +91,17 @@ void ast_ari_endpoints_list_by_tech(struct ast_variable *headers,
 	RAII_VAR(struct stasis_cache *, cache, NULL, ao2_cleanup);
 	RAII_VAR(struct ao2_container *, snapshots, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+	struct ast_endpoint *tech_endpoint;
 	struct ao2_iterator i;
 	void *obj;
 
-	if (!ast_get_channel_tech(args->tech)) {
+	tech_endpoint = ast_endpoint_find_by_id(args->tech);
+	if (!tech_endpoint) {
 		ast_ari_response_error(response, 404, "Not Found",
 				       "No Endpoints found - invalid tech %s", args->tech);
 		return;
 	}
+	ao2_ref(tech_endpoint, -1);
 
 	cache = ast_endpoint_cache();
 	if (!cache) {
@@ -146,6 +151,7 @@ void ast_ari_endpoints_list_by_tech(struct ast_variable *headers,
 	ao2_iterator_destroy(&i);
 	ast_ari_response_ok(response, ast_json_ref(json));
 }
+
 void ast_ari_endpoints_get(struct ast_variable *headers,
 	struct ast_ari_endpoints_get_args *args,
 	struct ast_ari_response *response)
diff --git a/res/res_xmpp.c b/res/res_xmpp.c
index 0cfc37b4ca4a6e5ec239133c4015c7c5d260594d..758a5f09a448f3d2a4c4edf092409acb299ad10c 100644
--- a/res/res_xmpp.c
+++ b/res/res_xmpp.c
@@ -559,6 +559,10 @@ static void xmpp_client_destructor(void *obj)
 
 	ast_xmpp_client_disconnect(client);
 
+	ast_endpoint_shutdown(client->endpoint);
+	ao2_cleanup(client->endpoint);
+	client->endpoint = NULL;
+
 	if (client->filter) {
 		iks_filter_delete(client->filter);
 	}
@@ -593,6 +597,20 @@ static int xmpp_buddy_cmp(void *obj, void *arg, int flags)
 	return !strcmp(buddy1->id, flags & OBJ_KEY ? id : buddy2->id) ? CMP_MATCH | CMP_STOP : 0;
 }
 
+/*! \brief Internal function which changes the XMPP client state */
+static void xmpp_client_change_state(struct ast_xmpp_client *client, int state)
+{
+	if (state == client->state) {
+		return;
+	}
+	client->state = state;
+	if (client->state == XMPP_STATE_DISCONNECTED) {
+		ast_endpoint_set_state(client->endpoint, AST_ENDPOINT_OFFLINE);
+	} else if (client->state == XMPP_STATE_CONNECTED) {
+		ast_endpoint_set_state(client->endpoint, AST_ENDPOINT_ONLINE);
+	}
+}
+
 /*! \brief Allocator function for ast_xmpp_client */
 static struct ast_xmpp_client *xmpp_client_alloc(const char *name)
 {
@@ -605,6 +623,12 @@ static struct ast_xmpp_client *xmpp_client_alloc(const char *name)
 	AST_LIST_HEAD_INIT(&client->messages);
 	client->thread = AST_PTHREADT_NULL;
 
+	client->endpoint = ast_endpoint_create("XMPP", name);
+	if (!client->endpoint) {
+		ao2_ref(client, -1);
+		return NULL;
+	}
+
 	if (!(client->buddies = ao2_container_alloc(BUDDY_BUCKETS, xmpp_buddy_hash, xmpp_buddy_cmp))) {
 		ast_log(LOG_ERROR, "Could not initialize buddy container for '%s'\n", name);
 		ao2_ref(client, -1);
@@ -626,7 +650,7 @@ static struct ast_xmpp_client *xmpp_client_alloc(const char *name)
 	ast_string_field_set(client, name, name);
 
 	client->timeout = 50;
-	client->state = XMPP_STATE_DISCONNECTED;
+	xmpp_client_change_state(client, XMPP_STATE_DISCONNECTED);
 	ast_copy_string(client->mid, "aaaaa", sizeof(client->mid));
 
 	return client;
@@ -2213,12 +2237,6 @@ static const struct ast_msg_tech msg_tech = {
 	.msg_send = xmpp_send_cb,
 };
 
-/*! \brief Internal function which changes the XMPP client state */
-static void xmpp_client_change_state(struct ast_xmpp_client *client, int state)
-{
-	client->state = state;
-}
-
 /*! \brief Internal function which creates a buddy on a client */
 static struct ast_xmpp_buddy *xmpp_client_create_buddy(struct ao2_container *container, const char *id)
 {
@@ -3530,7 +3548,7 @@ static int xmpp_action_hook(void *data, int type, iks *node)
 int ast_xmpp_client_disconnect(struct ast_xmpp_client *client)
 {
 	if ((client->thread != AST_PTHREADT_NULL) && !pthread_equal(pthread_self(), client->thread)) {
-		client->state = XMPP_STATE_DISCONNECTING;
+		xmpp_client_change_state(client, XMPP_STATE_DISCONNECTING);
 		pthread_join(client->thread, NULL);
 		client->thread = AST_PTHREADT_NULL;
 	}
@@ -3559,7 +3577,7 @@ int ast_xmpp_client_disconnect(struct ast_xmpp_client *client)
 		iks_disconnect(client->parser);
 	}
 
-	client->state = XMPP_STATE_DISCONNECTED;
+	xmpp_client_change_state(client, XMPP_STATE_DISCONNECTED);
 
 	return 0;
 }
@@ -3774,7 +3792,7 @@ static void *xmpp_client_thread(void *data)
 			ast_log(LOG_WARNING, "JABBER: Not Supported\n");
 		} else if (res == IKS_NET_DROPPED) {
 			ast_log(LOG_WARNING, "JABBER: Dropped?\n");
-		} else {
+		} else if (res == IKS_NET_UNKNOWN) {
 			ast_debug(5, "JABBER: Unknown\n");
 		}
 
diff --git a/rest-api/api-docs/applications.json b/rest-api/api-docs/applications.json
index 132dd641a6688784fb4f32b3e75bcfa8d8a91b38..cf0731c7cbab84cabf3ea12f9cceb14e38e00b7f 100644
--- a/rest-api/api-docs/applications.json
+++ b/rest-api/api-docs/applications.json
@@ -68,7 +68,7 @@
 						},
 						{
 							"name": "eventSource",
-							"description": "URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}, deviceState:{deviceName}",
+							"description": "URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}[/{resource}], deviceState:{deviceName}",
 							"paramType": "query",
 							"required": true,
 							"allowMultiple": true,
@@ -107,7 +107,7 @@
 						},
 						{
 							"name": "eventSource",
-							"description": "URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}, deviceState:{deviceName}",
+							"description": "URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}[/{resource}], deviceState:{deviceName}",
 							"paramType": "query",
 							"required": true,
 							"allowMultiple": true,