diff --git a/include/asterisk/res_pjsip_body_generator_types.h b/include/asterisk/res_pjsip_body_generator_types.h
new file mode 100644
index 0000000000000000000000000000000000000000..f27e8968a1f71c092ac4e2c5a830dbe720bdf520
--- /dev/null
+++ b/include/asterisk/res_pjsip_body_generator_types.h
@@ -0,0 +1,61 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2014, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef _RES_PJSIP_BODY_GENERATOR_TYPES_H
+#define _RES_PJSIP_BODY_GENERATOR_TYPES_H
+
+#include "asterisk/pbx.h"
+
+/*!
+ * \brief structure used for presence XML bodies
+ *
+ * This is used for the following body types:
+ * \li application/pidf+xml
+ * \li application/xpidf+xml
+ * \li application/cpim-pidf+xml
+ */
+struct ast_sip_exten_state_data {
+	/*! The extension of the current state change */
+	const char *exten;
+	/*! The extension state of the change */
+	enum ast_extension_states exten_state;
+	/*! The presence state of the change */
+	enum ast_presence_state presence_state;
+	/*! Current device state information */
+	struct ao2_container *device_state_info;
+	/*! Local dialog URI */
+	char local[PJSIP_MAX_URL_SIZE];
+	/*! Remote dialog URI */
+	char remote[PJSIP_MAX_URL_SIZE];
+	/*! Allocation pool */
+	pj_pool_t *pool;
+};
+
+/*!
+ * \brief Message counter used for message-summary XML bodies
+ *
+ * This is used for application/simple-message-summary bodies.
+ */
+struct ast_sip_message_accumulator {
+	/*! Number of old messages */
+	int old_msgs;
+	/*! Number of new messages */
+	int new_msgs;
+};
+
+#endif /* _RES_PJSIP_BODY_GENERATOR_TYPES_H */
diff --git a/include/asterisk/res_pjsip_exten_state.h b/include/asterisk/res_pjsip_exten_state.h
deleted file mode 100644
index 6a324a4f444f922cc83f95ef76d18d7e7e431960..0000000000000000000000000000000000000000
--- a/include/asterisk/res_pjsip_exten_state.h
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2013, Digium, Inc.
- *
- * Kevin Harwell <kharwell@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-#ifndef _RES_PJSIP_EXTEN_STATE_H
-#define _RES_PJSIP_EXTEN_STATE_H
-
-#include "asterisk/stringfields.h"
-#include "asterisk/linkedlists.h"
-
-#include "asterisk/pbx.h"
-#include "asterisk/presencestate.h"
-
-
-/*!
- * \brief Contains information pertaining to extension/device state changes.
- */
-struct ast_sip_exten_state_data {
-	/*! The extension of the current state change */
-	const char *exten;
-	/*! The extension state of the change */
-	enum ast_extension_states exten_state;
-	/*! The presence state of the change */
-	enum ast_presence_state presence_state;
-	/*! Current device state information */
-	struct ao2_container *device_state_info;
-};
-
-/*!
- * \brief Extension state provider.
- */
-struct ast_sip_exten_state_provider {
-	/*! The name of the event this provider registers for */
-	const char *event_name;
-	/*! Type of the body, ex: "application" */
-	const char *type;
-	/*! Subtype of the body, ex: "pidf+xml" */
-	const char *subtype;
-	/*! Type/Subtype together - ex: application/pidf+xml */
-	const char *body_type;
-	/*! Subscription handler to be used and associated with provider */
-	struct ast_sip_subscription_handler *handler;
-
-	/*!
-	 * \brief Create the body text of a NOTIFY request.
-	 *
-	 * Implementors use this to create body information within the given
-	 * ast_str.  That information is then added to the NOTIFY request.
-	 *
-	 * \param data Current extension state changes
-	 * \param local URI of the dialog's local party, e.g. 'from'
-	 * \param remote URI of the dialog's remote party, e.g. 'to'
-	 * \param body_text Out parameter used to populate the NOTIFY msg body
-	 * \retval 0 Successfully created the body's text
-	 * \retval -1 Failed to create the body's text
-	 */
-	int (*create_body)(struct ast_sip_exten_state_data *data, const char *local,
-			   const char *remote, struct ast_str **body_text);
-
-	/*! Next item in the list */
-	AST_LIST_ENTRY(ast_sip_exten_state_provider) next;
-};
-
-/*!
- * \brief Registers an extension state provider.
- *
- * \param obj An extension state provider
- * \retval 0 Successfully registered the extension state provider
- * \retval -1 Failed to register the extension state provider
- */
-int ast_sip_register_exten_state_provider(struct ast_sip_exten_state_provider *obj);
-
-/*!
- * \brief Unregisters an extension state provider.
- *
- * \param obj An extension state provider
- */
-void ast_sip_unregister_exten_state_provider(struct ast_sip_exten_state_provider *obj);
-
-#endif /* _RES_PJSIP_EXTEN_STATE_H */
diff --git a/include/asterisk/res_pjsip_pubsub.h b/include/asterisk/res_pjsip_pubsub.h
index 2f676f193807c9cd321c8cf0cacba39afc3127c5..0b0a49e66e647c22e9f1991b200a6b1a9721692b 100644
--- a/include/asterisk/res_pjsip_pubsub.h
+++ b/include/asterisk/res_pjsip_pubsub.h
@@ -226,23 +226,22 @@ struct ast_sip_subscription_response_data {
 struct ast_sip_subscription_handler {
 	/*! The name of the event this handler deals with */
 	const char *event_name;
-	/*! The types of body this handler accepts */
+	/*! The types of body this handler accepts.
+	 *
+	 * \note This option has no bearing when the handler is used in the
+	 * notifier role. When in a subscriber role, this header is used to
+	 * populate the Accept: header of an outbound SUBSCRIBE request
+	 */
 	const char *accept[AST_SIP_MAX_ACCEPT];
 	/*!
-	 * \brief Indicates if this handler can be used as a default handler for an event type.
+	 * \brief Default body type defined for the event package this handler handles.
 	 *
 	 * Typically, a SUBSCRIBE request will contain one or more Accept headers that tell
 	 * what format they expect the body of NOTIFY requests to use. However, every event
 	 * package is required to define a default body format type to be used if a SUBSCRIBE
 	 * request for the event contains no Accept header.
-	 *
-	 * If this value is non-zero, then this handler provides the default body format for
-	 * the event package and can handle SUBSCRIBES with no Accept headers present.
-	 * If this value is zero, then this handler provides an alternative body format
-	 * from the default for the event package and cannot handle SUBSCRIBEs with no
-	 * Accept header.
 	 */
-	unsigned int handles_default_accept;
+	const char *default_accept;
 	/*!
 	 * \brief Called when a subscription is to be destroyed
 	 *
@@ -537,4 +536,176 @@ int ast_sip_register_subscription_handler(struct ast_sip_subscription_handler *h
  */
 void ast_sip_unregister_subscription_handler(struct ast_sip_subscription_handler *handler);
 
+/*!
+ * \brief Pubsub body generator
+ *
+ * A body generator is responsible for taking Asterisk content
+ * and converting it into a body format to be placed in an outbound
+ * SIP NOTIFY or PUBLISH request.
+ */
+struct ast_sip_pubsub_body_generator {
+	/*!
+	 * \brief Content type
+	 * In "plain/text", "plain" is the type
+	 */
+	const char *type;
+	/*!
+	 * \brief Content subtype
+	 * In "plain/text", "text" is the subtype
+	 */
+	const char *subtype;
+	/*!
+	 * \brief allocate body structure.
+	 *
+	 * Body generators will have this method called when a NOTIFY
+	 * or PUBLISH body needs to be created. The type returned depends on
+	 * the type of content being produced for the body. The data parameter
+	 * is provided by the subscription handler and will vary between different
+	 * event types.
+	 *
+	 * \param data The subscription data provided by the event handler
+	 * \retval non-NULL The allocated body
+	 * \retval NULL Failure
+	 */
+	void *(*allocate_body)(void *data);
+	/*!
+	 * \brief Add content to the body of a SIP request
+	 *
+	 * The body of the request has already been allocated by the body generator's
+	 * allocate_body callback.
+	 *
+	 * \param body The body of the SIP request. The type is determined by the
+	 * content type.
+	 * \param data The subscription data used to populate the body. The type is
+	 * determined by the content type.
+	 */
+	int (*generate_body_content)(void *body, void *data);
+	/*!
+	 * \brief Convert the body to a string.
+	 *
+	 * \param body The request body.
+	 * \param str The converted string form of the request body
+	 */
+	void (*to_string)(void *body, struct ast_str **str);
+	/*!
+	 * \brief Deallocate resources created for the body
+	 *
+	 * Optional callback to destroy resources allocated for the
+	 * message body.
+	 *
+	 * \param body Body to be destroyed
+	 */
+	void (*destroy_body)(void *body);
+	AST_LIST_ENTRY(ast_sip_pubsub_body_generator) list;
+};
+
+/*!
+ * \brief Body supplement
+ *
+ * Body supplements provide additions to bodies not already
+ * provided by body generators. This may include proprietary
+ * extensions, optional content, or other nonstandard fare.
+ */
+struct ast_sip_pubsub_body_supplement {
+	/*!
+	 * \brief Content type
+	 * In "plain/text", "plain" is the type
+	 */
+	const char *type;
+	/*!
+	 * \brief Content subtype
+	 * In "plain/text", "text" is the subtype
+	 */
+	const char *subtype;
+	/*!
+	 * \brief Add additional content to a SIP request body.
+	 *
+	 * A body generator will have already allocated a body and populated
+	 * it with base data for the event. The supplement's duty is, if desired,
+	 * to extend the body to have optional data beyond what a base RFC specifies.
+	 *
+	 * \param body The body of the SIP request. The type is determined by the
+	 * body generator that allocated the body.
+	 * \param data The subscription data used to populate the body. The type is
+	 * determined by the content type.
+	 */
+	int (*supplement_body)(void *body, void *data);
+	AST_LIST_ENTRY(ast_sip_pubsub_body_supplement) list;
+};
+
+/*!
+ * \since 13.0.0
+ * \brief Generate body content for a PUBLISH or NOTIFY
+ *
+ * This function takes a pre-allocated body and calls into registered body
+ * generators in order to fill in the body with appropriate details.
+ * The primary body generator will be called first, followed by the
+ * supplementary body generators
+ *
+ * \param content_type The content type of the body
+ * \param content_subtype The content subtype of the body
+ * \param data The data associated with body generation.
+ * \param[out] str The string representation of the generated body
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+int ast_sip_pubsub_generate_body_content(const char *content_type,
+		const char *content_subtype, void *data, struct ast_str **str);
+
+/*!
+ * \since 13.0.0
+ * \brief Register a body generator with the pubsub core.
+ *
+ * This may fail if an attempt is made to register a primary body supplement
+ * for a given content type if a primary body supplement for that content type
+ * has already been registered.
+ *
+ * \param generator Body generator to register
+ * \retval 0 Success
+ * \retval -1 Failure
+ */
+int ast_sip_pubsub_register_body_generator(struct ast_sip_pubsub_body_generator *generator);
+
+/*!
+ * \since 13.0.0
+ * \brief Unregister a body generator with the pubsub core.
+ *
+ * \param generator Body generator to unregister
+ */
+void ast_sip_pubsub_unregister_body_generator(struct ast_sip_pubsub_body_generator *generator);
+
+/*!
+ * \since 13.0.0
+ * \brief Register a body generator with the pubsub core.
+ *
+ * This may fail if an attempt is made to register a primary body supplement
+ * for a given content type if a primary body supplement for that content type
+ * has already been registered.
+ *
+ * \param generator Body generator to register
+ * \retval 0 Success
+ * \retval -1 Failure
+ */
+int ast_sip_pubsub_register_body_supplement(struct ast_sip_pubsub_body_supplement *supplement);
+
+/*!
+ * \since 13.0.0
+ * \brief Unregister a body generator with the pubsub core.
+ *
+ * \param generator Body generator to unregister
+ */
+void ast_sip_pubsub_unregister_body_supplement(struct ast_sip_pubsub_body_supplement *supplement);
+
+/*!
+ * \since 13.0.0
+ * \brief Get the body type used for this subscription
+ */
+const char *ast_sip_subscription_get_body_type(struct ast_sip_subscription *sub);
+
+/*!
+ * \since 13.0.0
+ * \brief Get the body subtype used for this subscription
+ */
+const char *ast_sip_subscription_get_body_subtype(struct ast_sip_subscription *sub);
+
 #endif /* RES_PJSIP_PUBSUB_H */
diff --git a/res/res_pjsip/presence_xml.c b/res/res_pjsip/presence_xml.c
new file mode 100644
index 0000000000000000000000000000000000000000..31e06eba4adac2edd27859ed57465aebf929c621
--- /dev/null
+++ b/res/res_pjsip/presence_xml.c
@@ -0,0 +1,166 @@
+/*
+ * asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Kevin Harwell <kharwell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_pjsip</depend>
+	<depend>res_pjsip_pubsub</depend>
+	<depend>res_pjsip_exten_state</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_simple.h>
+#include <pjlib.h>
+
+#include "asterisk/module.h"
+#include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_pubsub.h"
+#include "asterisk/res_pjsip_presence_xml.h"
+#include "asterisk/res_pjsip_body_generator_types.h"
+
+void ast_sip_sanitize_xml(const char *input, char *output, size_t len)
+{
+	char *copy = ast_strdupa(input);
+	char *break_point;
+
+	output[0] = '\0';
+
+	while ((break_point = strpbrk(copy, "<>\"&'"))) {
+		char to_escape = *break_point;
+
+		*break_point = '\0';
+		strncat(output, copy, len);
+
+		switch (to_escape) {
+		case '<':
+			strncat(output, "&lt;", len);
+			break;
+		case '>':
+			strncat(output, "&gt;", len);
+			break;
+		case '"':
+			strncat(output, "&quot;", len);
+			break;
+		case '&':
+			strncat(output, "&amp;", len);
+			break;
+		case '\'':
+			strncat(output, "&apos;", len);
+			break;
+		};
+
+		copy = break_point + 1;
+	}
+
+	/* Be sure to copy everything after the final bracket */
+	if (*copy) {
+		strncat(output, copy, len);
+	}
+}
+
+void ast_sip_presence_exten_state_to_str(int state, char **statestring, char **pidfstate,
+			       char **pidfnote, enum ast_sip_pidf_state *local_state)
+{
+	switch (state) {
+	case AST_EXTENSION_RINGING:
+		*statestring = "early";
+		*local_state = NOTIFY_INUSE;
+		*pidfstate = "busy";
+		*pidfnote = "Ringing";
+		break;
+	case AST_EXTENSION_INUSE:
+		*statestring = "confirmed";
+		*local_state = NOTIFY_INUSE;
+		*pidfstate = "busy";
+		*pidfnote = "On the phone";
+		break;
+	case AST_EXTENSION_BUSY:
+		*statestring = "confirmed";
+		*local_state = NOTIFY_CLOSED;
+		*pidfstate = "busy";
+		*pidfnote = "On the phone";
+		break;
+	case AST_EXTENSION_UNAVAILABLE:
+		*statestring = "terminated";
+		*local_state = NOTIFY_CLOSED;
+		*pidfstate = "away";
+		*pidfnote = "Unavailable";
+		break;
+	case AST_EXTENSION_ONHOLD:
+		*statestring = "confirmed";
+		*local_state = NOTIFY_CLOSED;
+		*pidfstate = "busy";
+		*pidfnote = "On hold";
+		break;
+	case AST_EXTENSION_NOT_INUSE:
+	default:
+		/* Default setting */
+		*statestring = "terminated";
+		*local_state = NOTIFY_OPEN;
+		*pidfstate = "--";
+		*pidfnote ="Ready";
+		break;
+	}
+}
+
+pj_xml_attr *ast_sip_presence_xml_create_attr(pj_pool_t *pool,
+		pj_xml_node *node, const char *name, const char *value)
+{
+	pj_xml_attr *attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr);
+
+	pj_strdup2(pool, &attr->name, name);
+	pj_strdup2(pool, &attr->value, value);
+
+	pj_xml_add_attr(node, attr);
+	return attr;
+}
+
+pj_xml_node *ast_sip_presence_xml_create_node(pj_pool_t *pool,
+		pj_xml_node *parent, const char* name)
+{
+	pj_xml_node *node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
+
+	pj_list_init(&node->attr_head);
+	pj_list_init(&node->node_head);
+
+	pj_strdup2(pool, &node->name, name);
+
+	node->content.ptr = NULL;
+	node->content.slen = 0;
+
+	pj_xml_add_node(parent, node);
+	return node;
+}
+
+void ast_sip_presence_xml_find_node_attr(pj_pool_t* pool,
+		pj_xml_node *parent, const char *node_name, const char *attr_name,
+		pj_xml_node **node, pj_xml_attr **attr)
+{
+	pj_str_t name;
+
+	if (!(*node = pj_xml_find_node(parent, pj_cstr(&name, node_name)))) {
+		*node = ast_sip_presence_xml_create_node(pool, parent, node_name);
+	}
+
+	if (!(*attr = pj_xml_find_attr(*node, pj_cstr(&name, attr_name), NULL))) {
+		*attr = ast_sip_presence_xml_create_attr(pool, *node, attr_name, "");
+	}
+}
diff --git a/res/res_pjsip_exten_state.c b/res/res_pjsip_exten_state.c
index f312a522c890cb3b5da1fe97ed9e45067660e698..819155f00c04010d77ba3eeec8416ca920ced9c2 100644
--- a/res/res_pjsip_exten_state.c
+++ b/res/res_pjsip_exten_state.c
@@ -31,7 +31,7 @@
 
 #include "asterisk/res_pjsip.h"
 #include "asterisk/res_pjsip_pubsub.h"
