diff --git a/channels/pjsip/dialplan_functions_doc.xml b/channels/pjsip/dialplan_functions_doc.xml
index 3d19d929ba0f7203e1f442eb05f264456d5b7dde..ed077bc447c8f1cf38ddffcb6cc03a0d87d16c28 100644
--- a/channels/pjsip/dialplan_functions_doc.xml
+++ b/channels/pjsip/dialplan_functions_doc.xml
@@ -55,6 +55,46 @@
 		</description>
 	</application>
 
+	<application name="PJSIPNotify" language="en_US">
+		<synopsis>
+			Send a NOTIFY to either an arbitrary URI, or inside a SIP dialog.
+		</synopsis>
+		<syntax>
+			<parameter name="to" required="false">
+				<para>Abritrary URI to which to send the NOTIFY.  If none is specified, send inside
+				the SIP dialog for the current channel.</para>
+			</parameter>
+			<parameter name="content" required="true">
+				<para>Either an option pre-configured in pjsip_notify.conf or a list of headers and body content to send in the NOTIFY.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>
+			Sends a NOTIFY to a specified URI, or if none provided, within the current SIP dialog for the
+			current channel.  The content can either be set to either an entry configured in pjsip_notify.conf
+			or specified as a list of key value pairs.
+			</para>
+			<warning><para>
+			To send a NOTIFY to a specified URI, a default_outbound_endpoint must be configured.  This
+			endpoint determines the message contact.
+			</para></warning>
+			<para>
+			</para>
+			<example title="Send a NOTIFY with Event and X-Data headers in current dialog">
+			same = n,PJSIPNotify(,&amp;Event=Test&amp;X-Data=Fun)
+			</example>
+			<example title="Send a preconfigured NOTIFY force-answer defined in pjsip_notify.conf in current dialog">
+			same = n,PJSIPNotify(,force-answer)
+			</example>
+			<example title="Send a NOTIFY to &lt;sip:bob@127.0.0.1:5260&gt; with Test Event and X-Data headers">
+			same = n,PJSIPNotify(&lt;sip:bob@127.0.0.1:5260&gt;,&amp;Event=Test&amp;X-Data=Fun)
+			</example>
+			<example title="Send a NOTIFY to &lt;sip:bob@127.0.0.1:5260&gt; with Custom Event and message body">
+			same = n,PJSIPNotify(&lt;sip:bob@127.0.0.1:5260&gt;,&amp;Event=Custom&amp;Content-type=application&#47;voicemail&amp;Content=check-messages&amp;Content=)
+			</example>
+		</description>
+	</application>
+
 	<manager name="PJSIPHangup" language="en_US">
 		<synopsis>
 			Hangup an incoming PJSIP channel with a SIP response code
diff --git a/configs/samples/extensions.conf.sample b/configs/samples/extensions.conf.sample
index 494738ebf691a3256657cd65d1a39f4f83642210..d3f5eba8991227aee70834e1c4791ca3f5af8309 100644
--- a/configs/samples/extensions.conf.sample
+++ b/configs/samples/extensions.conf.sample
@@ -828,3 +828,53 @@ exten => _X.,40000(ani),NoOp(ANI: ${EXTEN})
 ; "core show functions" will list all dialplan functions
 ; "core show function <COMMAND>" will show you more information about
 ; one function. Remember that function names are UPPER CASE.
+
+; Examples using PJSIPNotify application.
+;[generate-notify]
+;
+; Send a NOTIFY with the following headers inside the SIP dialog for the current channel:
+;
+;	Event: Test
+;	X-Data: Fun
+;
+;exten => 6880,1,noOp()
+;    same => n,Answer()
+;    same => n,PJSIPNotify(,&Event=Test&X-Data=Fun)
+;    same => n,Wait(1)
+;    same => n,Hangup()
+;
+; Send a NOTIFY with the following headers to bob's custom uri. This requries a
+; default outbound endpoint to be configured in pjsip.conf.
+;
+;	Event: Test
+;	X-Data: Over
+;
+;exten => 6881,1,noOp()
+;    same => n,Answer()
+;    same => n,PJSIPNotify(<sip:bob@127.0.0.1:5260>,&Event=Test&X-Data=Over)
+;    same => n,Wait(1)
+;    same => n,Hangup()
+;
+; Send a NOTIFY with the the custom headers defined in pjsip_notify.conf under
+; 'custom-notify-1' inside the SIP dialog for the current channel.
+;
+;exten => 6882,1,noOp()
+;    same => n,Answer()
+;    same => n,PJSIPNotify(,custom-notify-1)
+;    same => n,Wait(1)
+;    same => n,Hangup()
+; Send a NOTIFY with the following headers and body to bob's custom uri. This
+; requries a default outbound endpoint to be configured in pjsip.conf.
+;
+;	Event: Custom
+;
+;	Content-Type: application/voicemail
+;	Content-Length:    14
+;
+;	check-messages
+;
+;exten => 6882,1,noOp()
+;    same => n,Answer()
+;    same => n,PJSIPNotify(,&Event=Custom&Content-type=application/voicemail&Content=check-messages&Content=)
+;    same => n,Wait(1)
+;    same => n,Hangup()
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index 61c8846b8f6553098f8a3c32774273fa26f3441f..a808694045e853433b4dca02f51cb3a24ae2ed20 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -432,6 +432,16 @@
 ;type=aor
 ;max_contacts=2
 
+;===============DEFAULT ENDPOINT FOR OUTBOUND REQUESTS TO URI===================
+;
+; This is an example default outbound endpoint.  The global setting:
+; default_outbound_endpoint needs to be set to such an endpoint in order to be
+; able to send an outbound request to a URI without a specified endpoint.
+;
+;[default_outbound_endpoint]
+;type=endpoint
+;context=none-invalid
+
 
 ;============EXAMPLE ACL CONFIGURATION==========================================
 ;
diff --git a/configs/samples/pjsip_notify.conf.sample b/configs/samples/pjsip_notify.conf.sample
index 8224ee1ff4c6ed10e26f6bbd310e8023abb442e1..46bb5a11a041ad8adc0fd2c8ad8388e0dcc52eb2 100644
--- a/configs/samples/pjsip_notify.conf.sample
+++ b/configs/samples/pjsip_notify.conf.sample
@@ -55,3 +55,17 @@ Event=>check-sync\;reboot=true
 
 [cisco-check-cfg]
 Event=>check-sync
+
+; custom examples to use for PJSIPNotify application
+
+; tell an endpoint to check messages
+[custom-notify-1]
+Event=>custom
+Content-type=>application/voicemail
+Content=>check-messages
+Content=>
+
+; tell an endpoint to force a remote hangup via custom header
+[custom-notify-2]
+Event=>custom
+X-Data=>force-hangup
\ No newline at end of file
diff --git a/res/res_pjsip_notify.c b/res/res_pjsip_notify.c
index 3d88c18a882c3868f3b91a162f10638a6b496fbc..1acd3144d4078e372ef2547bcc1fb6d9dd418681 100644
--- a/res/res_pjsip_notify.c
+++ b/res/res_pjsip_notify.c
@@ -27,6 +27,7 @@
 #include <pjsip.h>
 #include <pjsip_ua.h>
 
+#include "asterisk/app.h"
 #include "asterisk/cli.h"
 #include "asterisk/config.h"
 #include "asterisk/manager.h"
@@ -317,6 +318,14 @@ static void notify_cli_uri_data_destroy(void *obj)
 	ao2_cleanup(data->info);
 }
 