-#include "asterisk/res_pjsip_exten_state.h"
+#include "asterisk/res_pjsip_body_generator_types.h"
 #include "asterisk/module.h"
 #include "asterisk/logger.h"
 #include "asterisk/astobj2.h"
@@ -41,43 +41,6 @@
 #define BODY_SIZE 1024
 #define EVENT_TYPE_SIZE 50
 
-AST_RWLIST_HEAD_STATIC(providers, ast_sip_exten_state_provider);
-
-/*!
- * \internal
- * \brief Find a provider based on the given accept body type.
- */
-static struct ast_sip_exten_state_provider *provider_by_type(const char *type)
-{
-	struct ast_sip_exten_state_provider *i;
-	SCOPED_LOCK(lock, &providers, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
-	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&providers, i, next) {
-		if (!strcmp(i->body_type, type)) {
-			return i;
-		}
-	}
-	AST_RWLIST_TRAVERSE_SAFE_END;
-	return NULL;
-}
-
-/*!
- * \internal
- * \brief Find a provider based on the given accept body types.
- */
-static struct ast_sip_exten_state_provider *provider_by_types(const char *event_name,
-							      char **types, int count)
-{
-	int i;
-	struct ast_sip_exten_state_provider *res;
-	for (i = 0; i < count; ++i) {
-		if ((res = provider_by_type(types[i])) &&
-		    !strcmp(event_name, res->event_name)) {
-			return res;
-		}
-	}
-	return NULL;
-}
-
 /*!
  * \brief A subscription for extension state
  *
@@ -90,12 +53,6 @@ struct exten_state_subscription {
 	int id;
 	/*! The SIP subscription */
 	struct ast_sip_subscription *sip_sub;
-	/*! The name of the event the subscribed to */
-	char event_name[EVENT_TYPE_SIZE];
-	/*! The number of body types */
-	int body_types_count;
-	/*! The subscription body types */
-	char **body_types;
 	/*! Context in which subscription looks for updates */
 	char context[AST_MAX_CONTEXT];
 	/*! Extension within the context to receive updates from */
@@ -104,42 +61,38 @@ struct exten_state_subscription {
 	enum ast_extension_states last_exten_state;
 };
 
+#define DEFAULT_PRESENCE_BODY "application/pidf+xml"
+
+static void subscription_shutdown(struct ast_sip_subscription *sub);
+static struct ast_sip_subscription *new_subscribe(struct ast_sip_endpoint *endpoint,
+						  pjsip_rx_data *rdata);
+static void resubscribe(struct ast_sip_subscription *sub, pjsip_rx_data *rdata,
+			struct ast_sip_subscription_response_data *response_data);
+static void subscription_timeout(struct ast_sip_subscription *sub);
+static void subscription_terminated(struct ast_sip_subscription *sub,
+				    pjsip_rx_data *rdata);
+static void to_ami(struct ast_sip_subscription *sub,
+		   struct ast_str **buf);
+
+struct ast_sip_subscription_handler presence_handler = {
+	.event_name = "presence",
+	.accept = { DEFAULT_PRESENCE_BODY, },
+	.default_accept = DEFAULT_PRESENCE_BODY,
+	.subscription_shutdown = subscription_shutdown,
+	.new_subscribe = new_subscribe,
+	.resubscribe = resubscribe,
+	.subscription_timeout = subscription_timeout,
+	.subscription_terminated = subscription_terminated,
+	.to_ami = to_ami,
+};
+
 static void exten_state_subscription_destructor(void *obj)
 {
 	struct exten_state_subscription *sub = obj;
-	int i;
-
-	for (i = 0; i < sub->body_types_count; ++i) {
-		ast_free(sub->body_types[i]);
-	}
 
-	ast_free(sub->body_types);
 	ao2_cleanup(sub->sip_sub);
 }
 
-/*!
- * \internal
- * \brief Copies the body types the message wishes to subscribe to.
- */
-static void copy_body_types(pjsip_rx_data *rdata,
-			    struct exten_state_subscription *exten_state_sub)
-{
-	int i;
-	pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)
-		pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL);
-
-	exten_state_sub->body_types_count = hdr->count;
-	exten_state_sub->body_types = ast_malloc(hdr->count * sizeof(char*));
-
-	for (i = 0; i < hdr->count; ++i) {
-		exten_state_sub->body_types[i] =
-			ast_malloc(hdr->values[i].slen * sizeof(char*) + 1);
-
-		ast_copy_string(exten_state_sub->body_types[i],
-				pj_strbuf(&hdr->values[i]), hdr->values[i].slen + 1);
-	}
-}
-
 /*!
  * \internal
  * \brief Initialize the last extension state to something outside
@@ -157,11 +110,6 @@ static void copy_body_types(pjsip_rx_data *rdata,
 static struct exten_state_subscription *exten_state_subscription_alloc(
 	struct ast_sip_endpoint *endpoint, enum ast_sip_subscription_role role, pjsip_rx_data *rdata)
 {
-	static const pj_str_t event_name = { "Event", 5 };
-	pjsip_event_hdr *hdr = (pjsip_event_hdr*)pjsip_msg_find_hdr_by_name(
-		rdata->msg_info.msg, &event_name, NULL);
-
-	struct ast_sip_exten_state_provider *provider;
 	RAII_VAR(struct exten_state_subscription *, exten_state_sub,
 		 ao2_alloc(sizeof(*exten_state_sub), exten_state_subscription_destructor), ao2_cleanup);
 
@@ -169,19 +117,8 @@ static struct exten_state_subscription *exten_state_subscription_alloc(
 		return NULL;
 	}
 
-	ast_copy_pj_str(exten_state_sub->event_name, &hdr->event_type,
-			sizeof(exten_state_sub->event_name));
-
-	copy_body_types(rdata, exten_state_sub);
-	if (!(provider = provider_by_types(exten_state_sub->event_name,
-					   exten_state_sub->body_types,
-					   exten_state_sub->body_types_count))) {
-		ast_log(LOG_WARNING, "Unable to locate subscription handler\n");
-		return NULL;
-	}
-
 	if (!(exten_state_sub->sip_sub = ast_sip_create_subscription(
-		      provider->handler, role, endpoint, rdata))) {
+		      &presence_handler, role, endpoint, rdata))) {
 		ast_log(LOG_WARNING, "Unable to create SIP subscription for endpoint %s\n",
 			ast_sorcery_object_get_id(endpoint));
 		return NULL;
@@ -204,27 +141,13 @@ static void create_send_notify(struct exten_state_subscription *exten_state_sub,
 	pj_str_t reason_str;
 	const pj_str_t *reason_str_ptr = NULL;
 	pjsip_tx_data *tdata;
-	pjsip_dialog *dlg;
-	char local[PJSIP_MAX_URL_SIZE], remote[PJSIP_MAX_URL_SIZE];
 	struct ast_sip_body body;
 
-	struct ast_sip_exten_state_provider *provider = provider_by_types(
-		exten_state_sub->event_name, exten_state_sub->body_types,
-		exten_state_sub->body_types_count);
-
-	if (!provider) {
-		ast_log(LOG_ERROR, "Unable to locate provider for subscription\n");
-		return;
-	}
-
-	body.type = provider->type;
-	body.subtype = provider->subtype;
-
-	dlg = ast_sip_subscription_get_dlg(exten_state_sub->sip_sub);
-	ast_copy_pj_str(local, &dlg->local.info_str, sizeof(local));
-	ast_copy_pj_str(remote, &dlg->remote.info_str, sizeof(remote));
+	body.type = ast_sip_subscription_get_body_type(exten_state_sub->sip_sub);
+	body.subtype = ast_sip_subscription_get_body_subtype(exten_state_sub->sip_sub);
 
-	if (provider->create_body(exten_state_data, local, remote, &body_text)) {
+	if (ast_sip_pubsub_generate_body_content(body.type, body.subtype,
+				exten_state_data, &body_text)) {
 		ast_log(LOG_ERROR, "Unable to create body on NOTIFY request\n");
 		return;
 	}
@@ -262,13 +185,19 @@ static void send_notify(struct exten_state_subscription *exten_state_sub, const
 {
 	RAII_VAR(struct ao2_container*, info, NULL, ao2_cleanup);
 	char *subtype = NULL, *message = NULL;
-
+	pjsip_dialog *dlg;
 	struct ast_sip_exten_state_data exten_state_data = {
 		.exten = exten_state_sub->exten,
 		.presence_state = ast_hint_presence_state(NULL, exten_state_sub->context,
 							  exten_state_sub->exten, &subtype, &message),
 	};
 
+	dlg = ast_sip_subscription_get_dlg(exten_state_sub->sip_sub);
+	ast_copy_pj_str(exten_state_data.local, &dlg->local.info_str,
+			sizeof(exten_state_data.local));
+	ast_copy_pj_str(exten_state_data.remote, &dlg->remote.info_str,
+			sizeof(exten_state_data.remote));
+
 	if ((exten_state_data.exten_state = ast_extension_state_extended(
 		     NULL, exten_state_sub->context, exten_state_sub->exten, &info)) < 0) {
 
@@ -277,8 +206,12 @@ static void send_notify(struct exten_state_subscription *exten_state_sub, const
 		return;
 	}
 
+	exten_state_data.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(),
+			"exten_state", 1024, 1024);
+
 	exten_state_data.device_state_info = info;
 	create_send_notify(exten_state_sub, reason, evsub_state, &exten_state_data);
+	pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), exten_state_data.pool);
 }
 
 struct notify_task_data {
@@ -300,6 +233,7 @@ static struct notify_task_data *alloc_notify_task_data(char *exten, struct exten
 {
 	struct notify_task_data *task_data =
 		ao2_alloc(sizeof(*task_data), notify_task_data_destructor);
+	struct pjsip_dialog *dlg;
 
 	if (!task_data) {
 		ast_log(LOG_WARNING, "Unable to create notify task data\n");
@@ -320,6 +254,12 @@ static struct notify_task_data *alloc_notify_task_data(char *exten, struct exten
 		ao2_ref(task_data->exten_state_data.device_state_info, +1);
 	}
 
+	dlg = ast_sip_subscription_get_dlg(exten_state_sub->sip_sub);
+	ast_copy_pj_str(task_data->exten_state_data.local, &dlg->local.info_str,
+			sizeof(task_data->exten_state_data.local));
+	ast_copy_pj_str(task_data->exten_state_data.remote, &dlg->remote.info_str,
+			sizeof(task_data->exten_state_data.remote));
+
 	if ((info->exten_state == AST_EXTENSION_DEACTIVATED) ||
 	    (info->exten_state == AST_EXTENSION_REMOVED)) {
 		task_data->evsub_state = PJSIP_EVSUB_STATE_TERMINATED;
@@ -334,9 +274,16 @@ static int notify_task(void *obj)
 {
 	RAII_VAR(struct notify_task_data *, task_data, obj, ao2_cleanup);
 
+	/* Pool allocation has to happen here so that we allocate within a PJLIB thread */
+	task_data->exten_state_data.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(),
+			"exten_state", 1024, 1024);
+
 	create_send_notify(task_data->exten_state_sub, task_data->evsub_state ==
 			   PJSIP_EVSUB_STATE_TERMINATED ? "noresource" : NULL,
 			   task_data->evsub_state, &task_data->exten_state_data);
+
+	pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(),
+			task_data->exten_state_data.pool);
 	return 0;
 }
 
@@ -527,107 +474,19 @@ static void to_ami(struct ast_sip_subscription *sub,
 			       exten_state_sub->last_exten_state));
 }
 