+static void notify_cli_channel_data_destroy(void *obj)
+{
+	struct notify_channel_data *data = obj;
+
+	ao2_cleanup(data->info);
+	ao2_cleanup(data->session);
+}
+
 /*!
  * \internal
  * \brief Destroy the notify CLI data releasing any resources (URI variant)
@@ -375,6 +384,29 @@ static struct notify_uri_data* notify_cli_uri_data_create(
 	return data;
 }
 
+/*!
+ * \internal
+ * \brief Construct a notify URI data object for CLI.
+ */
+static struct notify_channel_data* notify_cli_channel_data_create(
+	struct ast_sip_session *session, void *info)
+{
+	struct notify_channel_data *data = ao2_alloc_options(sizeof(*data),
+		notify_cli_channel_data_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+
+	if (!data) {
+		return NULL;
+	}
+
+	data->session = session;
+	data->info = info;
+	ao2_ref(data->info, +1);
+
+	data->build_notify = build_cli_notify;
+
+	return data;
+}
+
 /*!
  * \internal
  * \brief Destroy the notify AMI data releasing any resources.
@@ -945,14 +977,13 @@ static char *cli_complete_endpoint(const char *word)
  * \internal
  * \brief Do completion on the notify CLI command.
  */
-static char *cli_complete_notify(const char *line, const char *word,
-				 int pos, int state, int using_uri)
+static char *cli_complete_notify(struct ast_cli_args *a)
 {
 	char *c = NULL;
 
-	if (pos == 3) {
+	if (a->pos == 3) {
 		int which = 0;
-		int wordlen = strlen(word);
+		int wordlen = strlen(a->word);
 
 		RAII_VAR(struct notify_cfg *, cfg,
 			 ao2_global_obj_ref(globals), ao2_cleanup);
@@ -961,7 +992,7 @@ static char *cli_complete_notify(const char *line, const char *word,
 		/* do completion for notify type */
 		struct ao2_iterator i = ao2_iterator_init(cfg->notify_options, 0);
 		while ((option = ao2_iterator_next(&i))) {
-			if (!strncasecmp(word, option->name, wordlen) && ++which > state) {
+			if (!strncasecmp(a->word, option->name, wordlen) && ++which > a->n) {
 				c = ast_strdup(option->name);
 			}
 
@@ -974,27 +1005,38 @@ static char *cli_complete_notify(const char *line, const char *word,
 		return c;
 	}
 
-	if (pos == 4) {
-		int wordlen = strlen(word);
+	if (a->pos == 4) {
+		int wordlen = strlen(a->word);
 
-		if (ast_strlen_zero(word)) {
-		    if (state == 0) {
+		if (ast_strlen_zero(a->word)) {
+		    if (a->n == 0) {
 		        c = ast_strdup("endpoint");
-		    } else if (state == 1) {
+		    } else if (a->n == 1) {
 		        c = ast_strdup("uri");
+		    } else if (a->n == 2) {
+				c = ast_strdup("channel");
 		    }
-		} else if (state == 0) {
-		    if (!strncasecmp(word, "endpoint", wordlen)) {
+		} else if (a->n == 0) {
+		    if (!strncasecmp(a->word, "endpoint", wordlen)) {
 		        c = ast_strdup("endpoint");
-		    } else if (!strncasecmp(word, "uri", wordlen)) {
+		    } else if (!strncasecmp(a->word, "uri", wordlen)) {
 		        c = ast_strdup("uri");
+		    } else if (!strncasecmp(a->word, "channel", wordlen)) {
+		        c = ast_strdup("channel");
 		    }
 		}
 
 		return c;
 	}
 
-	return pos > 4 && !using_uri ? cli_complete_endpoint(word) : NULL;
+	if (a->pos > 4) {
+		if (!strcasecmp(a->argv[4], "endpoint")) {
+			return cli_complete_endpoint(a->word);
+		} else if (!strcasecmp(a->argv[4], "channel")) {
+			return ast_complete_channels(a->line, a->word, a->pos, a->n, 5);
+		}
+	}
+	return NULL;
 }
 
 /*!
@@ -1012,21 +1054,18 @@ static char *cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a
 
 	int i;
 	int using_uri = 0;
+	int using_channel = 0;
 
 	switch (cmd) {
 	case CLI_INIT:
 		e->command = "pjsip send notify";
 		e->usage =
-			"Usage: pjsip send notify <type> {endpoint|uri} <peer> [<peer>...]\n"
+			"Usage: pjsip send notify <type> {endpoint|uri|channel} <peer> [<peer>...]\n"
 			"       Send a NOTIFY request to an endpoint\n"
 			"       Message types are defined in pjsip_notify.conf\n";
 		return NULL;
 	case CLI_GENERATE:
-		if (a->argc > 4 && (!strcasecmp(a->argv[4], "uri"))) {
-			using_uri = 1;
-		}
-
-		return cli_complete_notify(a->line, a->word, a->pos, a->n, using_uri);
+		return cli_complete_notify(a);
 	}
 
 	if (a->argc < 6) {
@@ -1035,6 +1074,8 @@ static char *cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a
 
 	if (!strcasecmp(a->argv[4], "uri")) {
 		using_uri = 1;
+	} else if (!strcasecmp(a->argv[4], "channel")) {
+		using_channel = 1;
 	} else if (strcasecmp(a->argv[4], "endpoint")) {
 		return CLI_SHOWUSAGE;
 	}
@@ -1049,14 +1090,23 @@ static char *cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a
 	}
 
 	for (i = 5; i < a->argc; ++i) {
+		enum notify_result result;
 		ast_cli(a->fd, "Sending NOTIFY of type '%s' to '%s'\n",
 			a->argv[3], a->argv[i]);
 
-		switch (using_uri ? push_notify_uri(a->argv[i], option, notify_cli_uri_data_create) :
-		                    push_notify(a->argv[i], option, notify_cli_data_create)) {
+		if (using_uri) {
+			result = push_notify_uri(a->argv[i], option, notify_cli_uri_data_create);
+		} else if (using_channel) {
+			result = push_notify_channel(a->argv[i], option, notify_cli_channel_data_create);
+		} else {
+			result = push_notify(a->argv[i], option, notify_cli_data_create);
+		}
+		switch(result) {
 		case INVALID_ENDPOINT:
-			ast_cli(a->fd, "Unable to retrieve endpoint %s\n",
-				a->argv[i]);
+			ast_cli(a->fd, "Unable to retrieve endpoint %s\n", a->argv[i]);
+			break;
+		case INVALID_CHANNEL:
+			ast_cli(a->fd, "Unable to find channel %s\n", a->argv[i]);
 			break;
 		case ALLOC_ERROR:
 			ast_cli(a->fd, "Unable to allocate NOTIFY task data\n");
@@ -1246,6 +1296,97 @@ static int manager_notify(struct mansession *s, const struct message *m)
 	return 0;
 }
 
+/*! \brief Convert headers string such as "Event=hold&Event=answer&..." into ast variable list*/
+/* Caller has to call ast_variables_destroy() to free the list*/
+static struct ast_variable *headers_to_variables(const char *headers)
+{
+	struct ast_variable *varlist = NULL;
+	struct ast_variable *var;
+	char *cur;
+	char *header;
+
+	cur = (char *)headers;
+
+	while( (header = strsep(&cur, "&")) ) {
+		char *name;
+		char *value;
+
+		name = value = header;
+		strsep(&value, "=");
+
+		if (!value || ast_strlen_zero(name)) {
+			continue;
+		}
+
+		var = ast_variable_new(name, value, "");
+		var->next = varlist;
+		varlist = var;
+	}
+	return varlist;
+}
+
+/*! \brief Application entry point to send a SIP notify to an endpoint. */
+static int app_notify(struct ast_channel *chan, const char *data)
+{
+	RAII_VAR(struct notify_cfg *, cfg, NULL, ao2_cleanup);
+	RAII_VAR(struct notify_option *, option, NULL, ao2_cleanup);
+
+	struct ast_variable *varlist = NULL;
+	char *tmp;
+	int res;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(to);
+		AST_APP_ARG(headers);
+	);
+
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "PJSIPNotify requires arguments (to, &header=...)\n");
+		return -1;
+	}
+
+	tmp = ast_strdupa(data);
+	AST_STANDARD_APP_ARGS(args, tmp);
+	cfg = ao2_global_obj_ref(globals);
+
+	if (!(option = notify_option_find(cfg->notify_options, args.headers))) {
+		/* If the app is passed a list of headers, use the notify_ami_*_data_create
+		   functions as the option data is handled the same way as the ami command. */
+		varlist = headers_to_variables(args.headers);
+		if (ast_strlen_zero(args.to)) {
+			res = push_notify_channel(ast_channel_name(chan), varlist, notify_ami_channel_data_create);
+		} else {
+			res = push_notify_uri(args.to, varlist, notify_ami_uri_data_create);
+		}
+	} else {
+		/* If the app is passed a configured notify option, use the notify_cli_*_data_create
+		   functions as the option data is handled the same way as the cli command. */
+		if (ast_strlen_zero(args.to)) {
+			res = push_notify_channel(ast_channel_name(chan), option, notify_cli_channel_data_create);
+		} else {
+			res = push_notify_uri(args.to, option, notify_cli_uri_data_create);
+		}
+	}
+
+	switch (res) {
+		case INVALID_CHANNEL:
+		case INVALID_ENDPOINT:
+		case ALLOC_ERROR:
+			res = -1;
+			ast_variables_destroy(varlist);
+			break;
+		case TASK_PUSH_ERROR:
+			/* Don't need to destroy vars since it is handled by cleanup in push_notify_channel */
+			res = -1;
+			break;
+		case SUCCESS:
+			res = 0;
+			break;
+	}
+
+	return res;
+}
+
 static int load_module(void)
 {
 	if (aco_info_init(&notify_cfg)) {
@@ -1262,6 +1403,7 @@ static int load_module(void)
 
 	ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));
 	ast_manager_register_xml("PJSIPNotify", EVENT_FLAG_SYSTEM, manager_notify);
+	ast_register_application_xml("PJSIPNotify", app_notify);
 
 	return AST_MODULE_LOAD_SUCCESS;
 }
@@ -1278,6 +1420,7 @@ static int reload_module(void)
 static int unload_module(void)
 {
 	ast_manager_unregister("PJSIPNotify");
+	ast_unregister_application("PJSIPNotify");
 	ast_cli_unregister_multiple(cli_options, ARRAY_LEN(cli_options));
 	aco_info_destroy(&notify_cfg);
 	ao2_global_obj_release(globals);