-#define DEFAULT_PRESENCE_BODY "application/pidf+xml"
-
-/*!
- * \internal
- * \brief Create and register a subscription handler.
- *
- * Creates a subscription handler that can be registered with the pub/sub
- * framework for the given event_name and accept value.
- */
-static struct ast_sip_subscription_handler *create_and_register_handler(
-	const char *event_name, const char *accept)
+static int load_module(void)
 {
-	struct ast_sip_subscription_handler *handler =
-		ao2_alloc(sizeof(*handler), NULL);
-
-	if (!handler) {
-		return NULL;
-	}
-
-	handler->event_name = event_name;
-	handler->accept[0] = accept;
-	if (!strcmp(accept, DEFAULT_PRESENCE_BODY)) {
-		handler->handles_default_accept = 1;
-	}
-
-	handler->subscription_shutdown = subscription_shutdown;
-	handler->new_subscribe = new_subscribe;
-	handler->resubscribe = resubscribe;
-	handler->subscription_timeout = subscription_timeout;
-	handler->subscription_terminated = subscription_terminated;
-	handler->to_ami = to_ami;
-
-	if (ast_sip_register_subscription_handler(handler)) {
+	if (ast_sip_register_subscription_handler(&presence_handler)) {
 		ast_log(LOG_WARNING, "Unable to register subscription handler %s\n",
-			handler->event_name);
-		ao2_cleanup(handler);
-		return NULL;
+			presence_handler.event_name);
+		return AST_MODULE_LOAD_DECLINE;
 	}
-
-	return handler;
-}
-
-int ast_sip_register_exten_state_provider(struct ast_sip_exten_state_provider *obj)
-{
-	if (ast_strlen_zero(obj->type)) {
-		ast_log(LOG_WARNING, "Type not specified on provider for event %s\n",
-			obj->event_name);
-		return -1;
-	}
-
-	if (ast_strlen_zero(obj->subtype)) {
-		ast_log(LOG_WARNING, "Subtype not specified on provider for event %s\n",
-			obj->event_name);
-		return -1;
-	}
-
-	if (!obj->create_body) {
-		ast_log(LOG_WARNING, "Body handler not specified on provide for event %s\n",
-		    obj->event_name);
-		return -1;
-	}
-
-	if (!(obj->handler = create_and_register_handler(obj->event_name, obj->body_type))) {
-		ast_log(LOG_WARNING, "Handler could not be registered for provider event %s\n",
-		    obj->event_name);
-		return -1;
-	}
-
-	/* scope to avoid mix declarations */
-	{
-		SCOPED_LOCK(lock, &providers, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
-		AST_RWLIST_INSERT_TAIL(&providers, obj, next);
-		ast_module_ref(ast_module_info->self);
-	}
-
-	return 0;
-}
-
-void ast_sip_unregister_exten_state_provider(struct ast_sip_exten_state_provider *obj)
-{
-	struct ast_sip_exten_state_provider *i;
-	SCOPED_LOCK(lock, &providers, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
-	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&providers, i, next) {
-		if (i == obj) {
-			ast_sip_unregister_subscription_handler(i->handler);
-			ao2_cleanup(i->handler);
-			AST_RWLIST_REMOVE_CURRENT(next);
-			ast_module_unref(ast_module_info->self);
-			break;
-		}
-	}
-	AST_RWLIST_TRAVERSE_SAFE_END;
-}
-
-static int load_module(void)
-{
 	return AST_MODULE_LOAD_SUCCESS;
 }
 
 static int unload_module(void)
 {
+	ast_sip_unregister_subscription_handler(&presence_handler);
 	return 0;
 }
 
diff --git a/res/res_pjsip_mwi.c b/res/res_pjsip_mwi.c
index bb12aa5e9fbd524b435dd3e842dc0de80c9fcfb0..9137b677b69911e62c45f28c59ab2aca8b7d75c3 100644
--- a/res/res_pjsip_mwi.c
+++ b/res/res_pjsip_mwi.c
@@ -31,6 +31,7 @@
 
 #include "asterisk/res_pjsip.h"
 #include "asterisk/res_pjsip_pubsub.h"
+#include "asterisk/res_pjsip_body_generator_types.h"
 #include "asterisk/module.h"
 #include "asterisk/logger.h"
 #include "asterisk/astobj2.h"
@@ -43,6 +44,10 @@ AO2_GLOBAL_OBJ_STATIC(unsolicited_mwi);
 
 #define STASIS_BUCKETS 13
 #define MWI_BUCKETS 53
+
+#define MWI_TYPE "application"
+#define MWI_SUBTYPE "simple-message-summary"
+
 static void mwi_subscription_shutdown(struct ast_sip_subscription *sub);
 static struct ast_sip_subscription *mwi_new_subscribe(struct ast_sip_endpoint *endpoint,
 		pjsip_rx_data *rdata);
@@ -58,8 +63,8 @@ static void mwi_to_ami(struct ast_sip_subscription *sub, struct ast_str **buf);
 
 static struct ast_sip_subscription_handler mwi_handler = {
 	.event_name = "message-summary",
-	.accept = { "application/simple-message-summary", },
-	.handles_default_accept = 1,
+	.accept = { MWI_TYPE"/"MWI_SUBTYPE, },
+	.default_accept =  MWI_TYPE"/"MWI_SUBTYPE,
 	.subscription_shutdown = mwi_subscription_shutdown,
 	.new_subscribe = mwi_new_subscribe,
 	.resubscribe = mwi_resubscribe,
@@ -223,17 +228,11 @@ static int mwi_sub_cmp(void *obj, void *arg, int flags)
 	return strcmp(mwi_sub1->id, mwi_sub2->id) ? 0 : CMP_MATCH;
 }
 
-struct message_accumulator {
-	int old_msgs;
-	int new_msgs;
-	const char *reason;
-};
-
 static int get_message_count(void *obj, void *arg, int flags)
 {
 	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
 	struct mwi_stasis_subscription *mwi_stasis = obj;
-	struct message_accumulator *counter = arg;
+	struct ast_sip_message_accumulator *counter = arg;
 	struct ast_mwi_state *mwi_state;
 
 	msg = stasis_cache_get(ast_mwi_state_cache(), ast_mwi_state_type(), mwi_stasis->mailbox);
@@ -366,11 +365,7 @@ static void send_unsolicited_mwi_notify(struct mwi_subscription *sub, pjsip_evsu
 static void send_mwi_notify(struct mwi_subscription *sub, pjsip_evsub_state state, const char *reason)
 {
 	const pj_str_t *reason_str_ptr = NULL;
-	static pjsip_media_type mwi_type = {
-		.type = { "application", 11 },
-		.subtype = { "simple-message-summary", 22 },
-	};
-	struct message_accumulator counter = {
+	struct ast_sip_message_accumulator counter = {
 		.old_msgs = 0,
 		.new_msgs = 0,
 	};
@@ -378,6 +373,13 @@ static void send_mwi_notify(struct mwi_subscription *sub, pjsip_evsub_state stat
 	pjsip_tx_data *tdata;
 	pj_str_t reason_str;
 	pj_str_t pj_body;
+	const char *type = sub->is_solicited ?
+		ast_sip_subscription_get_body_type(sub->sip_sub) :
+		MWI_TYPE;
+	const char *subtype = sub->is_solicited ?
+		ast_sip_subscription_get_body_subtype(sub->sip_sub) :
+		MWI_SUBTYPE;
+	pjsip_media_type mwi_type = { { 0,}, };
 
 	ao2_callback(sub->stasis_subs, OBJ_NODATA, get_message_count, &counter);
 
@@ -385,11 +387,17 @@ static void send_mwi_notify(struct mwi_subscription *sub, pjsip_evsub_state stat
 		pj_cstr(&reason_str, reason);
 		reason_str_ptr = &reason_str;
 	}
-	ast_str_append(&body, 0, "Messages-Waiting: %s\r\n", counter.new_msgs ? "yes" : "no");
-	ast_str_append(&body, 0, "Voice-Message: %d/%d (0/0)\r\n", counter.new_msgs, counter.old_msgs);
+
+	if (ast_sip_pubsub_generate_body_content(type, subtype, &counter, &body)) {
+		ast_log(LOG_WARNING, "Unable to generate SIP MWI NOTIFY body.\n");
+		return;
+	}
+
 	pj_cstr(&pj_body, ast_str_buffer(body));
+	pj_cstr(&mwi_type.type, type);
+	pj_cstr(&mwi_type.subtype, subtype);
 
-	ast_debug(5, "Sending  %s MWI NOTIFY to endpoint %s, new messages: %d, old messages: %d\n",
+	ast_debug(5, "Sending %s MWI NOTIFY to endpoint %s, new messages: %d, old messages: %d\n",
 			sub->is_solicited ? "solicited" : "unsolicited", sub->id, counter.new_msgs,
 			counter.old_msgs);
 
diff --git a/res/res_pjsip_mwi_body_generator.c b/res/res_pjsip_mwi_body_generator.c
new file mode 100644
index 0000000000000000000000000000000000000000..9a721dbf47d7441492e41bf72ef70b1708fba1e1
--- /dev/null
+++ b/res/res_pjsip_mwi_body_generator.c
@@ -0,0 +1,112 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2014, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_pjsip</depend>
+	<depend>res_pjsip_pubsub</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_simple.h>
+#include <pjlib.h>
+
+#include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_pubsub.h"
+#include "asterisk/res_pjsip_body_generator_types.h"
+#include "asterisk/module.h"
+#include "asterisk/strings.h"
+
+#define MWI_TYPE "application"
+#define MWI_SUBTYPE "simple-message-summary"
+
+static void *mwi_allocate_body(void *data)
+{
+	struct ast_str **mwi_str;
+
+	mwi_str = ast_malloc(sizeof(*mwi_str));
+	if (!mwi_str) {
+		return NULL;
+	}
+	*mwi_str = ast_str_create(64);
+	if (!*mwi_str) {
+		ast_free(mwi_str);
+		return NULL;
+	}
+	return mwi_str;
+}
+
+static int mwi_generate_body_content(void *body, void *data)
+{
+	struct ast_str **mwi = body;
+	struct ast_sip_message_accumulator *counter = data;
+
+	ast_str_append(mwi, 0, "Messages-Waiting: %s\r\n",
+			counter->new_msgs ? "yes" : "no");
+	ast_str_append(mwi, 0, "Voice-Message: %d/%d (0/0)\r\n",
+			counter->new_msgs, counter->old_msgs);
+
+	return 0;
+}
+
+static void mwi_to_string(void *body, struct ast_str **str)
+{
+	struct ast_str **mwi = body;
+
+	ast_str_set(str, 0, "%s", ast_str_buffer(*mwi));
+}
+
+static void mwi_destroy_body(void *body)
+{
+	struct ast_str **mwi = body;
+
+	ast_free(*mwi);
+	ast_free(mwi);
+}
+
+static struct ast_sip_pubsub_body_generator mwi_generator = {
+	.type = MWI_TYPE,
+	.subtype = MWI_SUBTYPE,
+	.allocate_body = mwi_allocate_body,
+	.generate_body_content = mwi_generate_body_content,
+	.to_string = mwi_to_string,
+	.destroy_body = mwi_destroy_body,
+};
+
+static int load_module(void)
+{
+	if (ast_sip_pubsub_register_body_generator(&mwi_generator)) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_sip_pubsub_unregister_body_generator(&mwi_generator);
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP MWI resource",
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_CHANNEL_DEPEND,
+);
diff --git a/res/res_pjsip_pidf.c b/res/res_pjsip_pidf.c
deleted file mode 100644
index 2a22c134a7a483756d0016f1939b58a7dd41d426..0000000000000000000000000000000000000000
--- a/res/res_pjsip_pidf.c
+++ /dev/null
@@ -1,398 +0,0 @@
-/*
- * asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2013, Digium, Inc.
- *
- * Kevin Harwell <kharwell@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*** MODULEINFO
-	<depend>pjproject</depend>
-	<depend>res_pjsip</depend>
-	<depend>res_pjsip_pubsub</depend>
-	<depend>res_pjsip_exten_state</depend>
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-#include <pjsip.h>
-#include <pjsip_simple.h>
-#include <pjlib.h>
-
-#include "asterisk/module.h"
-#include "asterisk/res_pjsip.h"
-#include "asterisk/res_pjsip_exten_state.h"
-
-enum state {
-	NOTIFY_OPEN,
-	NOTIFY_INUSE,
-	NOTIFY_CLOSED
-};
-
-static void exten_state_to_str(int state, char **statestring, char **pidfstate,
-			       char **pidfnote, int *local_state)
-{
-	switch (state) {
-	case AST_EXTENSION_RINGING:
-		*statestring = "early";
-		*local_state = NOTIFY_INUSE;
-		*pidfstate = "busy";
-		*pidfnote = "Ringing";
-		break;
-	case AST_EXTENSION_INUSE:
-		*statestring = "confirmed";
-		*local_state = NOTIFY_INUSE;
-		*pidfstate = "busy";
-		*pidfnote = "On the phone";
-		break;
-	case AST_EXTENSION_BUSY:
-		*statestring = "confirmed";
-		*local_state = NOTIFY_CLOSED;
-		*pidfstate = "busy";
-		*pidfnote = "On the phone";
-		break;
-	case AST_EXTENSION_UNAVAILABLE:
-		*statestring = "terminated";
-		*local_state = NOTIFY_CLOSED;
-		*pidfstate = "away";
-		*pidfnote = "Unavailable";
-		break;
-	case AST_EXTENSION_ONHOLD:
-		*statestring = "confirmed";
-		*local_state = NOTIFY_CLOSED;
-		*pidfstate = "busy";
-		*pidfnote = "On hold";
-		break;
-	case AST_EXTENSION_NOT_INUSE:
-	default:
-		/* Default setting */
-		*statestring = "terminated";
-		*local_state = NOTIFY_OPEN;
-		*pidfstate = "--";
-		*pidfnote ="Ready";
-
-		break;
-	}
-}
-
-static pj_xml_attr *create_attr(pj_pool_t *pool, pj_xml_node *node,
-				const char *name, const char *value)
-{
-	pj_xml_attr *attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr);
-
-	pj_strdup2(pool, &attr->name, name);
-	pj_strdup2(pool, &attr->value, value);
-
-	pj_xml_add_attr(node, attr);
-	return attr;
-}
-
-static pj_xml_node *create_node(pj_pool_t *pool, pj_xml_node *parent,
-				const char* name)
-{
-	pj_xml_node *node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
-
-	pj_list_init(&node->attr_head);
-	pj_list_init(&node->node_head);
-
-	pj_strdup2(pool, &node->name, name);
-
-	node->content.ptr = NULL;
-	node->content.slen = 0;
-
-	pj_xml_add_node(parent, node);
-	return node;
-}
-
-static void find_node_attr(pj_pool_t* pool, pj_xml_node *parent,
-				   const char *node_name, const char *attr_name,
-				   pj_xml_node **node, pj_xml_attr **attr)
-{
-	pj_str_t name;
-
-	if (!(*node = pj_xml_find_node(parent, pj_cstr(&name, node_name)))) {
-		*node = create_node(pool, parent, node_name);
-	}
-
-	if (!(*attr = pj_xml_find_attr(*node, pj_cstr(&name, attr_name), NULL))) {
-		*attr = create_attr(pool, *node, attr_name, "");
-	}
-}
-
-/*!
- * \internal
- * \brief Adds non standard elements to the xml body
- *
- * This is some code that was part of the original chan_sip implementation
- * that is not part of the RFC 3863 definition, but we are keeping available
- * for backward compatability. The original comment stated that Eyebeam
- * supports this format.
-
- */
-static void add_non_standard(pj_pool_t *pool, pj_xml_node *node, const char *pidfstate)
-{
-	static const char *XMLNS_PP = "xmlns:pp";
-	static const char *XMLNS_PERSON = "urn:ietf:params:xml:ns:pidf:person";
-
-	static const char *XMLNS_ES = "xmlns:es";
-	static const char *XMLNS_RPID_STATUS = "urn:ietf:params:xml:ns:pidf:rpid:status:rpid-status";
-
-	static const char *XMLNS_EP = "xmlns:ep";
-	static const char *XMLNS_RPID_PERSON = "urn:ietf:params:xml:ns:pidf:rpid:rpid-person";
-
-	pj_xml_node *person = create_node(pool, node, "pp:person");
-	pj_xml_node *status = create_node(pool, person, "status");
-
-	if (pidfstate[0] != '-') {
-		pj_xml_node *activities = create_node(pool, status, "ep:activities");
-		size_t str_size = sizeof("ep:") + strlen(pidfstate);
-
-		activities->content.ptr = pj_pool_alloc(pool, str_size);
-		activities->content.slen = pj_ansi_snprintf(activities->content.ptr, str_size,
-				"ep:%s", pidfstate);
-	}
-
-	create_attr(pool, node, XMLNS_PP, XMLNS_PERSON);
-	create_attr(pool, node, XMLNS_ES, XMLNS_RPID_STATUS);
-	create_attr(pool, node, XMLNS_EP, XMLNS_RPID_PERSON);
-}
-
-static void release_pool(void *obj)
-{
-	pj_pool_t *pool = obj;
-
-	pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
-}
-
-/*!
- * \internal
- * \brief Convert angle brackets in input into escaped forms suitable for XML
- *
- * \param input Raw input string
- * \param output Sanitized string
- * \param len Size of output buffer
- */
-static void sanitize_xml(const char *input, char *output, size_t len)
-{
-	char *copy = ast_strdupa(input);
-	char *break_point;
-
-	output[0] = '\0';
-
-	while ((break_point = strpbrk(copy, "<>\"&'"))) {
-		char to_escape = *break_point;
-
-		*break_point = '\0';
-		strncat(output, copy, len);
-
-		switch (to_escape) {
-		case '<':
-			strncat(output, "&lt;", len);
-			break;
-		case '>':
-			strncat(output, "&gt;", len);
-			break;
-		case '"':
-			strncat(output, "&quot;", len);
-			break;
-		case '&':
-			strncat(output, "&amp;", len);
-			break;
-		case '\'':
-			strncat(output, "&apos;", len);
-			break;
-		};
-
-		copy = break_point + 1;
-	}
-
-	/* Be sure to copy everything after the final bracket */
-	if (*copy) {
-		strncat(output, copy, len);
-	}
-}
-
-static int pidf_xml_create_body(struct ast_sip_exten_state_data *data, const char *local,
-				const char *remote, struct ast_str **body_text)
-{
-	pjpidf_pres *pres;
-	pjpidf_tuple *tuple;
-	pj_str_t entity, note, id, contact, priority;
-	char *statestring = NULL, *pidfstate = NULL, *pidfnote = NULL;
-	int local_state, size;
-	char sanitized[PJSIP_MAX_URL_SIZE];
-
-	RAII_VAR(pj_pool_t *, pool,
-		 pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(),
-					 "pidf", 1024, 1024), release_pool);
-
-	exten_state_to_str(data->exten_state, &statestring, &pidfstate,
-			   &pidfnote, &local_state);
-
-	if (!(pres = pjpidf_create(pool, pj_cstr(&entity, local)))) {
-		ast_log(LOG_WARNING, "Unable to create PIDF presence\n");
-		return -1;
-	}
-
-	add_non_standard(pool, pres, pidfstate);
-
-	if (!pjpidf_pres_add_note(pool, pres, pj_cstr(&note, pidfnote))) {
-		ast_log(LOG_WARNING, "Unable to add note to PIDF presence\n");
-		return -1;
-	}
-
-	if (!(tuple = pjpidf_pres_add_tuple(pool, pres, pj_cstr(&id, data->exten)))) {
-		ast_log(LOG_WARNING, "Unable to create PIDF tuple\n");
-		return -1;
-	}
-
-	sanitize_xml(remote, sanitized, sizeof(sanitized));
-	pjpidf_tuple_set_contact(pool, tuple, pj_cstr(&contact, sanitized));
-	pjpidf_tuple_set_contact_prio(pool, tuple, pj_cstr(&priority, "1"));
-	pjpidf_status_set_basic_open(pjpidf_tuple_get_status(tuple),
-			local_state == NOTIFY_OPEN);
-
-	if (!(size = pjpidf_print(pres, ast_str_buffer(*body_text),
-				  ast_str_size(*body_text)))) {
-		ast_log(LOG_WARNING, "PIDF body text too large\n");
-		return -1;
-	}
-	*(ast_str_buffer(*body_text) + size) = '\0';
-	ast_str_update(*body_text);
-
-	return 0;
-}
-
-static struct ast_sip_exten_state_provider pidf_xml_provider = {
-	.event_name = "presence",
-	.type = "application",
-	.subtype = "pidf+xml",
-	.body_type = "application/pidf+xml",
-	.create_body = pidf_xml_create_body
-};
-
-static int xpidf_xml_create_body(struct ast_sip_exten_state_data *data, const char *local,
-				 const char *remote, struct ast_str **body_text)
-{
-	static pj_str_t STR_ADDR_PARAM = { ";user=ip", 8 };
-	pjxpidf_pres *pres;
-	pj_xml_attr *attr;
-	pj_str_t name, uri;
-	char *statestring = NULL, *pidfstate = NULL, *pidfnote = NULL;
-	int local_state, size;
-	char sanitized[PJSIP_MAX_URL_SIZE];
-	pj_xml_node *atom;
-	pj_xml_node *address;
-	pj_xml_node *status;
-	pj_xml_node *msnsubstatus;
-
-	RAII_VAR(pj_pool_t *, pool,
-		 pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(),
-					 "pidf", 1024, 1024), release_pool);
-
-	exten_state_to_str(data->exten_state, &statestring, &pidfstate,
-			   &pidfnote, &local_state);
-
-	if (!(pres = pjxpidf_create(pool, pj_cstr(&name, local)))) {
-		ast_log(LOG_WARNING, "Unable to create PIDF presence\n");
-		return -1;
-	}
-
-	find_node_attr(pool, pres, "atom", "id", &atom, &attr);
-	pj_strdup2(pool, &attr->value, data->exten);
-
-	find_node_attr(pool, atom, "address", "uri", &address, &attr);
-
-	sanitize_xml(remote, sanitized, sizeof(sanitized));
-
-	uri.ptr = (char*) pj_pool_alloc(pool, strlen(sanitized) + STR_ADDR_PARAM.slen);
-	pj_strcpy2( &uri, sanitized);
-
-	pj_strcat( &uri, &STR_ADDR_PARAM);
-	pj_strdup(pool, &attr->value, &uri);
-
-	create_attr(pool, address, "priority", "0.80000");
-
-	find_node_attr(pool, address, "status", "status", &status, &attr);
-	pj_strdup2(pool, &attr->value,
-		   (local_state ==  NOTIFY_OPEN) ? "open" :
-		   (local_state == NOTIFY_INUSE) ? "inuse" : "closed");
-
-	find_node_attr(pool, address, "msnsubstatus", "substatus", &msnsubstatus, &attr);
-	pj_strdup2(pool, &attr->value,
-		   (local_state == NOTIFY_OPEN) ? "online" :
-		   (local_state == NOTIFY_INUSE) ? "onthephone" : "offline");
-
-	if (!(size = pjxpidf_print(pres, ast_str_buffer(*body_text),
-				  ast_str_size(*body_text)))) {
-		ast_log(LOG_WARNING, "XPIDF body text too large\n");
-		return -1;
-	}
-
-	*(ast_str_buffer(*body_text) + size) = '\0';
-	ast_str_update(*body_text);
-
-	return 0;
-}
-
-static struct ast_sip_exten_state_provider xpidf_xml_provider = {
-	.event_name = "presence",
-	.type = "application",
-	.subtype = "xpidf+xml",
-	.body_type = "application/xpidf+xml",
-	.create_body = xpidf_xml_create_body
-};
-
-static struct ast_sip_exten_state_provider cpim_pidf_xml_provider = {
-	.event_name = "presence",
-	.type = "application",
-	.subtype = "cpim-pidf+xml",
-	.body_type = "application/cpim-pidf+xml",
-	.create_body = xpidf_xml_create_body,
-};
-
-static int load_module(void)
-{
-	if (ast_sip_register_exten_state_provider(&pidf_xml_provider)) {
-		ast_log(LOG_WARNING, "Unable to load provider event_name=%s, body_type=%s",
-			pidf_xml_provider.event_name, pidf_xml_provider.body_type);
-	}
-
-	if (ast_sip_register_exten_state_provider(&xpidf_xml_provider)) {
-		ast_log(LOG_WARNING, "Unable to load provider event_name=%s, body_type=%s",
-			xpidf_xml_provider.event_name, xpidf_xml_provider.body_type);
-	}
-
-	if (ast_sip_register_exten_state_provider(&cpim_pidf_xml_provider)) {
-		ast_log(LOG_WARNING, "Unable to load provider event_name=%s, body_type=%s",
-			cpim_pidf_xml_provider.event_name, cpim_pidf_xml_provider.body_type);
-	}
-
-	return AST_MODULE_LOAD_SUCCESS;
-}
-
-static int unload_module(void)
-{
-	ast_sip_unregister_exten_state_provider(&cpim_pidf_xml_provider);
-	ast_sip_unregister_exten_state_provider(&xpidf_xml_provider);
-	ast_sip_unregister_exten_state_provider(&pidf_xml_provider);
-
-	return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Extension State PIDF Provider",
-		.load = load_module,
-		.unload = unload_module,
-		.load_pri = AST_MODPRI_CHANNEL_DEPEND,
-);
diff --git a/res/res_pjsip_pidf_body_generator.c b/res/res_pjsip_pidf_body_generator.c
new file mode 100644
index 0000000000000000000000000000000000000000..50e8be9e77ee20fd2b2db69c5707d50ee6743f43
--- /dev/null
+++ b/res/res_pjsip_pidf_body_generator.c
@@ -0,0 +1,135 @@
+/*
+ * asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2014, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_pjsip</depend>
+	<depend>res_pjsip_pubsub</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_simple.h>
+#include <pjlib.h>
+
+#include "asterisk/module.h"
+#include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_pubsub.h"
+#include "asterisk/res_pjsip_presence_xml.h"
+#include "asterisk/res_pjsip_body_generator_types.h"
+
+static void *pidf_allocate_body(void *data)
+{
+	struct ast_sip_exten_state_data *state_data = data;
+	pjpidf_pres *pres;
+	pj_str_t entity;
+
+	pres = pjpidf_create(state_data->pool, pj_cstr(&entity, state_data->local));
+
+	return pres;
+}
+
+static int pidf_generate_body_content(void *body, void *data)
+{
+	pjpidf_tuple *tuple;
+	pj_str_t note, id, contact, priority;
+	char *statestring = NULL, *pidfstate = NULL, *pidfnote = NULL;
+	enum ast_sip_pidf_state local_state;
+	char sanitized[PJSIP_MAX_URL_SIZE];
+	pjpidf_pres *pres = body;
+	struct ast_sip_exten_state_data *state_data = data;
+
+	ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring,
+			&pidfstate, &pidfnote, &local_state);
+
+	if (!pjpidf_pres_add_note(state_data->pool, pres, pj_cstr(&note, pidfnote))) {
+		ast_log(LOG_WARNING, "Unable to add note to PIDF presence\n");
+		return -1;
+	}
+
+	if (!(tuple = pjpidf_pres_add_tuple(state_data->pool, pres,
+					pj_cstr(&id, state_data->exten)))) {
+		ast_log(LOG_WARNING, "Unable to create PIDF tuple\n");
+		return -1;
+	}
+
+	ast_sip_sanitize_xml(state_data->remote, sanitized, sizeof(sanitized));
+	pjpidf_tuple_set_contact(state_data->pool, tuple, pj_cstr(&contact, sanitized));
+	pjpidf_tuple_set_contact_prio(state_data->pool, tuple, pj_cstr(&priority, "1"));
+	pjpidf_status_set_basic_open(pjpidf_tuple_get_status(tuple),
+			local_state == NOTIFY_OPEN);
+
+	return 0;
+}
+
+#define MAX_STRING_GROWTHS 3
+
+static void pidf_to_string(void *body, struct ast_str **str)
+{
+	int size;
+	int growths = 0;
+	pjpidf_pres *pres = body;
+
+	do {
+		size = pjpidf_print(pres, ast_str_buffer(*str), ast_str_size(*str) - 1);
+		if (size < 0) {
+			ast_str_make_space(str, ast_str_size(*str) * 2);
+			++growths;
+			return;
+		}
+	} while (size < 0 && growths < MAX_STRING_GROWTHS);
+
+	if (size < 0) {
+		ast_log(LOG_WARNING, "PIDF body text too large\n");
+		return;
+	}
+
+	*(ast_str_buffer(*str) + size) = '\0';
+	ast_str_update(*str);
+}
+
+static struct ast_sip_pubsub_body_generator pidf_body_generator = {
+	.type = "application",
+	.subtype = "pidf+xml",
+	.allocate_body = pidf_allocate_body,
+	.generate_body_content = pidf_generate_body_content,
+	.to_string = pidf_to_string,
+	/* No need for a destroy_body callback since we use a pool */
+};
+
+static int load_module(void)
+{
+	if (ast_sip_pubsub_register_body_generator(&pidf_body_generator)) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_sip_pubsub_unregister_body_generator(&pidf_body_generator);
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Extension State PIDF Provider",
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_CHANNEL_DEPEND,
+);
diff --git a/res/res_pjsip_pidf_eyebeam_body_supplement.c b/res/res_pjsip_pidf_eyebeam_body_supplement.c
new file mode 100644
index 0000000000000000000000000000000000000000..042cbf5e89ff76027604c431c70965d4fc736ef0
--- /dev/null
+++ b/res/res_pjsip_pidf_eyebeam_body_supplement.c
@@ -0,0 +1,113 @@
+/*
+ * asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2014, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_pjsip</depend>
+	<depend>res_pjsip_pubsub</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_simple.h>
+#include <pjlib.h>
+
+#include "asterisk/module.h"
+#include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_pubsub.h"
+#include "asterisk/res_pjsip_presence_xml.h"
+#include "asterisk/res_pjsip_body_generator_types.h"
+
+/*!
+ * \internal
+ * \brief Adds non standard elements to the xml body
+ *
+ * This is some code that was part of the original chan_sip implementation
+ * that is not part of the RFC 3863 definition, but we are keeping available
+ * for backward compatability. The original comment stated that Eyebeam
+ * supports this format.
+ */
+static void add_eyebeam(pj_pool_t *pool, pj_xml_node *node, const char *pidfstate)
+{
+	static const char *XMLNS_PP = "xmlns:pp";
+	static const char *XMLNS_PERSON = "urn:ietf:params:xml:ns:pidf:person";
+
+	static const char *XMLNS_ES = "xmlns:es";
+	static const char *XMLNS_RPID_STATUS = "urn:ietf:params:xml:ns:pidf:rpid:status:rpid-status";
+
+	static const char *XMLNS_EP = "xmlns:ep";
+	static const char *XMLNS_RPID_PERSON = "urn:ietf:params:xml:ns:pidf:rpid:rpid-person";
+
+	pj_xml_node *person = ast_sip_presence_xml_create_node(pool, node, "pp:person");
+	pj_xml_node *status = ast_sip_presence_xml_create_node(pool, person, "status");
+
+	if (pidfstate[0] != '-') {
+		pj_xml_node *activities = ast_sip_presence_xml_create_node(pool, status, "ep:activities");
+		size_t str_size = sizeof("ep:") + strlen(pidfstate);
+
+		activities->content.ptr = pj_pool_alloc(pool, str_size);
+		activities->content.slen = pj_ansi_snprintf(activities->content.ptr, str_size,
+				"ep:%s", pidfstate);
+	}
+
+	ast_sip_presence_xml_create_attr(pool, node, XMLNS_PP, XMLNS_PERSON);
+	ast_sip_presence_xml_create_attr(pool, node, XMLNS_ES, XMLNS_RPID_STATUS);
+	ast_sip_presence_xml_create_attr(pool, node, XMLNS_EP, XMLNS_RPID_PERSON);
+}
+
+static int pidf_supplement_body(void *body, void *data)
+{
+	pjpidf_pres *pres = body;
+	struct ast_sip_exten_state_data *state_data = data;
+	char *statestring = NULL, *pidfstate = NULL, *pidfnote = NULL;
+	enum ast_sip_pidf_state local_state;
+
+	ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring,
+			&pidfstate, &pidfnote, &local_state);
+
+	add_eyebeam(state_data->pool, pres, pidfstate);
+	return 0;
+}
+
+static struct ast_sip_pubsub_body_supplement pidf_supplement = {
+	.type = "application",
+	.subtype = "pidf+xml",
+	.supplement_body = pidf_supplement_body,
+};
+
+static int load_module(void)
+{
+	if (ast_sip_pubsub_register_body_supplement(&pidf_supplement)) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_sip_pubsub_unregister_body_supplement(&pidf_supplement);
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP PIDF Eyebeam supplement",
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_CHANNEL_DEPEND,
+);
diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index 5bc2cb46842cc616863c4b6338e52379f19fee71..f10bf41fc302443637da53105ec6f033acbaa747 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -81,6 +81,8 @@ static struct pjsip_module pubsub_module = {
 	.on_rx_request = pubsub_on_rx_request,
 };
 
+#define MOD_DATA_BODY_GENERATOR "sub_body_generator"
+
 static const pj_str_t str_event_name = { "Event", 5 };
 
 /*! \brief Scheduler used for automatically expiring publications */
@@ -195,6 +197,8 @@ struct ast_sip_subscription {
 	pjsip_evsub *evsub;
 	/*! The underlying PJSIP dialog */
 	pjsip_dialog *dlg;
+	/*! Body generaator for NOTIFYs */
+	struct ast_sip_pubsub_body_generator *body_generator;
 	/*! Next item in the list */
 	AST_LIST_ENTRY(ast_sip_subscription) next;
 };
@@ -206,6 +210,9 @@ static const char *sip_subscription_roles_map[] = {
 
 AST_RWLIST_HEAD_STATIC(subscriptions, ast_sip_subscription);
 
+AST_RWLIST_HEAD_STATIC(body_generators, ast_sip_pubsub_body_generator);
+AST_RWLIST_HEAD_STATIC(body_supplements, ast_sip_pubsub_body_supplement);
+
 static void add_subscription(struct ast_sip_subscription *obj)
 {
 	SCOPED_LOCK(lock, &subscriptions, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
@@ -402,6 +409,8 @@ struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_su
 		ao2_ref(sub, -1);
 		return NULL;
 	}
+	sub->body_generator = ast_sip_mod_data_get(rdata->endpt_info.mod_data,
+			pubsub_module.id, MOD_DATA_BODY_GENERATOR);
 	sub->role = role;
 	if (role == AST_SIP_NOTIFIER) {
 		dlg = ast_sip_create_dialog_uas(endpoint, rdata);
@@ -631,31 +640,35 @@ static void sub_add_handler(struct ast_sip_subscription_handler *handler)
 	ast_module_ref(ast_module_info->self);
 }
 
-static int sub_handler_exists_for_event_name(const char *event_name)
+static struct ast_sip_subscription_handler *find_sub_handler_for_event_name(const char *event_name)
 {
 	struct ast_sip_subscription_handler *iter;
 	SCOPED_LOCK(lock, &subscription_handlers, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
 
 	AST_RWLIST_TRAVERSE(&subscription_handlers, iter, next) {
 		if (!strcmp(iter->event_name, event_name)) {
-			return 1;
+			break;
 		}
 	}
-	return 0;
+	return iter;
 }
 
 int ast_sip_register_subscription_handler(struct ast_sip_subscription_handler *handler)
 {
+	pj_str_t event;
 	pj_str_t accept[AST_SIP_MAX_ACCEPT];
+	struct ast_sip_subscription_handler *existing;
 	int i;
 
 	if (ast_strlen_zero(handler->event_name)) {
-		ast_log(LOG_ERROR, "No event package specifief for subscription handler. Cannot register\n");
+		ast_log(LOG_ERROR, "No event package specified for subscription handler. Cannot register\n");
 		return -1;
 	}
 
-	if (ast_strlen_zero(handler->accept[0])) {
-		ast_log(LOG_ERROR, "Subscription handler must supply at least one 'Accept' format\n");
+	existing = find_sub_handler_for_event_name(handler->event_name);
+	if (existing) {
+		ast_log(LOG_ERROR, "Unable to register subscription handler for event %s."
+				"A handler is already registered\n", handler->event_name);
 		return -1;
 	}
 
@@ -663,19 +676,12 @@ int ast_sip_register_subscription_handler(struct ast_sip_subscription_handler *h
 		pj_cstr(&accept[i], handler->accept[i]);
 	}
 
-	if (!sub_handler_exists_for_event_name(handler->event_name)) {
-		pj_str_t event;
-
-		pj_cstr(&event, handler->event_name);
+	pj_cstr(&event, handler->event_name);
 
-		if (!strcmp(handler->event_name, "message-summary")) {
-			pjsip_mwi_init_module(ast_sip_get_pjsip_endpoint(), pjsip_evsub_instance());
-		} else {
-			pjsip_evsub_register_pkg(&pubsub_module, &event, DEFAULT_EXPIRES, i, accept);
-		}
+	if (!strcmp(handler->event_name, "message-summary")) {
+		pjsip_mwi_init_module(ast_sip_get_pjsip_endpoint(), pjsip_evsub_instance());
 	} else {
-		pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), &pubsub_module, PJSIP_H_ACCEPT, NULL,
-			i, accept);
+		pjsip_evsub_register_pkg(&pubsub_module, &event, DEFAULT_EXPIRES, i, accept);
 	}
 
 	sub_add_handler(handler);
@@ -696,48 +702,52 @@ void ast_sip_unregister_subscription_handler(struct ast_sip_subscription_handler
 	AST_RWLIST_TRAVERSE_SAFE_END;
 }
 
-static struct ast_sip_subscription_handler *find_sub_handler(const char *event, char accept[AST_SIP_MAX_ACCEPT][64], size_t num_accept)
+static struct ast_sip_pubsub_body_generator *find_body_generator_type_subtype(const char *content_type,
+		const char *content_subtype)
 {
-	struct ast_sip_subscription_handler *iter;
-	int match = 0;
-	SCOPED_LOCK(lock, &subscription_handlers, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
-	AST_RWLIST_TRAVERSE(&subscription_handlers, iter, next) {
-		int i;
-		int j;
-		if (strcmp(event, iter->event_name)) {
-			ast_debug(3, "Event %s does not match %s\n", event, iter->event_name);
-			continue;
-		}
-		ast_debug(3, "Event name match: %s = %s\n", event, iter->event_name);
-		if (!num_accept && iter->handles_default_accept) {
-			/* The SUBSCRIBE contained no Accept headers, and this subscription handler
-			 * provides the default body type, so it's a match!
-			 */
+	struct ast_sip_pubsub_body_generator *iter;
+	SCOPED_LOCK(lock, &body_generators, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
+
+	AST_LIST_TRAVERSE(&body_generators, iter, list) {
+		if (!strcmp(iter->type, content_type) &&
+				!strcmp(iter->subtype, content_subtype)) {
 			break;
 		}
-		for (i = 0; i < num_accept; ++i) {
-			for (j = 0; j < num_accept; ++j) {
-				if (ast_strlen_zero(iter->accept[i])) {
-					ast_debug(3, "Breaking because subscription handler has run out of 'accept' types\n");
-					break;
-				}
-				if (!strcmp(accept[j], iter->accept[i])) {
-					ast_debug(3, "Accept headers match: %s = %s\n", accept[j], iter->accept[i]);
-					match = 1;
-					break;
-				}
-				ast_debug(3, "Accept %s does not match %s\n", accept[j], iter->accept[i]);
-			}
-			if (match) {
-				break;
-			}
-		}
-		if (match) {
+	};
+
+	return iter;
+}
+
+static struct ast_sip_pubsub_body_generator *find_body_generator_accept(const char *accept)
+{
+	char *accept_copy = ast_strdupa(accept);
+	char *subtype = accept_copy;
+	char *type = strsep(&subtype, "/");
+
+	if (ast_strlen_zero(type) || ast_strlen_zero(subtype)) {
+		return NULL;
+	}
+
+	return find_body_generator_type_subtype(type, subtype);
+}
+
+static struct ast_sip_pubsub_body_generator *find_body_generator(char accept[AST_SIP_MAX_ACCEPT][64],
+		size_t num_accept)
+{
+	int i;
+	struct ast_sip_pubsub_body_generator *generator = NULL;
+
+	for (i = 0; i < num_accept; ++i) {
+		generator = find_body_generator_accept(accept[i]);
+		if (generator) {
+			ast_debug(3, "Body generator %p found for accept type %s\n", generator, accept[i]);
 			break;
+		} else {
+			ast_debug(3, "No body generator found for accept type %s\n", accept[i]);
 		}
 	}
 
-	return iter;
+	return generator;
 }
 
 static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
@@ -751,6 +761,7 @@ static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
 	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
 	struct ast_sip_subscription *sub;
 	size_t num_accept_headers;
+	struct ast_sip_pubsub_body_generator *generator;
 
 	endpoint = ast_pjsip_rdata_get_endpoint(rdata);
 	ast_assert(endpoint != NULL);
@@ -778,6 +789,13 @@ static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
 	}
 	ast_copy_pj_str(event, &event_header->event_type, sizeof(event));
 
+	handler = find_sub_handler_for_event_name(event);
+	if (!handler) {
+		ast_log(LOG_WARNING, "No registered subscribe handler for event %s\n", event);
+		pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 489, NULL, NULL, NULL);
+		return PJ_TRUE;
+	}
+
 	accept_header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, rdata->msg_info.msg->hdr.next);
 	if (accept_header) {
 		int i;
@@ -787,15 +805,22 @@ static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
 		}
 		num_accept_headers = accept_header->count;
 	} else {
-		num_accept_headers = 0;
+		/* If a SUBSCRIBE contains no Accept headers, then we must assume that
+		 * the default accept type for the event package is to be used.
+		 */
+		ast_copy_string(accept[0], handler->default_accept, sizeof(accept[0]));
+		num_accept_headers = 1;
 	}
 
-	handler = find_sub_handler(event, accept, num_accept_headers);
-	if (!handler) {
-		ast_log(LOG_WARNING, "No registered subscribe handler for event %s\n", event);
+	generator = find_body_generator(accept, num_accept_headers);
+	if (!generator) {
 		pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 489, NULL, NULL, NULL);
 		return PJ_TRUE;
 	}
+
+	ast_sip_mod_data_set(rdata->tp_info.pool, rdata->endpt_info.mod_data,
+			pubsub_module.id, MOD_DATA_BODY_GENERATOR, generator);
+
 	sub = handler->new_subscribe(endpoint, rdata);
 	if (!sub) {
 		pjsip_transaction *trans = pjsip_rdata_get_tsx(rdata);
@@ -1063,6 +1088,137 @@ pj_status_t ast_sip_publication_send_response(struct ast_sip_publication *pub, p
 	return pjsip_tsx_send_msg(tsx, tdata);
 }
 
+int ast_sip_pubsub_register_body_generator(struct ast_sip_pubsub_body_generator *generator)
+{
+	struct ast_sip_pubsub_body_generator *existing;
+	pj_str_t accept;
+	pj_size_t accept_len;
+
+	existing = find_body_generator_type_subtype(generator->type, generator->subtype);
+	if (existing) {
+		ast_log(LOG_WARNING, "Cannot register body generator of %s/%s."
+				"One is already registered.\n", generator->type, generator->subtype);
+		return -1;
+	}
+
+	AST_RWLIST_WRLOCK(&body_generators);
+	AST_LIST_INSERT_HEAD(&body_generators, generator, list);
+	AST_RWLIST_UNLOCK(&body_generators);
+
+	/* Lengths of type and subtype plus space for a slash. pj_str_t is not
+	 * null-terminated, so there is no need to allocate for the extra null
+	 * byte
+	 */
+	accept_len = strlen(generator->type) + strlen(generator->subtype) + 1;
+
+	accept.ptr = alloca(accept_len);
+	accept.slen = accept_len;
+	/* Safe use of sprintf */
+	sprintf(accept.ptr, "%s/%s", generator->type, generator->subtype);
+	pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), &pubsub_module,
+			PJSIP_H_ACCEPT, NULL, 1, &accept);
+
+	return 0;
+}
+
+void ast_sip_pubsub_unregister_body_generator(struct ast_sip_pubsub_body_generator *generator)
+{
+	struct ast_sip_pubsub_body_generator *iter;
+	SCOPED_LOCK(lock, &body_generators, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&body_generators, iter, list) {
+		if (iter == generator) {
+			AST_LIST_REMOVE_CURRENT(list);
+			break;
+		}
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+}
+
+int ast_sip_pubsub_register_body_supplement(struct ast_sip_pubsub_body_supplement *supplement)
+{
+	AST_RWLIST_WRLOCK(&body_supplements);
+	AST_RWLIST_INSERT_TAIL(&body_supplements, supplement, list);
+	AST_RWLIST_UNLOCK(&body_supplements);
+
+	return 0;
+}
+
+void ast_sip_pubsub_unregister_body_supplement(struct ast_sip_pubsub_body_supplement *supplement)
+{
+	struct ast_sip_pubsub_body_supplement *iter;
+	SCOPED_LOCK(lock, &body_supplements, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&body_supplements, iter, list) {
+		if (iter == supplement) {
+			AST_LIST_REMOVE_CURRENT(list);
+			break;
+		}
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+}
+
+const char *ast_sip_subscription_get_body_type(struct ast_sip_subscription *sub)
+{
+	return sub->body_generator->type;
+}
+
+const char *ast_sip_subscription_get_body_subtype(struct ast_sip_subscription *sub)
+{
+	return sub->body_generator->subtype;
+}
+
+int ast_sip_pubsub_generate_body_content(const char *type, const char *subtype,
+		void *data, struct ast_str **str)
+{
+	struct ast_sip_pubsub_body_supplement *supplement;
+	struct ast_sip_pubsub_body_generator *generator;
+	int res;
+	void *body;
+
+	generator = find_body_generator_type_subtype(type, subtype);
+	if (!generator) {
+		ast_log(LOG_WARNING, "Unable to find a body generator for %s/%s\n",
+				type, subtype);
+		return -1;
+	}
+
+	body = generator->allocate_body(data);
+	if (!body) {
+		ast_log(LOG_WARNING, "Unable to allocate a NOTIFY body of type %s/%s\n",
+				type, subtype);
+		return -1;
+	}
+
+	if (generator->generate_body_content(body, data)) {
+		res = -1;
+		goto end;
+	}
+
+	AST_RWLIST_RDLOCK(&body_supplements);
+	AST_RWLIST_TRAVERSE(&body_supplements, supplement, list) {
+		if (!strcmp(generator->type, supplement->type) &&
+				!strcmp(generator->subtype, supplement->subtype)) {
+			res = supplement->supplement_body(body, data);
+			if (res) {
+				break;
+			}
+		}
+	}
+	AST_RWLIST_UNLOCK(&body_supplements);
+
+	if (!res) {
+		generator->to_string(body, str);
+	}
+
+end:
+	if (generator->destroy_body) {
+		generator->destroy_body(body);
+	}
+
+	return res;
+}
+
 static pj_bool_t pubsub_on_rx_request(pjsip_rx_data *rdata)
 {
 	if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_subscribe_method())) {
diff --git a/res/res_pjsip_pubsub.exports.in b/res/res_pjsip_pubsub.exports.in
index 15838f2dc88531c59129105ea2d5627bd546fae3..f279b5433702bdbeaa3c592e025275f9fbd931e8 100644
--- a/res/res_pjsip_pubsub.exports.in
+++ b/res/res_pjsip_pubsub.exports.in
@@ -21,6 +21,14 @@
 		LINKER_SYMBOL_PREFIXast_sip_publication_add_datastore;
 		LINKER_SYMBOL_PREFIXast_sip_publication_get_datastore;
 		LINKER_SYMBOL_PREFIXast_sip_publication_remove_datastore;
+		LINKER_SYMBOL_PREFIXast_sip_publication_remove_datastore;
+		LINKER_SYMBOL_PREFIXast_sip_pubsub_register_body_generator;
+		LINKER_SYMBOL_PREFIXast_sip_pubsub_unregister_body_generator;
+		LINKER_SYMBOL_PREFIXast_sip_pubsub_register_body_supplement;
+		LINKER_SYMBOL_PREFIXast_sip_pubsub_unregister_body_supplement;
+		LINKER_SYMBOL_PREFIXast_sip_pubsub_generate_body_content;
+		LINKER_SYMBOL_PREFIXast_sip_subscription_get_body_type;
+		LINKER_SYMBOL_PREFIXast_sip_subscription_get_body_subtype;
 	local:
 		*;
 };
diff --git a/res/res_pjsip_xpidf_body_generator.c b/res/res_pjsip_xpidf_body_generator.c
new file mode 100644
index 0000000000000000000000000000000000000000..b68c9bba6c845bdd4fc487ac01fe4ea8241ceec2
--- /dev/null
+++ b/res/res_pjsip_xpidf_body_generator.c
@@ -0,0 +1,177 @@
+/*
+ * asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2014, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_pjsip</depend>
+	<depend>res_pjsip_pubsub</depend>
+	<depend>res_pjsip_exten_state</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_simple.h>
+#include <pjlib.h>
+
+#include "asterisk/module.h"
+#include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_pubsub.h"
+#include "asterisk/res_pjsip_presence_xml.h"
+#include "asterisk/res_pjsip_body_generator_types.h"
+
+static void *xpidf_allocate_body(void *data)
+{
+	struct ast_sip_exten_state_data *state_data = data;
+	pjxpidf_pres *pres;
+	pj_str_t name;
+
+	pres = pjxpidf_create(state_data->pool, pj_cstr(&name, state_data->local));
+	return pres;
+}
+
+static int xpidf_generate_body_content(void *body, void *data)
+{
+	pjxpidf_pres *pres = body;
+	struct ast_sip_exten_state_data *state_data = data;
+	static pj_str_t STR_ADDR_PARAM = { ";user=ip", 8 };
+	char *statestring = NULL, *pidfstate = NULL, *pidfnote = NULL;
+	pj_xml_attr *attr;
+	enum ast_sip_pidf_state local_state;
+	pj_str_t uri;
+	char sanitized[PJSIP_MAX_URL_SIZE];
+	pj_xml_node *atom;
+	pj_xml_node *address;
+	pj_xml_node *status;
+	pj_xml_node *msnsubstatus;
+
+	ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring,
+			&pidfstate, &pidfnote, &local_state);
+
+	ast_sip_presence_xml_find_node_attr(state_data->pool, pres, "atom", "id",
+			&atom, &attr);
+	pj_strdup2(state_data->pool, &attr->value, state_data->exten);
+
+	ast_sip_presence_xml_find_node_attr(state_data->pool, atom, "address",
+			"uri", &address, &attr);
+
+	ast_sip_sanitize_xml(state_data->remote, sanitized, sizeof(sanitized));
+
+	uri.ptr = (char*) pj_pool_alloc(state_data->pool,
+			strlen(sanitized) + STR_ADDR_PARAM.slen);
+	pj_strcpy2( &uri, sanitized);
+	pj_strcat( &uri, &STR_ADDR_PARAM);
+	pj_strdup(state_data->pool, &attr->value, &uri);
+
+	ast_sip_presence_xml_create_attr(state_data->pool, address, "priority", "0.80000");
+
+	ast_sip_presence_xml_find_node_attr(state_data->pool, address,
+			"status", "status", &status, &attr);
+	pj_strdup2(state_data->pool, &attr->value,
+		   (local_state ==  NOTIFY_OPEN) ? "open" :
+		   (local_state == NOTIFY_INUSE) ? "inuse" : "closed");
+
+	ast_sip_presence_xml_find_node_attr(state_data->pool, address,
+			"msnsubstatus", "substatus", &msnsubstatus, &attr);
+	pj_strdup2(state_data->pool, &attr->value,
+		   (local_state == NOTIFY_OPEN) ? "online" :
+		   (local_state == NOTIFY_INUSE) ? "onthephone" : "offline");
+
+	return 0;
+}
+
+#define MAX_STRING_GROWTHS 3
+
+static void xpidf_to_string(void *body, struct ast_str **str)
+{
+	pjxpidf_pres *pres = body;
+	int growths = 0;
+	int size;
+
+	do {
+
+		size = pjxpidf_print(pres, ast_str_buffer(*str), ast_str_size(*str));
+		if (size < 0) {
+			ast_str_make_space(str, ast_str_size(*str) * 2);
+			++growths;
+			return;
+		}
+	} while (size < 0 && growths < MAX_STRING_GROWTHS);
+
+	if (size < 0) {
+		ast_log(LOG_WARNING, "XPIDF body text too large\n");
+		return;
+	}
+
+	*(ast_str_buffer(*str) + size) = '\0';
+	ast_str_update(*str);
+}
+
+static struct ast_sip_pubsub_body_generator xpidf_body_generator = {
+	.type = "application",
+	.subtype = "xpidf+xml",
+	.allocate_body = xpidf_allocate_body,
+	.generate_body_content = xpidf_generate_body_content,
+	.to_string = xpidf_to_string,
+	/* No need for a destroy_body callback since we use a pool */
+};
+
+static struct ast_sip_pubsub_body_generator cpim_pidf_body_generator = {
+	.type = "application",
+	.subtype = "cpim-pidf+xml",
+	.allocate_body = xpidf_allocate_body,
+	.generate_body_content = xpidf_generate_body_content,
+	.to_string = xpidf_to_string,
+	/* No need for a destroy_body callback since we use a pool */
+};
+
+static void unregister_all(void)
+{
+	ast_sip_pubsub_unregister_body_generator(&cpim_pidf_body_generator);
+	ast_sip_pubsub_unregister_body_generator(&xpidf_body_generator);
+}
+
+static int load_module(void)
+{
+	if (ast_sip_pubsub_register_body_generator(&xpidf_body_generator)) {
+		goto fail;
+	}
+
+	if (ast_sip_pubsub_register_body_generator(&cpim_pidf_body_generator)) {
+		goto fail;
+	}
+
+	return AST_MODULE_LOAD_SUCCESS;
+
+fail:
+	unregister_all();
+	return AST_MODULE_LOAD_DECLINE;
+}
+
+static int unload_module(void)
+{
+	unregister_all();
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Extension State PIDF Provider",
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_CHANNEL_DEPEND,
+);