diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h
index 65e403fb49936994faaadf88fdfdf8095fdf8667..684452fee0c7be4ba0beb40ee1b6c0efcc9ed69f 100644
--- a/include/asterisk/_private.h
+++ b/include/asterisk/_private.h
@@ -57,6 +57,7 @@ int ast_msg_init(void);             /*!< Provided by message.c */
 void ast_msg_shutdown(void);        /*!< Provided by message.c */
 int aco_init(void);             /*!< Provided by config_options.c */
 int dns_core_init(void);        /*!< Provided by dns_core.c */
+int ast_refer_init(void);             /*!< Provided by refer.c */
 
 /*!
  * \brief Initialize malloc debug phase 1.
diff --git a/include/asterisk/refer.h b/include/asterisk/refer.h
new file mode 100644
index 0000000000000000000000000000000000000000..4d0744c876143a1c04624cab94856c515202307c
--- /dev/null
+++ b/include/asterisk/refer.h
@@ -0,0 +1,325 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2023, Commend International
+ *
+ * Maximilian Fridrich <m.fridrich@commend.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.
+ */
+
+/*!
+ * \file
+ *
+ * \brief Out-of-call refer support
+ *
+ * \author Maximilian Fridrich <m.fridrich@commend.com>
+ *
+ * The purpose of this API is to provide support for refers that
+ * are not session based. The refers are passed into the Asterisk core
+ * to be routed through the dialplan or another interface and potentially
+ * sent back out through a refer technology that has been registered
+ * through this API.
+ */
+
+#ifndef __AST_REFER_H__
+#define __AST_REFER_H__
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+/*!
+ * \brief A refer structure.
+ *
+ * This is an opaque type that represents a refer.
+ */
+struct ast_refer;
+
+/*!
+ * \brief A refer technology
+ *
+ * A refer technology is capable of transmitting text refers.
+ */
+struct ast_refer_tech {
+	/*!
+	 * \brief Name of this refer technology
+	 *
+	 * This is the name that comes at the beginning of a URI for refers
+	 * that should be sent to this refer technology implementation.
+	 * For example, refers sent to "pjsip:m.fridrich@commend.com" would be
+	 * passed to the ast_refer_tech with a name of "pjsip".
+	 */
+	const char * const name;
+	/*!
+	 * \brief Send a refer.
+	 *
+	 * \param refer The refer to send
+	 *
+	 * The fields of the ast_refer are guaranteed not to change during the
+	 * duration of this function call.
+	 *
+	 * \retval 0 success
+	 * \retval non-zero failure
+	 */
+	int (* const refer_send)(const struct ast_refer *refer);
+};
+
+/*!
+ * \brief Register a refer technology
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_refer_tech_register(const struct ast_refer_tech *tech);
+
+/*!
+ * \brief Unregister a refer technology.
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_refer_tech_unregister(const struct ast_refer_tech *tech);
+
+/*!
+ * \brief Allocate a refer.
+ *
+ * Allocate a refer for the purposes of passing it into the Asterisk core
+ * to be routed through the dialplan. This refer must be destroyed using
+ * ast_refer_destroy().
+ *
+ * \return A refer object. This function will return NULL if an allocation
+ *         error occurs.
+ */
+struct ast_refer *ast_refer_alloc(void);
+
+/*!
+ * \brief Destroy an ast_refer
+ *
+ * \retval NULL always.
+ */
+struct ast_refer *ast_refer_destroy(struct ast_refer *refer);
+
+/*!
+ * \brief Bump a refer's ref count
+ */
+struct ast_refer *ast_refer_ref(struct ast_refer *refer);
+
+/*!
+ * \brief Set the 'to' URI of a refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+		ast_refer_set_to(struct ast_refer *refer, const char *fmt, ...);
+
+/*!
+ * \brief Set the 'from' URI of a refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+		ast_refer_set_from(struct ast_refer *refer, const char *fmt, ...);
+
+/*!
+ * \brief Set the 'refer_to' URI of a refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+		ast_refer_set_refer_to(struct ast_refer *refer, const char *fmt, ...);
+
+/*!
+ * \brief Set the 'to_self' value of a refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_refer_set_to_self(struct ast_refer *refer, int val);
+
+/*!
+ * \brief Set the technology associated with this refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+		ast_refer_set_tech(struct ast_refer *refer, const char *fmt, ...);
+
+/*!
+ * \brief Set the technology's endpoint associated with this refer
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int __attribute__((format(printf, 2, 3)))
+		ast_refer_set_endpoint(struct ast_refer *refer, const char *fmt, ...);
+
+/*!
+ * \brief Set a variable on the refer being sent to a refer tech directly.
+ * \note Setting a variable that already exists overwrites the existing variable value
+ *
+ * \param refer
+ * \param name Name of variable to set
+ * \param value Value of variable to set
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_refer_set_var_outbound(struct ast_refer *refer, const char *name, const char *value);
+
+/*!
+ * \brief Get the specified variable on the refer and unlink it from the container of variables
+ * \note The return value must be freed by the caller.
+ *
+ * \param refer
+ * \param name Name of variable to get
+ *
+ * \return The value associated with variable "name". NULL if variable not found.
+ */
+char *ast_refer_get_var_and_unlink(struct ast_refer *refer, const char *name);
+
+/*!
+ * \brief Get the specified variable on the refer
+ * \note The return value is valid only as long as the ast_refer is valid. Hold a reference
+ *       to the refer if you plan on storing the return value. It is possible to re-set the
+ *       same refer var name (with ast_refer_set_var_outbound passing the variable name)
+ *       while holding a pointer to the result of this function.
+ *
+ * \param refer
+ * \param name Name of variable to get
+ *
+ * \return The value associated with variable "name". NULL if variable not found.
+ */
+const char *ast_refer_get_var(struct ast_refer *refer, const char *name);
+
+/*!
+ * \brief Get the "refer-to" value of a refer.
+ * \note The return value is valid only as long as the ast_refer is valid. Hold a reference
+ *       to the refer if you plan on storing the return value.
+ *
+ * \param refer The refer to get the "refer-to" value from
+ *
+ * \return The "refer-to" value of the refer, encoded in UTF-8.
+ */
+const char *ast_refer_get_refer_to(const struct ast_refer *refer);
+
+/*!
+ * \brief Retrieve the source of this refer
+ *
+ * \param refer The refer to get the soure from
+ *
+ * \return The source of the refer
+ * \retval NULL or empty string if the refer has no source
+ */
+const char *ast_refer_get_from(const struct ast_refer *refer);
+
+/*!
+ * \brief Retrieve the destination of this refer
+ *
+ * \param refer The refer to get the destination from
+ *
+ * \return The destination of the refer
+ * \retval NULL or empty string if the refer has no destination
+ */
+const char *ast_refer_get_to(const struct ast_refer *refer);
+
+/*!
+ * \brief Retrieve the "to_self" value of this refer
+ *
+ * \param refer The refer to get the destination from
+ *
+ * \return The to_self value of the refer
+ */
+int ast_refer_get_to_self(const struct ast_refer *refer);
+
+/*!
+ * \brief Retrieve the technology associated with this refer
+ *
+ * \param refer The refer to get the technology from
+ *
+ * \return The technology of the refer
+ * \retval NULL or empty string if the refer has no associated technology
+ */
+const char *ast_refer_get_tech(const struct ast_refer *refer);
+
+/*!
+ * \brief Retrieve the endpoint associated with this refer
+ *
+ * \param refer The refer to get the endpoint from
+ *
+ * \return The endpoint associated with the refer
+ * \retval NULL or empty string if the refer has no associated endpoint
+ */
+const char *ast_refer_get_endpoint(const struct ast_refer *refer);
+
+/*!
+ * \brief Send a refer directly to an endpoint.
+ *
+ * Regardless of the return value of this function, this function will take
+ * care of ensuring that the refer object is properly destroyed when needed.
+ *
+ * \retval 0 refer successfully queued to be sent out
+ * \retval non-zero failure, refer not get sent out.
+ */
+int ast_refer_send(struct ast_refer *refer);
+
+/*!
+ * \brief Opaque iterator for refer variables
+ */
+struct ast_refer_var_iterator;
+
+/*!
+ * \brief Create a new refer variable iterator
+ * \param refer A refer whose variables are to be iterated over
+ *
+ * \return An opaque pointer to the new iterator
+ */
+struct ast_refer_var_iterator *ast_refer_var_iterator_init(const struct ast_refer *refer);
+
+/*!
+ * \brief Get the next variable name and value
+ *
+ * \param iter An iterator created with ast_refer_var_iterator_init
+ * \param name A pointer to the name result pointer
+ * \param value A pointer to the value result pointer
+ *
+ * \note The refcount to iter->current_used must be decremented by the caller
+ *       by calling ast_refer_var_unref_current.
+ *
+ * \retval 0 No more entries
+ * \retval 1 Valid entry
+ */
+int ast_refer_var_iterator_next(struct ast_refer_var_iterator *iter, const char **name, const char **value);
+
+/*!
+ * \brief Destroy a refer variable iterator
+ * \param iter Iterator to be destroyed
+ */
+void ast_refer_var_iterator_destroy(struct ast_refer_var_iterator *iter);
+
+/*!
+ * \brief Unref a refer var from inside an iterator loop
+ */
+void ast_refer_var_unref_current(struct ast_refer_var_iterator *iter);
+
+/*!
+ *  @}
+ */
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* __AST_REFER_H__ */
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 673ec95505e76554d04573d9a64c4e68c57cbabb..cf95aafda4f5eb00f8b51e7a6812a54241399dd8 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -339,6 +339,20 @@ struct ast_sip_nat_hook {
 	void (*outgoing_external_message)(struct pjsip_tx_data *tdata, struct ast_sip_transport *transport);
 };
 
+/*! \brief Structure which contains information about a transport */
+struct ast_sip_request_transport_details {
+	/*! \brief Type of transport */
+	enum ast_transport type;
+	/*! \brief Potential pointer to the transport itself, if UDP */
+	pjsip_transport *transport;
+	/*! \brief Potential pointer to the transport factory itself, if TCP/TLS */
+	pjsip_tpfactory *factory;
+	/*! \brief Local address for transport */
+	pj_str_t local_address;
+	/*! \brief Local port for transport */
+	int local_port;
+};
+
 /*!
  * \brief The kind of security negotiation
  */
@@ -3506,18 +3520,65 @@ struct ast_threadpool *ast_sip_threadpool(void);
  * \brief Retrieve transport state
  * \since 13.7.1
  *
- * @param transport_id
- * @returns transport_state
+ * \param transport_id
+ * \retval transport_state
  *
  * \note ao2_cleanup(...) or ao2_ref(...,  -1) must be called on the returned object
  */
 struct ast_sip_transport_state *ast_sip_get_transport_state(const char *transport_id);
 
+/*!
+ * \brief Return the SIP URI of the Contact header
+ * 
+ * \param tdata
+ * \retval Pointer to SIP URI of Contact
+ * \retval NULL if Contact header not found or not a SIP(S) URI
+ *
+ * \note Do not free the returned object.
+ */
+pjsip_sip_uri *ast_sip_get_contact_sip_uri(pjsip_tx_data *tdata);
+
+/*!
+ * \brief Returns the transport state currently in use based on request transport details
+ *
+ * \param details
+ * \retval transport_state
+ *
+ * \note ao2_cleanup(...) or ao2_ref(...,  -1) must be called on the returned object
+ */
+struct ast_sip_transport_state *ast_sip_find_transport_state_in_use(struct ast_sip_request_transport_details *details);
+
+/*!
+ * \brief Sets request transport details based on tdata
+ *
+ * \param details pre-allocated request transport details to set
+ * \param tdata
+ * \param use_ipv6 if non-zero, ipv6 transports will be considered
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_sip_set_request_transport_details(struct ast_sip_request_transport_details *details, pjsip_tx_data *tdata, int use_ipv6);
+
+/*!
+ * \brief Replace domain and port of SIP URI to point to (external) signaling address of this Asterisk instance
+ *
+ * \param uri
+ * \param tdata
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \note Uses domain and port in Contact header if it exists, otherwise the local URI of the dialog is used if the
+ *       message is sent within the context of a dialog. Further, NAT settings are considered - i.e. if the target
+ *       is not in the localnet, the external_signaling_address and port are used.
+ */
+int ast_sip_rewrite_uri_to_local(pjsip_sip_uri *uri, pjsip_tx_data *tdata);
+
 /*!
  * \brief Retrieves all transport states
  * \since 13.7.1
  *
- * @returns ao2_container
+ * \retval ao2_container
  *
  * \note ao2_cleanup(...) or ao2_ref(...,  -1) must be called on the returned object
  */
@@ -3647,6 +3708,105 @@ int ast_sip_set_id_from_invite(struct pjsip_rx_data *rdata, struct ast_party_id
 void ast_sip_modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr,
 	const struct ast_party_id *id);
 
+/*!
+ * \brief Retrieves an endpoint and URI from the "to" string.
+ *
+ * This URI is used as the Request URI.
+ *
+ * Expects the given 'to' to be in one of the following formats:
+ * Why we allow so many is a mystery.
+ *
+ * Basic:
+ *
+ *      endpoint        : We'll get URI from the default aor/contact
+ *      endpoint/aor    : We'll get the URI from the specific aor/contact
+ *      endpoint@domain : We toss the domain part and just use the endpoint
+ *
+ *   These all use the endpoint and specified URI:
+ * \verbatim
+        endpoint/<sip[s]:host>
+        endpoint/<sip[s]:user@host>
+        endpoint/"Bob" <sip[s]:host>
+        endpoint/"Bob" <sip[s]:user@host>
+        endpoint/sip[s]:host
+        endpoint/sip[s]:user@host
+        endpoint/host
+        endpoint/user@host
+   \endverbatim
+ *
+ *   These all use the default endpoint and specified URI:
+ * \verbatim
+        <sip[s]:host>
+        <sip[s]:user@host>
+        "Bob" <sip[s]:host>
+        "Bob" <sip[s]:user@host>
+        sip[s]:host
+        sip[s]:user@host
+   \endverbatim
+ *
+ *   These use the default endpoint and specified host:
+ * \verbatim
+        host
+        user@host
+   \endverbatim
+ *
+ *   This form is similar to a dialstring:
+ * \verbatim
+        PJSIP/user@endpoint
+   \endverbatim
+ *
+ *   In this case, the user will be added to the endpoint contact's URI.
+ *   If the contact URI already has a user, it will be replaced.
+ *
+ * The ones that have the sip[s] scheme are the easiest to parse.
+ * The rest all have some issue.
+ *
+ *      endpoint vs host              : We have to test for endpoint first
+ *      endpoint/aor vs endpoint/host : We have to test for aor first
+ *                                      What if there's an aor with the same
+ *                                      name as the host?
+ *      endpoint@domain vs user@host  : We have to test for endpoint first.
+ *                                      What if there's an endpoint with the
+ *                                      same name as the user?
+ *
+ * \param to 'To' field with possible endpoint
+ * \param get_default_outbound If nonzero, try to retrieve the default
+ * 			       outbound endpoint if no endpoint was found.
+ * 			       Otherwise, return NULL if no endpoint was found.
+ * \param uri Pointer to a char* which will be set to the URI.
+ *            Always must be ast_free'd by the caller - even if the return value is NULL!
+ *
+ * \note The logic below could probably be condensed but then it wouldn't be
+ * as clear.
+ */
+struct ast_sip_endpoint *ast_sip_get_endpoint(const char *to, int get_default_outbound, char **uri);
+
+/*!
+ * \brief Replace the To URI in the tdata with the supplied one
+ *
+ * \param tdata the outbound message data structure
+ * \param to URI to replace the To URI with. Must be a valid SIP URI.
+ *
+ * \retval 0: success, -1: failure
+ */
+int ast_sip_update_to_uri(pjsip_tx_data *tdata, const char *to);
+
+/*!
+ * \brief Overwrite fields in the outbound 'From' header
+ *
+ * The outbound 'From' header is created/added in ast_sip_create_request with
+ * default data.  If available that data may be info specified in the 'from_user'
+ * and 'from_domain' options found on the endpoint.  That information will be
+ * overwritten with data in the given 'from' parameter.
+ *
+ * \param tdata the outbound message data structure
+ * \param from info to copy into the header.
+ *		  Can be either a SIP URI, or in the format user[@domain]
+ *
+ * \retval 0: success, -1: failure
+ */
+int ast_sip_update_from(pjsip_tx_data *tdata, char *from);
+
 /*!
  * \brief Retrieve the unidentified request security event thresholds
  * \since 13.8.0
diff --git a/main/asterisk.c b/main/asterisk.c
index f96f6b2cadddd6759a2f4e3e9c4540f6ef8ee144..c9b491b040841ef1c749e168b0a9ee8b8c401be7 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -4256,6 +4256,7 @@ static void asterisk_daemon(int isroot, const char *runuser, const char *rungrou
 	check_init(load_pbx_app(), "PBX Application Support");
 	check_init(load_pbx_hangup_handler(), "PBX Hangup Handler Support");
 	check_init(ast_local_init(), "Local Proxy Channel Driver");
+	check_init(ast_refer_init(), "Refer API");
 
 	/* We should avoid most config loads before this point as they can't use realtime. */
 	check_init(load_modules(), "Module");
diff --git a/main/refer.c b/main/refer.c
new file mode 100644
index 0000000000000000000000000000000000000000..11db0652fc7fa87baed177f1cd41dbb5578ac56f
--- /dev/null
+++ b/main/refer.c
@@ -0,0 +1,537 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2023, Commend International
+ *
+ * Maximilian Fridrich <m.fridrich@commend.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.
+ */
+
+/*! \file
+ *
+ * \brief Out-of-call refer support
+ *
+ * \author Maximilian Fridrich <m.fridrich@commend.com>
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/_private.h"
+
+#include "asterisk/module.h"
+#include "asterisk/datastore.h"
+#include "asterisk/pbx.h"
+#include "asterisk/manager.h"
+#include "asterisk/strings.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/vector.h"
+#include "asterisk/app.h"
+#include "asterisk/taskprocessor.h"
+#include "asterisk/refer.h"
+
+struct refer_data {
+	/* Stored in stuff[] at struct end */
+	char *name;
+	/* Stored separately */
+	char *value;
+	/* Holds name */
+	char stuff[0];
+};
+
+/*!
+ * \brief A refer.
+ */
+struct ast_refer {
+	AST_DECLARE_STRING_FIELDS(
+		/*! Where the refer is going */
+		AST_STRING_FIELD(to);
+		/*! Where we "say" the refer came from */
+		AST_STRING_FIELD(from);
+		/*! Where to refer to */
+		AST_STRING_FIELD(refer_to);
+		/*! An endpoint associated with this refer */
+		AST_STRING_FIELD(endpoint);
+		/*! The technology of the endpoint associated with this refer */
+		AST_STRING_FIELD(tech);
+	);
+	/* Whether to refer to Asterisk itself, if refer_to is an Asterisk endpoint. */
+	int to_self;
+	/*! Technology/dialplan specific variables associated with the refer */
+	struct ao2_container *vars;
+};
+
+/*! \brief Lock for \c refer_techs vector */
+static ast_rwlock_t refer_techs_lock;
+
+/*! \brief Vector of refer technologies */
+AST_VECTOR(, const struct ast_refer_tech *) refer_techs;
+
+static int refer_data_cmp_fn(void *obj, void *arg, int flags)
+{
+	const struct refer_data *object_left = obj;
+	const struct refer_data *object_right = arg;
+	const char *right_key = arg;
+	int cmp;
+
+	switch (flags & OBJ_SEARCH_MASK) {
+	case OBJ_SEARCH_OBJECT:
+		right_key = object_right->name;
+	case OBJ_SEARCH_KEY:
+		cmp = strcasecmp(object_left->name, right_key);
+		break;
+	case OBJ_SEARCH_PARTIAL_KEY:
+		cmp = strncasecmp(object_left->name, right_key, strlen(right_key));
+		break;
+	default:
+		cmp = 0;
+		break;
+	}
+	if (cmp) {
+		return 0;
+	}
+	return CMP_MATCH;
+}
+
+static void refer_data_destructor(void *obj)
+{
+	struct refer_data *data = obj;
+	ast_free(data->value);
+	ast_free(data);
+}
+
+static void refer_destructor(void *obj)
+{
+	struct ast_refer *refer = obj;
+
+	ast_string_field_free_memory(refer);
+	ao2_cleanup(refer->vars);
+}
+
+struct ast_refer *ast_refer_alloc(void)
+{
+	struct ast_refer *refer;
+
+	if (!(refer = ao2_alloc_options(sizeof(*refer), refer_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
+		return NULL;
+	}
+
+	if (ast_string_field_init(refer, 128)) {
+		ao2_ref(refer, -1);
+		return NULL;
+	}
+
+	refer->vars = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
+		NULL, refer_data_cmp_fn);
+	if (!refer->vars) {
+		ao2_ref(refer, -1);
+		return NULL;
+	}
+	refer->to_self = 0;
+
+	return refer;
+}
+
+struct ast_refer *ast_refer_ref(struct ast_refer *refer)
+{
+	ao2_ref(refer, 1);
+	return refer;
+}
+
+struct ast_refer *ast_refer_destroy(struct ast_refer *refer)
+{
+	ao2_ref(refer, -1);
+	return NULL;
+}
+
+int ast_refer_set_to(struct ast_refer *refer, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	ast_string_field_build_va(refer, to, fmt, ap);
+	va_end(ap);
+
+	return 0;
+}
+
+int ast_refer_set_from(struct ast_refer *refer, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	ast_string_field_build_va(refer, from, fmt, ap);
+	va_end(ap);
+
+	return 0;
+}
+
+int ast_refer_set_refer_to(struct ast_refer *refer, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	ast_string_field_build_va(refer, refer_to, fmt, ap);
+	va_end(ap);
+
+	return 0;
+}
+
+int ast_refer_set_to_self(struct ast_refer *refer, int val)
+{
+	refer->to_self = val;
+	return 0;
+}
+
+int ast_refer_set_tech(struct ast_refer *refer, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	ast_string_field_build_va(refer, tech, fmt, ap);
+	va_end(ap);
+
+	return 0;
+}
+
+int ast_refer_set_endpoint(struct ast_refer *refer, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	ast_string_field_build_va(refer, endpoint, fmt, ap);
+	va_end(ap);
+
+	return 0;
+}
+
+const char *ast_refer_get_refer_to(const struct ast_refer *refer)
+{
+	return refer->refer_to;
+}
+
+const char *ast_refer_get_from(const struct ast_refer *refer)
+{
+	return refer->from;
+}
+
+const char *ast_refer_get_to(const struct ast_refer *refer)
+{
+	return refer->to;
+}
+
+int ast_refer_get_to_self(const struct ast_refer *refer)
+{
+	return refer->to_self;
+}
+
+const char *ast_refer_get_tech(const struct ast_refer *refer)
+{
+	return refer->tech;
+}
+
+const char *ast_refer_get_endpoint(const struct ast_refer *refer)
+{
+	return refer->endpoint;
+}
+
+static struct refer_data *refer_data_new(const char *name)
+{
+	struct refer_data *data;
+	int name_len = strlen(name) + 1;
+
+	if ((data = ao2_alloc_options(name_len + sizeof(*data), refer_data_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
+		data->name = data->stuff;
+		strcpy(data->name, name);
+	}
+
+	return data;
+}
+
+static struct refer_data *refer_data_find(struct ao2_container *vars, const char *name)
+{
+	return ao2_find(vars, name, OBJ_SEARCH_KEY);
+}
+
+char *ast_refer_get_var_and_unlink(struct ast_refer *refer, const char *name)
+{
+	struct refer_data *data;
+	char *val = NULL;
+
+	if (!(data = ao2_find(refer->vars, name, OBJ_SEARCH_KEY | OBJ_UNLINK))) {
+		return NULL;
+	}
+
+	val = ast_strdup(data->value);
+	ao2_ref(data, -1);
+
+	return val;
+}
+
+static int refer_set_var_full(struct ast_refer *refer, const char *name, const char *value)
+{
+	struct refer_data *data;
+
+	if (!(data = refer_data_find(refer->vars, name))) {
+		if (ast_strlen_zero(value)) {
+			return 0;
+		}
+		if (!(data = refer_data_new(name))) {
+			return -1;
+		};
+		data->value = ast_strdup(value);
+
+		ao2_link(refer->vars, data);
+	} else {
+		if (ast_strlen_zero(value)) {
+			ao2_unlink(refer->vars, data);
+		} else {
+			data->value = ast_strdup(value);
+		}
+	}
+
+	ao2_ref(data, -1);
+
+	return 0;
+}
+
+int ast_refer_set_var_outbound(struct ast_refer *refer, const char *name, const char *value)
+{
+	return refer_set_var_full(refer, name, value);
+}
+
+const char *ast_refer_get_var(struct ast_refer *refer, const char *name)
+{
+	struct refer_data *data;
+	const char *val = NULL;
+
+	if (!(data = refer_data_find(refer->vars, name))) {
+		return NULL;
+	}
+
+	val = data->value;
+	ao2_ref(data, -1);
+
+	return val;
+}
+
+struct ast_refer_var_iterator {
+	struct ao2_iterator iter;
+	struct refer_data *current_used;
+};
+
+struct ast_refer_var_iterator *ast_refer_var_iterator_init(const struct ast_refer *refer)
+{
+	struct ast_refer_var_iterator *iter;
+
+	iter = ast_calloc(1, sizeof(*iter));
+	if (!iter) {
+		return NULL;
+	}
+
+	iter->iter = ao2_iterator_init(refer->vars, 0);
+
+	return iter;
+}
+
+int ast_refer_var_iterator_next(struct ast_refer_var_iterator *iter, const char **name, const char **value)
+{
+	struct refer_data *data;
+
+	if (!iter) {
+		return 0;
+	}
+
+	data = ao2_iterator_next(&iter->iter);
+	if (!data) {
+		return 0;
+	}
+
+	*name = data->name;
+	*value = data->value;
+
+	iter->current_used = data;
+
+	return 1;
+}
+
+void ast_refer_var_unref_current(struct ast_refer_var_iterator *iter)
+{
+	ao2_cleanup(iter->current_used);
+	iter->current_used = NULL;
+}
+
+void ast_refer_var_iterator_destroy(struct ast_refer_var_iterator *iter)
+{
+	if (iter) {
+		ao2_iterator_destroy(&iter->iter);
+		ast_refer_var_unref_current(iter);
+		ast_free(iter);
+	}
+}
+
+/*!
+ * \internal \brief Find a \c ast_refer_tech by its technology name
+ *
+ * \param tech_name The name of the refer technology
+ *
+ * \note \c refer_techs should be locked via \c refer_techs_lock prior to
+ *       calling this function
+ *
+ * \retval NULL if no \ref ast_refer_tech has been registered
+ * \return \ref ast_refer_tech if registered
+ */
+static const struct ast_refer_tech *refer_find_by_tech_name(const char *tech_name)
+{
+	const struct ast_refer_tech *current;
+	int i;
+
+	for (i = 0; i < AST_VECTOR_SIZE(&refer_techs); i++) {
+		current = AST_VECTOR_GET(&refer_techs, i);
+		if (!strcmp(current->name, tech_name)) {
+			return current;
+		}
+	}
+
+	return NULL;
+}
+
+int ast_refer_send(struct ast_refer *refer)
+{
+	char *tech_name = NULL;
+	const struct ast_refer_tech *refer_tech;
+	int res = -1;
+
+	if (ast_strlen_zero(refer->to)) {
+		ao2_ref(refer, -1);
+		return -1;
+	}
+
+	tech_name = ast_strdupa(refer->to);
+	tech_name = strsep(&tech_name, ":");
+
+	ast_rwlock_rdlock(&refer_techs_lock);
+	refer_tech = refer_find_by_tech_name(tech_name);
+
+	if (!refer_tech) {
+		ast_log(LOG_ERROR, "Unknown refer tech: %s\n", tech_name);
+		ast_rwlock_unlock(&refer_techs_lock);
+		ao2_ref(refer, -1);
+		return -1;
+	}
+
+	ao2_lock(refer);
+	res = refer_tech->refer_send(refer);
+	ao2_unlock(refer);
+
+	ast_rwlock_unlock(&refer_techs_lock);
+
+	ao2_ref(refer, -1);
+
+	return res;
+}
+
+int ast_refer_tech_register(const struct ast_refer_tech *tech)
+{
+	const struct ast_refer_tech *match;
+
+	ast_rwlock_wrlock(&refer_techs_lock);
+
+	match = refer_find_by_tech_name(tech->name);
+	if (match) {
+		ast_log(LOG_ERROR, "Refer technology already registered for '%s'\n",
+		        tech->name);
+		ast_rwlock_unlock(&refer_techs_lock);
+		return -1;
+	}
+
+	if (AST_VECTOR_APPEND(&refer_techs, tech)) {
+		ast_log(LOG_ERROR, "Failed to register refer technology for '%s'\n",
+		        tech->name);
+		ast_rwlock_unlock(&refer_techs_lock);
+		return -1;
+	}
+	ast_verb(3, "Refer technology '%s' registered.\n", tech->name);
+
+	ast_rwlock_unlock(&refer_techs_lock);
+
+	return 0;
+}
+
+/*!
+ * \brief Comparison callback for \c ast_refer_tech vector removal
+ *
+ * \param vec_elem The element in the vector being compared
+ * \param srch The element being looked up
+ *
+ * \retval non-zero The items are equal
+ * \retval 0 The items are not equal
+ */
+static int refer_tech_cmp(const struct ast_refer_tech *vec_elem, const struct ast_refer_tech *srch)
+{
+	if (!vec_elem->name || !srch->name) {
+		return (vec_elem->name == srch->name) ? 1 : 0;
+	}
+	return !strcmp(vec_elem->name, srch->name);
+}
+
+int ast_refer_tech_unregister(const struct ast_refer_tech *tech)
+{
+	int match;
+
+	ast_rwlock_wrlock(&refer_techs_lock);
+	match = AST_VECTOR_REMOVE_CMP_UNORDERED(&refer_techs, tech, refer_tech_cmp,
+	                                        AST_VECTOR_ELEM_CLEANUP_NOOP);
+	ast_rwlock_unlock(&refer_techs_lock);
+
+	if (match) {
+		ast_log(LOG_ERROR, "No '%s' refer technology found.\n", tech->name);
+		return -1;
+	}
+
+	ast_verb(2, "Refer technology '%s' unregistered.\n", tech->name);
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Clean up other resources on Asterisk shutdown
+ */
+static void refer_shutdown(void)
+{
+	AST_VECTOR_FREE(&refer_techs);
+	ast_rwlock_destroy(&refer_techs_lock);
+}
+
+/*!
+ * \internal
+ * \brief Initialize stuff during Asterisk startup.
+ *
+ * Cleanup isn't a big deal in this function.  If we return non-zero,
+ * Asterisk is going to exit.
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_refer_init(void)
+{
+	ast_rwlock_init(&refer_techs_lock);
+	if (AST_VECTOR_INIT(&refer_techs, 8)) {
+		return -1;
+	}
+	ast_register_cleanup(refer_shutdown);
+	return 0;
+}
diff --git a/res/ari/resource_endpoints.c b/res/ari/resource_endpoints.c
index 461328277a86c664c04c6237acbd040df861b70d..3663232cc349f5659a1eba70f729cc68e8981ef7 100644
--- a/res/ari/resource_endpoints.c
+++ b/res/ari/resource_endpoints.c
@@ -33,6 +33,7 @@
 #include "asterisk/stasis_endpoints.h"
 #include "asterisk/channel.h"
 #include "asterisk/message.h"
+#include "asterisk/refer.h"
 
 void ast_ari_endpoints_list(struct ast_variable *headers,
 	struct ast_ari_endpoints_list_args *args,
@@ -307,3 +308,135 @@ void ast_ari_endpoints_send_message_to_endpoint(struct ast_variable *headers,
 	send_message(msg_to, args->from, args->body, variables, response);
 	ast_variables_destroy(variables);
 }
+
+static void send_refer(const char *to, const char *from, const char *refer_to, int to_self, struct ast_variable *variables, struct ast_ari_response *response)
+{
+	struct ast_variable *current;
+	struct ast_refer *refer;
+	int res = 0;
+
+	if (ast_strlen_zero(to)) {
+		ast_ari_response_error(response, 400, "Bad Request",
+			"To must be specified");
+		return;
+	}
+
+	refer = ast_refer_alloc();
+	if (!refer) {
+		ast_ari_response_alloc_failed(response);
+		return;
+	}
+
+	ast_refer_set_to(refer, "%s", to);
+	ast_refer_set_to_self(refer, to_self);
+
+	if (!ast_strlen_zero(from)) {
+		ast_refer_set_from(refer, "%s", from);
+	}
+	if (!ast_strlen_zero(refer_to)) {
+		ast_refer_set_refer_to(refer, "%s", refer_to);
+	}
+
+	for (current = variables; current; current = current->next) {
+		res |= ast_refer_set_var_outbound(refer, current->name, current->value);
+	}
+
+	if (res) {
+		ast_ari_response_alloc_failed(response);
+		ast_refer_destroy(refer);
+		return;
+	}
+
+	if (ast_refer_send(refer)) {
+		ast_ari_response_error(response, 404, "Not Found",
+			"Endpoint not found");
+		return;
+	}
+
+	response->message = ast_json_null();
+	response->response_code = 202;
+	response->response_text = "Accepted";
+}
+
+static int parse_refer_json(struct ast_json *body,
+	struct ast_ari_response *response,
+	struct ast_variable **variables)
+{
+	const char *known_variables[] = { "display_name" };
+	const char *value;
+	struct ast_variable *new_var;
+	struct ast_json *json_variable;
+	int err = 0;
+	int i;
+
+	if (!body) {
+		return 0;
+	}
+
+	json_variable = ast_json_object_get(body, "variables");
+	if (json_variable) {
+		err = json_to_ast_variables(response, json_variable, variables);
+		if (err) {
+			return err;
+		}
+	}
+
+	for (i = 0; i < sizeof(known_variables) / sizeof(*known_variables); ++i) {
+		json_variable = ast_json_object_get(body, known_variables[i]);
+		if (json_variable && ast_json_typeof(json_variable) == AST_JSON_STRING) {
+			value = ast_json_string_get(json_variable);
+			new_var = ast_variable_new(known_variables[i], value, "");
+			if (new_var) {
+				ast_variable_list_append(variables, new_var);
+			}
+		}
+	}
+
+	return err;
+}
+
+void ast_ari_endpoints_refer(struct ast_variable *headers,
+	struct ast_ari_endpoints_refer_args *args,
+	struct ast_ari_response *response)
+{
+	struct ast_variable *variables = NULL;
+
+	ast_ari_endpoints_refer_parse_body(args->variables, args);
+
+	if (parse_refer_json(args->variables, response, &variables)) {
+		return;
+	}
+
+	send_refer(args->to, args->from, args->refer_to, args->to_self, variables, response);
+	ast_variables_destroy(variables);
+}
+
+void ast_ari_endpoints_refer_to_endpoint(struct ast_variable *headers,
+	struct ast_ari_endpoints_refer_to_endpoint_args *args,
+	struct ast_ari_response *response)
+{
+	struct ast_variable *variables = NULL;
+	struct ast_endpoint_snapshot *snapshot;
+	char to[128];
+	char *tech = ast_strdupa(args->tech);
+
+	/* Really, we just want to know if this thing exists */
+	snapshot = ast_endpoint_latest_snapshot(args->tech, args->resource);
+	if (!snapshot) {
+		ast_ari_response_error(response, 404, "Not Found",
+			"Endpoint not found");
+		return;
+	}
+	ao2_ref(snapshot, -1);
+
+	ast_ari_endpoints_refer_to_endpoint_parse_body(args->variables, args);
+
+	if (parse_refer_json(args->variables, response, &variables)) {
+		return;
+	}
+
+	snprintf(to, sizeof(to), "%s:%s", ast_str_to_lower(tech), args->resource);
+
+	send_refer(to, args->from, args->refer_to, args->to_self, variables, response);
+	ast_variables_destroy(variables);
+}
diff --git a/res/ari/resource_endpoints.h b/res/ari/resource_endpoints.h
index b5ad634077c432b0eb4bcdbea8a7083363385d5c..3c212b93fa61eff6a4bf5d3370998ca93367ff37 100644
--- a/res/ari/resource_endpoints.h
+++ b/res/ari/resource_endpoints.h
@@ -58,6 +58,7 @@ struct ast_ari_endpoints_send_message_args {
 	const char *from;
 	/*! The body of the message */
 	const char *body;
+	/*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip and sip resource types will add the key/value pairs as SIP headers, */
 	struct ast_json *variables;
 };
 /*!
@@ -79,6 +80,38 @@ int ast_ari_endpoints_send_message_parse_body(
  * \param[out] response HTTP response
  */
 void ast_ari_endpoints_send_message(struct ast_variable *headers, struct ast_ari_endpoints_send_message_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_endpoints_refer() */
+struct ast_ari_endpoints_refer_args {
+	/*! The endpoint resource or technology specific URI that should be referred to somewhere. Valid resource is pjsip. */
+	const char *to;
+	/*! The endpoint resource or technology specific identity to refer from. */
+	const char *from;
+	/*! The endpoint resource or technology specific URI to refer to. */
+	const char *refer_to;
+	/*! If true and "refer_to" refers to an Asterisk endpoint, the "refer_to" value is set to point to this Asterisk endpoint - so the referee is referred to Asterisk. Otherwise, use the contact URI associated with the endpoint. */
+	int to_self;
+	/*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, the pjsip resource type will add the key/value pairs as SIP headers. The "display_name" key is used by the PJSIP technology. Its value will be prepended as a display name to the Refer-To URI. */
+	struct ast_json *variables;
+};
+/*!
+ * \brief Body parsing function for /endpoints/refer.
+ * \param body The JSON body from which to parse parameters.
+ * \param[out] args The args structure to parse into.
+ * \retval zero on success
+ * \retval non-zero on failure
+ */
+int ast_ari_endpoints_refer_parse_body(
+	struct ast_json *body,
+	struct ast_ari_endpoints_refer_args *args);
+
+/*!
+ * \brief Refer an endpoint or technology URI to some technology URI or endpoint.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_endpoints_refer(struct ast_variable *headers, struct ast_ari_endpoints_refer_args *args, struct ast_ari_response *response);
 /*! Argument struct for ast_ari_endpoints_list_by_tech() */
 struct ast_ari_endpoints_list_by_tech_args {
 	/*! Technology of the endpoints (pjsip,iax2,...) */
@@ -117,6 +150,7 @@ struct ast_ari_endpoints_send_message_to_endpoint_args {
 	const char *from;
 	/*! The body of the message */
 	const char *body;
+	/*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip and sip resource types will add the key/value pairs as SIP headers, */
 	struct ast_json *variables;
 };
 /*!
@@ -138,5 +172,39 @@ int ast_ari_endpoints_send_message_to_endpoint_parse_body(
  * \param[out] response HTTP response
  */
 void ast_ari_endpoints_send_message_to_endpoint(struct ast_variable *headers, struct ast_ari_endpoints_send_message_to_endpoint_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_endpoints_refer_to_endpoint() */
+struct ast_ari_endpoints_refer_to_endpoint_args {
+	/*! Technology of the endpoint */
+	const char *tech;
+	/*! ID of the endpoint */
+	const char *resource;
+	/*! The endpoint resource or technology specific identity to refer from. */
+	const char *from;
+	/*! The endpoint resource or technology specific URI to refer to. */
+	const char *refer_to;
+	/*! If true and "refer_to" refers to an Asterisk endpoint, the "refer_to" value is set to point to this Asterisk endpoint - so the referee is referred to Asterisk. Otherwise, use the contact URI associated with the endpoint. */
+	int to_self;
+	/*! The "variables" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, the pjsip resource type will add the key/value pairs as SIP headers, */
+	struct ast_json *variables;
+};
+/*!
+ * \brief Body parsing function for /endpoints/{tech}/{resource}/refer.
+ * \param body The JSON body from which to parse parameters.
+ * \param[out] args The args structure to parse into.
+ * \retval zero on success
+ * \retval non-zero on failure
+ */
+int ast_ari_endpoints_refer_to_endpoint_parse_body(
+	struct ast_json *body,
+	struct ast_ari_endpoints_refer_to_endpoint_args *args);
+
+/*!
+ * \brief Refer an endpoint or technology URI to some technology URI or endpoint.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_endpoints_refer_to_endpoint(struct ast_variable *headers, struct ast_ari_endpoints_refer_to_endpoint_args *args, struct ast_ari_response *response);
 
 #endif /* _ASTERISK_RESOURCE_ENDPOINTS_H */
diff --git a/res/res_ari_endpoints.c b/res/res_ari_endpoints.c
index d41096c0e2b27cf6fdd3297875c9d078bcdc3714..b40fd8cebcc2b0af11c4df957a7ba1646663d275 100644
--- a/res/res_ari_endpoints.c
+++ b/res/res_ari_endpoints.c
@@ -188,6 +188,102 @@ static void ast_ari_endpoints_send_message_cb(
 	}
 #endif /* AST_DEVMODE */
 
+fin: __attribute__((unused))
+	return;
+}
+int ast_ari_endpoints_refer_parse_body(
+	struct ast_json *body,
+	struct ast_ari_endpoints_refer_args *args)
+{
+	struct ast_json *field;
+	/* Parse query parameters out of it */
+	field = ast_json_object_get(body, "to");
+	if (field) {
+		args->to = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "from");
+	if (field) {
+		args->from = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "refer_to");
+	if (field) {
+		args->refer_to = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "to_self");
+	if (field) {
+		args->to_self = ast_json_is_true(field);
+	}
+	return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /endpoints/refer.
+ * \param ser TCP/TLS session object
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param body
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_endpoints_refer_cb(
+	struct ast_tcptls_session_instance *ser,
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
+{
+	struct ast_ari_endpoints_refer_args args = {};
+	struct ast_variable *i;
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
+	for (i = get_params; i; i = i->next) {
+		if (strcmp(i->name, "to") == 0) {
+			args.to = (i->value);
+		} else
+		if (strcmp(i->name, "from") == 0) {
+			args.from = (i->value);
+		} else
+		if (strcmp(i->name, "refer_to") == 0) {
+			args.refer_to = (i->value);
+		} else
+		if (strcmp(i->name, "to_self") == 0) {
+			args.to_self = ast_true(i->value);
+		} else
+		{}
+	}
+	args.variables = body;
+	ast_ari_endpoints_refer(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 0: /* Implementation is still a stub, or the code wasn't set */
+		is_valid = response->message == NULL;
+		break;
+	case 500: /* Internal Server Error */
+	case 501: /* Not Implemented */
+	case 400: /* Invalid parameters for referring. */
+	case 404: /* Endpoint not found */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ast_ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /endpoints/refer\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /endpoints/refer\n");
+		ast_ari_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+
 fin: __attribute__((unused))
 	return;
 }
@@ -403,6 +499,104 @@ static void ast_ari_endpoints_send_message_to_endpoint_cb(
 	}
 #endif /* AST_DEVMODE */
 
+fin: __attribute__((unused))
+	return;
+}
+int ast_ari_endpoints_refer_to_endpoint_parse_body(
+	struct ast_json *body,
+	struct ast_ari_endpoints_refer_to_endpoint_args *args)
+{
+	struct ast_json *field;
+	/* Parse query parameters out of it */
+	field = ast_json_object_get(body, "from");
+	if (field) {
+		args->from = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "refer_to");
+	if (field) {
+		args->refer_to = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "to_self");
+	if (field) {
+		args->to_self = ast_json_is_true(field);
+	}
+	return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /endpoints/{tech}/{resource}/refer.
+ * \param ser TCP/TLS session object
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param body
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_endpoints_refer_to_endpoint_cb(
+	struct ast_tcptls_session_instance *ser,
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
+{
+	struct ast_ari_endpoints_refer_to_endpoint_args args = {};
+	struct ast_variable *i;
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
+	for (i = get_params; i; i = i->next) {
+		if (strcmp(i->name, "from") == 0) {
+			args.from = (i->value);
+		} else
+		if (strcmp(i->name, "refer_to") == 0) {
+			args.refer_to = (i->value);
+		} else
+		if (strcmp(i->name, "to_self") == 0) {
+			args.to_self = ast_true(i->value);
+		} else
+		{}
+	}
+	for (i = path_vars; i; i = i->next) {
+		if (strcmp(i->name, "tech") == 0) {
+			args.tech = (i->value);
+		} else
+		if (strcmp(i->name, "resource") == 0) {
+			args.resource = (i->value);
+		} else
+		{}
+	}
+	args.variables = body;
+	ast_ari_endpoints_refer_to_endpoint(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 0: /* Implementation is still a stub, or the code wasn't set */
+		is_valid = response->message == NULL;
+		break;
+	case 500: /* Internal Server Error */
+	case 501: /* Not Implemented */
+	case 400: /* Invalid parameters for referring. */
+	case 404: /* Endpoint not found */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ast_ari_validate_void(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /endpoints/{tech}/{resource}/refer\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /endpoints/{tech}/{resource}/refer\n");
+		ast_ari_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+
 fin: __attribute__((unused))
 	return;
 }
@@ -417,6 +611,15 @@ static struct stasis_rest_handlers endpoints_sendMessage = {
 	.children = {  }
 };
 /*! \brief REST handler for /api-docs/endpoints.json */
+static struct stasis_rest_handlers endpoints_refer = {
+	.path_segment = "refer",
+	.callbacks = {
+		[AST_HTTP_POST] = ast_ari_endpoints_refer_cb,
+	},
+	.num_children = 0,
+	.children = {  }
+};
+/*! \brief REST handler for /api-docs/endpoints.json */
 static struct stasis_rest_handlers endpoints_tech_resource_sendMessage = {
 	.path_segment = "sendMessage",
 	.callbacks = {
@@ -426,14 +629,23 @@ static struct stasis_rest_handlers endpoints_tech_resource_sendMessage = {
 	.children = {  }
 };
 /*! \brief REST handler for /api-docs/endpoints.json */
+static struct stasis_rest_handlers endpoints_tech_resource_refer = {
+	.path_segment = "refer",
+	.callbacks = {
+		[AST_HTTP_POST] = ast_ari_endpoints_refer_to_endpoint_cb,
+	},
+	.num_children = 0,
+	.children = {  }
+};
+/*! \brief REST handler for /api-docs/endpoints.json */
 static struct stasis_rest_handlers endpoints_tech_resource = {
 	.path_segment = "resource",
 	.is_wildcard = 1,
 	.callbacks = {
 		[AST_HTTP_GET] = ast_ari_endpoints_get_cb,
 	},
-	.num_children = 1,
-	.children = { &endpoints_tech_resource_sendMessage, }
+	.num_children = 2,
+	.children = { &endpoints_tech_resource_sendMessage,&endpoints_tech_resource_refer, }
 };
 /*! \brief REST handler for /api-docs/endpoints.json */
 static struct stasis_rest_handlers endpoints_tech = {
@@ -451,8 +663,8 @@ static struct stasis_rest_handlers endpoints = {
 	.callbacks = {
 		[AST_HTTP_GET] = ast_ari_endpoints_list_cb,
 	},
-	.num_children = 2,
-	.children = { &endpoints_sendMessage,&endpoints_tech, }
+	.num_children = 3,
+	.children = { &endpoints_sendMessage,&endpoints_refer,&endpoints_tech, }
 };
 
 static int unload_module(void)
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index d112aa30433981bdd27c786c26f47237c1a9eb06..98a406d74f3eb196a746c089c7e4802dd76f318f 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -27,6 +27,8 @@
 #include <pjmedia/errno.h>
 
 #include "asterisk/res_pjsip.h"
+#include "asterisk/strings.h"
+#include "pjsip/sip_parser.h"
 #include "res_pjsip/include/res_pjsip_private.h"
 #include "asterisk/linkedlists.h"
 #include "asterisk/logger.h"
@@ -48,6 +50,7 @@
 #include "asterisk/res_pjsip_presence_xml.h"
 #include "asterisk/res_pjproject.h"
 #include "asterisk/utf8.h"
+#include "asterisk/acl.h"
 
 /*** MODULEINFO
 	<depend>pjproject</depend>
@@ -558,6 +561,140 @@ int ast_sip_will_uri_survive_restart(pjsip_sip_uri *uri, struct ast_sip_endpoint
 	return result;
 }
 
+pjsip_sip_uri *ast_sip_get_contact_sip_uri(pjsip_tx_data *tdata)
+{
+	pjsip_contact_hdr *contact = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
+
+	if (!contact || (!PJSIP_URI_SCHEME_IS_SIP(contact->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact->uri))) {
+		return NULL;
+	}
+
+	return pjsip_uri_get_uri(contact->uri);
+}
+
+/*! \brief Callback function for finding the transport the request is going out on */
+static int find_transport_state_in_use(void *obj, void *arg, int flags)
+{
+	struct ast_sip_transport_state *transport_state = obj;
+	struct ast_sip_request_transport_details *details = arg;
+
+	/* If an explicit transport or factory matches then this is what is in use, if we are unavailable
+	 * to compare based on that we make sure that the type is the same and the source IP address/port are the same
+	 */
+	if (transport_state && ((details->transport && details->transport == transport_state->transport) ||
+		(details->factory && details->factory == transport_state->factory) ||
+		((details->type == transport_state->type) && (transport_state->factory) &&
+			!pj_strcmp(&transport_state->factory->addr_name.host, &details->local_address) &&
+			transport_state->factory->addr_name.port == details->local_port))) {
+		return CMP_MATCH | CMP_STOP;
+	}
+
+	return 0;
+}
+
+struct ast_sip_transport_state *ast_sip_find_transport_state_in_use(struct ast_sip_request_transport_details *details) {
+	RAII_VAR(struct ao2_container *, transport_states, NULL, ao2_cleanup);
+
+	if (!(transport_states = ast_sip_get_transport_states())) {
+		return NULL;
+	}
+
+	return ao2_callback(transport_states, 0, find_transport_state_in_use, details);
+}
+
+int ast_sip_rewrite_uri_to_local(pjsip_sip_uri *uri, pjsip_tx_data *tdata) {
+	RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup);
+	struct ast_sip_request_transport_details details;
+	pjsip_sip_uri *tmp_uri;
+	pjsip_dialog *dlg;
+	struct ast_sockaddr addr = { { 0, } };
+
+	if ((tmp_uri = ast_sip_get_contact_sip_uri(tdata))) {
+		pj_strdup(tdata->pool, &uri->host, &tmp_uri->host);
+		uri->port = tmp_uri->port;
+	} else if ((dlg = pjsip_tdata_get_dlg(tdata))
+		&& (tmp_uri = pjsip_uri_get_uri(dlg->local.info->uri))
+		&& (PJSIP_URI_SCHEME_IS_SIP(tmp_uri) || PJSIP_URI_SCHEME_IS_SIPS(tmp_uri))) {
+		pj_strdup(tdata->pool, &uri->host, &tmp_uri->host);
+		uri->port = tmp_uri->port;
+	}
+
+	if (ast_sip_set_request_transport_details(&details, tdata, 1)
+		|| !(transport_state = ast_sip_find_transport_state_in_use(&details))
+		|| !(transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id))) {
+		return 0;
+	}
+
+	if (transport_state->localnet) {
+		ast_sockaddr_parse(&addr, tdata->tp_info.dst_name, PARSE_PORT_FORBID);
+		ast_sockaddr_set_port(&addr, tdata->tp_info.dst_port);
+		if (ast_sip_transport_is_local(transport_state, &addr)) {
+			return 0;
+		}
+	}
+
+	if (!ast_sockaddr_isnull(&transport_state->external_signaling_address)) {
+		pj_strdup2(tdata->pool, &uri->host, ast_sockaddr_stringify_host(&transport_state->external_signaling_address));
+	}
+
+	if (transport->external_signaling_port) {
+		uri->port = transport->external_signaling_port;
+	}
+
+	return 0;
+}
+
+int ast_sip_set_request_transport_details(struct ast_sip_request_transport_details *details, pjsip_tx_data *tdata,
+	int use_ipv6) {
+	pjsip_sip_uri *uri;
+	pjsip_via_hdr *via;
+	long transport_type;
+
+	if (!details || !tdata) {
+		return -1;
+	}
+
+	/* If IPv6 should be considered, un-set Bit 7 to make TCP6 equal to TCP and TLS6 equal to TLS */
+	transport_type = use_ipv6 ? tdata->tp_info.transport->key.type & ~(PJSIP_TRANSPORT_IPV6)
+		: tdata->tp_info.transport->key.type;
+
+	if (tdata->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
+		details->transport = tdata->tp_sel.u.transport;
+	} else if (tdata->tp_sel.type == PJSIP_TPSELECTOR_LISTENER) {
+		details->factory = tdata->tp_sel.u.listener;
+	} else if (transport_type == PJSIP_TRANSPORT_UDP || transport_type == PJSIP_TRANSPORT_UDP6) {
+		/* Connectionless uses the same transport for all requests */
+		details->type = AST_TRANSPORT_UDP;
+		details->transport = tdata->tp_info.transport;
+	} else {
+		if (transport_type == PJSIP_TRANSPORT_TCP) {
+			details->type = AST_TRANSPORT_TCP;
+		} else if (transport_type == PJSIP_TRANSPORT_TLS) {
+			details->type = AST_TRANSPORT_TLS;
+		} else {
+			/* Unknown transport type, we can't map. */
+			return -1;
+		}
+
+		if ((uri = ast_sip_get_contact_sip_uri(tdata))) {
+			details->local_address = uri->host;
+			details->local_port = uri->port;
+		} else if ((tdata->msg->type == PJSIP_REQUEST_MSG) &&
+			(via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL))) {
+			details->local_address = via->sent_by.host;
+			details->local_port = via->sent_by.port;
+		} else {
+			return -1;
+		}
+
+		if (!details->local_port) {
+			details->local_port = (details->type == AST_TRANSPORT_TLS) ? 5061 : 5060;
+		}
+	}
+	return 0;
+}
+
 int ast_sip_get_transport_name(const struct ast_sip_endpoint *endpoint,
 	pjsip_sip_uri *sip_uri, char *buf, size_t buf_len)
 {
@@ -835,7 +972,11 @@ pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint,
 	pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
 	static const pj_str_t HCONTACT = { "Contact", 7 };
 
-	snprintf(enclosed_uri, sizeof(enclosed_uri), "<%s>", uri);
+	if (!ast_begins_with(uri, "<")) {
+		snprintf(enclosed_uri, sizeof(enclosed_uri), "<%s>", uri);
+	} else {
+		snprintf(enclosed_uri, sizeof(enclosed_uri), "%s", uri);
+	}
 	pj_cstr(&remote_uri, enclosed_uri);
 
 	pj_cstr(&target_uri, uri);
@@ -1130,6 +1271,7 @@ int ast_sip_create_rdata(pjsip_rx_data *rdata, char *packet, const char *src_nam
 /* PJSIP doesn't know about the INFO method, so we have to define it ourselves */
 static const pjsip_method info_method = {PJSIP_OTHER_METHOD, {"INFO", 4} };
 static const pjsip_method message_method = {PJSIP_OTHER_METHOD, {"MESSAGE", 7} };
+static const pjsip_method refer_method = {PJSIP_OTHER_METHOD, {"REFER", 5} };
 
 static struct {
 	const char *method;
@@ -1146,6 +1288,7 @@ static struct {
 	{ "PUBLISH", &pjsip_publish_method },
 	{ "INFO", &info_method },
 	{ "MESSAGE", &message_method },
+	{ "REFER", &refer_method },
 };
 
 static const pjsip_method *get_pjsip_method(const char *method)
@@ -2727,6 +2870,575 @@ void ast_sip_modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr, const s
 	}
 }
 
+/*!
+ * \brief Find a contact and insert a "user@" into its URI.
+ *
+ * \param to Original destination (for error messages only)
+ * \param endpoint_name Endpoint name (for error messages only)
+ * \param aors Command separated list of AORs
+ * \param user The user to insert in the contact URI
+ * \param uri Pointer to buffer in which to return the URI. Must be freed by caller.
+ *
+ * \return  0 Success
+ * \return -1 Fail
+ *
+ * \note If the contact URI found for the endpoint already has a user in
+ * its URI, it will be replaced by the user passed as an argument to this function.
+ */
+static int insert_user_in_contact_uri(const char *to, const char *endpoint_name, const char *aors,
+	const char *user, char **uri)
+{
+	RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
+	pj_pool_t *pool;
+	pjsip_name_addr *name_addr;
+	pjsip_sip_uri *sip_uri;
+	int err = 0;
+
+	contact = ast_sip_location_retrieve_contact_from_aor_list(aors);
+	if (!contact) {
+		ast_log(LOG_WARNING, "Dest: '%s'. Couldn't find contact for endpoint '%s'\n",
+			to, endpoint_name);
+		return -1;
+	}
+
+	pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "uri-user-insert", 128, 128);
+	if (!pool) {
+		ast_log(LOG_WARNING, "Failed to allocate ParseUri endpoint pool.\n");
+		return -1;
+	}
+
+	name_addr = (pjsip_name_addr *) pjsip_parse_uri(pool, (char*)contact->uri, strlen(contact->uri), PJSIP_PARSE_URI_AS_NAMEADDR);
+	if (!name_addr || (!PJSIP_URI_SCHEME_IS_SIP(name_addr->uri) && !PJSIP_URI_SCHEME_IS_SIPS(name_addr->uri))) {
+		ast_log(LOG_WARNING, "Failed to parse URI '%s'\n", contact->uri);
+		err = -1;
+		goto out;
+	}
+
+	ast_debug(3, "Dest: '%s' User: '%s'  Endpoint: '%s'  ContactURI: '%s'\n", to, user, endpoint_name, contact->uri);
+
+	sip_uri = pjsip_uri_get_uri(name_addr->uri);
+	pj_strset2(&sip_uri->user, (char*)user);
+
+	*uri = ast_malloc(PJSIP_MAX_URL_SIZE);
+	if (!(*uri)) {
+		err = -1;
+		goto out;
+	}
+	pjsip_uri_print(PJSIP_URI_IN_REQ_URI, name_addr, *uri, PJSIP_MAX_URL_SIZE);
+
+out:
+	pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+
+	return err;
+}
+
+/*!
+ * \internal
+ * \brief Get endpoint and URI when the destination is only a single token
+ *
+ * "destination" could be one of the following:
+ * \verbatim
+		endpoint_name
+		hostname
+ * \endverbatim
+ *
+ * \param to
+ * \param destination
+ * \param get_default_outbound If nonzero, try to retrieve the default
+ * 			       outbound endpoint if no endpoint was found.
+ * 			       Otherwise, return NULL if no endpoint was found.
+ * \param uri Pointer to URI variable.  Must be freed by caller - even if the return value is NULL!
+ * \return endpoint
+ */
+static struct ast_sip_endpoint *handle_single_token(const char *to, char *destination, int get_default_outbound, char **uri) {
+	RAII_VAR(struct ast_sip_contact*, contact, NULL, ao2_cleanup);
+	char *endpoint_name = NULL;
+	struct ast_sip_endpoint *endpoint = NULL;
+
+	/*
+	 * If "destination" is just one token, it could be an endpoint name
+	 * or a hostname without a scheme.
+	 */
+
+	endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", destination);
+	if (!endpoint) {
+		/*
+		 * We can only assume it's a hostname.
+		 */
+		char *temp_uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
+		if (!temp_uri) {
+			goto failure;
+		}
+		sprintf(temp_uri, "sip:%s", destination);
+		*uri = temp_uri;
+		if (get_default_outbound) {
+			endpoint = ast_sip_default_outbound_endpoint();
+		}
+		ast_debug(3, "Dest: '%s' Didn't find endpoint so adding scheme and using URI '%s'%s\n",
+			to, *uri, get_default_outbound ? " with default endpoint" : "");
+		return endpoint;
+	}
+
+	/*
+	 * It's an endpoint
+	 */
+
+	endpoint_name = destination;
+	contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
+	if (!contact) {
+		ast_log(LOG_WARNING, "Dest: '%s'. Found endpoint '%s' but didn't find an aor/contact for it\n",
+			to, endpoint_name);
+		ao2_cleanup(endpoint);
+		goto failure;
+	}
+
+	*uri = ast_strdup(contact->uri);
+	if (!(*uri)) {
+		ao2_cleanup(endpoint);
+		goto failure;
+	}
+
+	ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s'\n",
+		to, endpoint_name, *uri);
+	return endpoint;
+
+failure:
+	*uri = NULL;
+	return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Get endpoint and URI when the destination contained a '/'
+ *
+ * "to" could be one of the following:
+ * \verbatim
+		endpoint/aor
+		endpoint/<sip[s]:host>
+		endpoint/<sip[s]:user@host>
+		endpoint/"Bob" <sip[s]:host>
+		endpoint/"Bob" <sip[s]:user@host>
+		endpoint/sip[s]:host
+		endpoint/sip[s]:user@host
+		endpoint/host
+		endpoint/user@host
+ * \endverbatim
+ *
+ * \param to Destination
+ * \param uri Pointer to URI variable.  Must be freed by caller - even if the return value is NULL!
+ * \param destination, slash, atsign, scheme
+ * \return endpoint
+ */
+static struct ast_sip_endpoint *handle_slash(const char *to, char *destination, char **uri,
+	char *slash, char *atsign, char *scheme)
+{
+	char *endpoint_name = NULL;
+	struct ast_sip_endpoint *endpoint = NULL;
+	struct ast_sip_contact *contact = NULL;
+	char *user = NULL;
+	char *afterslash = slash + 1;
+	struct ast_sip_aor *aor;
+
+	if (ast_begins_with(destination, "PJSIP/")) {
+		ast_debug(3, "Dest: '%s' Dialplan format'\n", to);
+		/*
+		 * This has to be the form PJSIP/user@endpoint
+		 */
+		if (!atsign || strchr(afterslash, '/')) {
+			/*
+			 * If there's no "user@" or there's a slash somewhere after
+			 * "PJSIP/" then we go no further.
+			 */
+			ast_log(LOG_WARNING,
+				"Dest: '%s'. Destinations beginning with 'PJSIP/' must be in the form of 'PJSIP/user@endpoint'\n",
+				to);
+			goto failure;
+		}
+		*atsign = '\0';
+		user = afterslash;
+		endpoint_name = atsign + 1;
+		ast_debug(3, "Dest: '%s' User: '%s'  Endpoint: '%s'\n", to, user, endpoint_name);
+	} else {
+		/*
+		 * Either...
+		 *	endpoint/aor
+		 *	endpoint/uri
+		 */
+		*slash = '\0';
+		endpoint_name = destination;
+		ast_debug(3, "Dest: '%s' Endpoint: '%s'\n", to, endpoint_name);
+	}
+
+	endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
+	if (!endpoint) {
+		ast_log(LOG_WARNING, "Dest: '%s'. Didn't find endpoint with name '%s'\n",
+			to, endpoint_name);
+		goto failure;
+	}
+
+	if (scheme) {
+		/*
+		 * If we found a scheme, then everything after the slash MUST be a URI.
+		 * We don't need to do any further modification.
+		 */
+		*uri = ast_strdup(afterslash);
+		if (!(*uri)) {
+			goto failure;
+		}
+		ast_debug(3, "Dest: '%s' Found endpoint '%s' and found URI '%s' after '/'\n",
+			to, endpoint_name, *uri);
+		return endpoint;
+	}
+
+	if (user) {
+		/*
+		 * This has to be the form PJSIP/user@endpoint
+		 */
+		int rc;
+
+		/*
+		 * Set the return URI to be the endpoint's contact URI with the user
+		 * portion set to the user that was specified before the endpoint name.
+		 */
+		rc = insert_user_in_contact_uri(to, endpoint_name, endpoint->aors, user, uri);
+		if (rc != 0) {
+			/*
+			 * insert_user_in_contact_uri prints the warning message.
+			 */
+			goto failure;
+		}
+		ast_debug(3, "Dest: '%s' User: '%s'  Endpoint: '%s'  URI: '%s'\n", to, user,
+			endpoint_name, *uri);
+
+		return endpoint;
+	}
+
+	/*
+	 * We're now left with two possibilities...
+	 * 	endpoint/aor
+	 *	endpoint/uri-without-scheme
+	 */
+	aor = ast_sip_location_retrieve_aor(afterslash);
+	if (!aor) {
+		/*
+		 * It's probably a URI without a scheme but we don't have a way to tell
+		 * for sure.  We're going to assume it is and prepend it with a scheme.
+		 */
+		*uri = ast_malloc(strlen(afterslash) + strlen("sip:") + 1);
+		if (!(*uri)) {
+			goto failure;
+		}
+		sprintf(*uri, "sip:%s", afterslash);
+		ast_debug(3, "Dest: '%s' Found endpoint '%s' but didn't find aor after '/' so using URI '%s'\n",
+			to, endpoint_name, *uri);
+		return endpoint;
+	}
+
+	/*
+	 * Only one possibility left... There was an aor name after the slash.
+	 */
+	ast_debug(3, "Dest: '%s' Found endpoint '%s' and found aor '%s' after '/'\n",
+		to, endpoint_name, ast_sorcery_object_get_id(aor));
+
+	contact = ast_sip_location_retrieve_first_aor_contact(aor);
+	if (!contact) {
+		ast_log(LOG_WARNING, "Dest: '%s'. Found endpoint '%s' but didn't find contact for aor '%s'\n",
+			to, endpoint_name, ast_sorcery_object_get_id(aor));
+		ao2_cleanup(aor);
+		goto failure;
+	}
+
+	*uri = ast_strdup(contact->uri);
+	ao2_cleanup(contact);
+	ao2_cleanup(aor);
+	if (!(*uri)) {
+		goto failure;
+	}
+
+	ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' for aor '%s'\n",
+		to, endpoint_name, *uri, ast_sorcery_object_get_id(aor));
+
+	return endpoint;
+
+failure:
+	ao2_cleanup(endpoint);
+	*uri = NULL;
+	return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Get endpoint and URI when the destination contained a '@' but no '/' or scheme
+ *
+ * "to" could be one of the following:
+ * \verbatim
+		<sip[s]:user@host>
+		"Bob" <sip[s]:user@host>
+		sip[s]:user@host
+		user@host
+ * \endverbatim
+ *
+ * \param to Destination
+ * \param uri Pointer to URI variable.  Must be freed by caller - even if the return value is NULL!
+ * \param destination, slash, atsign, scheme
+ * \param get_default_outbound If nonzero, try to retrieve the default
+ * 			       outbound endpoint if no endpoint was found.
+ * 			       Otherwise, return NULL if no endpoint was found.
+ * \return endpoint
+ */
+static struct ast_sip_endpoint *handle_atsign(const char *to, char *destination, char **uri,
+	char *slash, char *atsign, char *scheme, int get_default_outbound)
+{
+	char *endpoint_name = NULL;
+	struct ast_sip_endpoint *endpoint = NULL;
+	struct ast_sip_contact *contact = NULL;
+	char *afterat = atsign + 1;
+
+	*atsign = '\0';
+	endpoint_name = destination;
+
+	/* Apparently there may be ';<user_options>' after the endpoint name ??? */
+	AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(endpoint_name);
+	endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
+	if (!endpoint) {
+		/*
+		 * It's probably a uri with a user but without a scheme but we don't have a way to tell.
+		 * We're going to assume it is and prepend it with a scheme.
+		 */
+		*uri = ast_malloc(strlen(to) + strlen("sip:") + 1);
+		if (!(*uri)) {
+			goto failure;
+		}
+		sprintf(*uri, "sip:%s", to);
+		if (get_default_outbound) {
+			endpoint = ast_sip_default_outbound_endpoint();
+		}
+		ast_debug(3, "Dest: '%s' Didn't find endpoint before the '@' so using URI '%s'%s\n",
+			to, *uri, get_default_outbound ? " with default endpoint" : "");
+		return endpoint;
+	}
+
+	/*
+	 * OK, it's an endpoint and a domain (which we ignore)
+	 */
+	contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
+	if (!contact) {
+		ast_log(LOG_WARNING, "Dest: '%s'. Found endpoint '%s' but didn't find contact\n",
+			to, endpoint_name);
+		goto failure;
+	}
+
+	*uri = ast_strdup(contact->uri);
+	ao2_cleanup(contact);
+	if (!(*uri)) {
+		goto failure;
+	}
+	ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' (discarding domain %s)\n",
+		to, endpoint_name, *uri, afterat);
+
+	return endpoint;
+
+failure:
+	ao2_cleanup(endpoint);
+	*uri = NULL;
+	return NULL;
+}
+
+struct ast_sip_endpoint *ast_sip_get_endpoint(const char *to, int get_default_outbound, char **uri)
+{
+	char *destination;
+	char *slash = NULL;
+	char *atsign = NULL;
+	char *scheme = NULL;
+	struct ast_sip_endpoint *endpoint = NULL;
+
+	destination = ast_strdupa(to);
+
+	slash = strchr(destination, '/');
+	atsign = strchr(destination, '@');
+	scheme = S_OR(strstr(destination, "sip:"), strstr(destination, "sips:"));
+
+	if (!slash && !atsign && !scheme) {
+		/*
+		 * If there's only a single token, it can be either...
+		 * 	endpoint
+		 * 	host
+		 */
+		return handle_single_token(to, destination, get_default_outbound, uri);
+	}
+
+	if (slash) {
+		/*
+		 * If there's a '/', then the form must be one of the following...
+		 * 	PJSIP/user@endpoint
+		 * 	endpoint/aor
+		 * 	endpoint/uri
+		 */
+		return handle_slash(to, destination, uri, slash, atsign, scheme);
+	}
+
+	if (atsign && !scheme) {
+		/*
+		 * If there's an '@' but no scheme then it's either following an endpoint name
+		 * and being followed by a domain name (which we discard).
+		 * OR is's a user@host uri without a scheme.  It's probably the latter but because
+		 * endpoint@domain looks just like user@host, we'll test for endpoint first.
+		 */
+		return handle_atsign(to, destination, uri, slash, atsign, scheme, get_default_outbound);
+	}
+
+	/*
+	 * If all else fails, we assume it's a URI or just a hostname.
+	 */
+	if (scheme) {
+		*uri = ast_strdup(destination);
+		if (!(*uri)) {
+			goto failure;
+		}
+		ast_debug(3, "Dest: '%s' Didn't find an endpoint but did find a scheme so using URI '%s'%s\n",
+			to, *uri, get_default_outbound ? " with default endpoint" : "");
+	} else {
+		*uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
+		if (!(*uri)) {
+			goto failure;
+		}
+		sprintf(*uri, "sip:%s", destination);
+		ast_debug(3, "Dest: '%s' Didn't find an endpoint and didn't find scheme so adding scheme and using URI '%s'%s\n",
+			to, *uri, get_default_outbound ? " with default endpoint" : "");
+	}
+	if (get_default_outbound) {
+		endpoint = ast_sip_default_outbound_endpoint();
+	}
+
+	return endpoint;
+
+failure:
+	ao2_cleanup(endpoint);
+	*uri = NULL;
+	return NULL;
+}
+
+int ast_sip_update_to_uri(pjsip_tx_data *tdata, const char *to)
+{
+	pjsip_name_addr *parsed_name_addr;
+	pjsip_sip_uri *sip_uri;
+	pjsip_name_addr *tdata_name_addr;
+	pjsip_sip_uri *tdata_sip_uri;
+	pjsip_to_hdr *to_hdr;
+	char *buf = NULL;
+#define DEBUG_BUF_SIZE 256
+
+	parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, (char*)to, strlen(to),
+		PJSIP_PARSE_URI_AS_NAMEADDR);
+
+	if (!parsed_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
+			&& !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri))) {
+		ast_log(LOG_WARNING, "To address '%s' is not a valid SIP/SIPS URI\n", to);
+		return -1;
+	}
+
+	sip_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
+	if (DEBUG_ATLEAST(3)) {
+		buf = ast_alloca(DEBUG_BUF_SIZE);
+		pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, sip_uri, buf, DEBUG_BUF_SIZE);
+		ast_debug(3, "Parsed To: %.*s  %s\n", (int)parsed_name_addr->display.slen,
+			parsed_name_addr->display.ptr, buf);
+	}
+
+	to_hdr = PJSIP_MSG_TO_HDR(tdata->msg);
+	tdata_name_addr = to_hdr ? (pjsip_name_addr *) to_hdr->uri : NULL;
+	if (!tdata_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(tdata_name_addr->uri)
+			&& !PJSIP_URI_SCHEME_IS_SIPS(tdata_name_addr->uri))) {
+		/* Highly unlikely but we have to check */
+		ast_log(LOG_WARNING, "tdata To address '%s' is not a valid SIP/SIPS URI\n", to);
+		return -1;
+	}
+
+	tdata_sip_uri = pjsip_uri_get_uri(tdata_name_addr->uri);
+	if (DEBUG_ATLEAST(3)) {
+		buf[0] = '\0';
+		pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, DEBUG_BUF_SIZE);
+		ast_debug(3, "Original tdata To: %.*s  %s\n", (int)tdata_name_addr->display.slen,
+			tdata_name_addr->display.ptr, buf);
+	}
+
+	/* Replace the uri */
+	pjsip_sip_uri_assign(tdata->pool, tdata_sip_uri, sip_uri);
+	/* The display name isn't part of the URI so we need to replace it separately */
+	pj_strdup(tdata->pool, &tdata_name_addr->display, &parsed_name_addr->display);
+
+	if (DEBUG_ATLEAST(3)) {
+		buf[0] = '\0';
+		pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, 256);
+		ast_debug(3, "New tdata To: %.*s  %s\n", (int)tdata_name_addr->display.slen,
+			tdata_name_addr->display.ptr, buf);
+	}
+
+	return 0;
+#undef DEBUG_BUF_SIZE
+}
+
+int ast_sip_update_from(pjsip_tx_data *tdata, char *from)
+{
+	pjsip_name_addr *name_addr;
+	pjsip_sip_uri *uri;
+	pjsip_name_addr *parsed_name_addr;
+	pjsip_from_hdr *from_hdr;
+
+	if (ast_strlen_zero(from)) {
+		return 0;
+	}
+
+	from_hdr = PJSIP_MSG_FROM_HDR(tdata->msg);
+	if (!from_hdr) {
+		return -1;
+	}
+	name_addr = (pjsip_name_addr *) from_hdr->uri;
+	uri = pjsip_uri_get_uri(name_addr);
+
+	parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, from,
+		strlen(from), PJSIP_PARSE_URI_AS_NAMEADDR);
+	if (parsed_name_addr) {
+		pjsip_sip_uri *parsed_uri;
+
+		if (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
+				&& !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri)) {
+			ast_log(LOG_WARNING, "From address '%s' is not a valid SIP/SIPS URI\n", from);
+			return -1;
+		}
+
+		parsed_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
+
+		if (pj_strlen(&parsed_name_addr->display)) {
+			pj_strdup(tdata->pool, &name_addr->display, &parsed_name_addr->display);
+		}
+
+		/* Unlike the To header, we only want to replace the user, host and port */
+		pj_strdup(tdata->pool, &uri->user, &parsed_uri->user);
+		pj_strdup(tdata->pool, &uri->host, &parsed_uri->host);
+		uri->port = parsed_uri->port;
+
+		return 0;
+	} else {
+		/* assume it is 'user[@domain]' format */
+		char *domain = strchr(from, '@');
+
+		if (domain) {
+			pj_str_t pj_from;
+
+			pj_strset3(&pj_from, from, domain);
+			pj_strdup(tdata->pool, &uri->user, &pj_from);
+
+			pj_strdup2(tdata->pool, &uri->host, domain + 1);
+		} else {
+			pj_strdup2(tdata->pool, &uri->user, from);
+		}
+
+		return 0;
+	}
+
+	return -1;
+}
 
 static void remove_request_headers(pjsip_endpoint *endpt)
 {
diff --git a/res/res_pjsip_messaging.c b/res/res_pjsip_messaging.c
index 336d392cd40cc19bb438ca61f601f6fa99568954..51d8160b61c49761aa1c64dca766dfd3ad76d14e 100644
--- a/res/res_pjsip_messaging.c
+++ b/res/res_pjsip_messaging.c
@@ -195,571 +195,6 @@ static enum pjsip_status_code check_content_type_in_dialog(const pjsip_rx_data *
 	return res;
 }
 
-/*!
- * \brief Find a contact and insert a "user@" into its URI.
- *
- * \param to Original destination (for error messages only)
- * \param endpoint_name Endpoint name (for error messages only)
- * \param aors Command separated list of AORs
- * \param user The user to insert in the contact URI
- * \param uri Pointer to buffer in which to return the URI
- *
- * \return  0 Success
- * \return -1 Fail
- *
- * \note If the contact URI found for the endpoint already has a user in
- * its URI, it will be replaced.
- */
-static int insert_user_in_contact_uri(const char *to, const char *endpoint_name, const char *aors,
-	const char *user, char **uri)
-{
-	char *scheme = NULL;
-	char *contact_uri = NULL;
-	char *after_scheme = NULL;
-	char *host;
-	struct ast_sip_contact *contact = NULL;
-
-
-	contact = ast_sip_location_retrieve_contact_from_aor_list(aors);
-	if (!contact) {
-		/*
-		 * We're getting the contact using the same method as
-		 * ast_sip_create_request() so if there's no contact
-		 * we can never send this message.
-		 */
-		ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Couldn't find contact for endpoint '%s'\n",
-			to, endpoint_name);
-		return -1;
-	}
-
-	contact_uri = ast_strdupa(contact->uri);
-	ao2_cleanup(contact);
-
-	ast_debug(3, "Dest: '%s' User: '%s'  Endpoint: '%s'  ContactURI: '%s'\n", to, user, endpoint_name, contact_uri);
-
-	/*
-	 * Contact URIs must have a scheme so we must insert the user between it and the host.
-	 */
-	scheme = contact_uri;
-	after_scheme = strchr(contact_uri, ':');
-	if (!after_scheme) {
-		/* A contact URI without a scheme?  Something's wrong.  Bail */
-		ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: There was no scheme in the contact URI '%s'\n",
-			to, contact_uri);
-		return -1;
-	}
-	/*
-	 * Terminate the scheme.
-	 */
-	*after_scheme = '\0';
-	after_scheme++;
-
-	/*
-	 * If the contact_uri already has a user, the host starts after the '@', otherwise
-	 * the host is at after_scheme.
-	 *
-	 * We're going to ignore the existing user.
-	 */
-	host = strchr(after_scheme, '@');
-	if (host) {
-		host++;
-	} else {
-		host = after_scheme;
-	}
-
-	*uri = ast_malloc(strlen(scheme) + strlen(user) + strlen(host) + 3 /* One for the ':', '@' and terminating NULL */);
-	sprintf(*uri, "%s:%s@%s", scheme, user, host); /* Safe */
-
-	return 0;
-}
-
-/*!
- * \internal
- * \brief Get endpoint and URI when the destination is only a single token
- *
- * "to" could be one of the following:
- * \verbatim
-		endpoint_name
-		hostname
- * \endverbatim
- *
- * \param to Destination specified in MessageSend
- * \param destination
- * \param uri Pointer to URI variable.  Must be freed by caller
- * \return endpoint
- */
-static struct ast_sip_endpoint *handle_single_token(const char *to, char *destination, char **uri) {
-	char *endpoint_name = NULL;
-	struct ast_sip_endpoint *endpoint = NULL;
-	struct ast_sip_contact *contact = NULL;
-
-	/*
-	 * If "to" is just one token, it could be an endpoint name
-	 * or a hostname without a scheme.
-	 */
-
-	endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", destination);
-	if (!endpoint) {
-		/*
-		 * We can only assume it's a hostname.
-		 */
-		char *temp_uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
-		sprintf(temp_uri, "sip:%s", destination);
-		*uri = temp_uri;
-		endpoint = ast_sip_default_outbound_endpoint();
-		ast_debug(3, "Dest: '%s' Didn't find endpoint so adding scheme and using URI '%s' with default endpoint\n",
-			to, *uri);
-		return endpoint;
-	}
-
-	/*
-	 * It's an endpoint
-	 */
-
-	endpoint_name = destination;
-	contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
-	if (!contact) {
-		/*
-		 * We're getting the contact using the same method as
-		 * ast_sip_create_request() so if there's no contact
-		 * we can never send this message.
-		 */
-		ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Found endpoint '%s' but didn't find an aor/contact for it\n",
-			to, endpoint_name);
-		ao2_cleanup(endpoint);
-		*uri = NULL;
-		return NULL;
-	}
-
-	*uri = ast_strdup(contact->uri);
-	ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s'\n",
-		to, endpoint_name, *uri);
-	ao2_cleanup(contact);
-	return endpoint;
-
-}
-
-/*!
- * \internal
- * \brief Get endpoint and URI when the destination contained a '/'
- *
- * "to" could be one of the following:
- * \verbatim
-		endpoint/aor
-		endpoint/<sip[s]:host>
-		endpoint/<sip[s]:user@host>
-		endpoint/"Bob" <sip[s]:host>
-		endpoint/"Bob" <sip[s]:user@host>
-		endpoint/sip[s]:host
-		endpoint/sip[s]:user@host
-		endpoint/host
-		endpoint/user@host
- * \endverbatim
- *
- * \param to Destination specified in MessageSend
- * \param uri Pointer to URI variable.  Must be freed by caller
- * \param destination, slash, atsign, scheme
- * \return endpoint
- */
-static struct ast_sip_endpoint *handle_slash(const char *to, char *destination, char **uri,
-	char *slash, char *atsign, char *scheme)
-{
-	char *endpoint_name = NULL;
-	struct ast_sip_endpoint *endpoint = NULL;
-	struct ast_sip_contact *contact = NULL;
-	char *user = NULL;
-	char *afterslash = slash + 1;
-	struct ast_sip_aor *aor;
-
-	if (ast_begins_with(destination, "PJSIP/")) {
-		ast_debug(3, "Dest: '%s' Dialplan format'\n", to);
-		/*
-		 * This has to be the form PJSIP/user@endpoint
-		 */
-		if (!atsign || strchr(afterslash, '/')) {
-			/*
-			 * If there's no "user@" or there's a slash somewhere after
-			 * "PJSIP/" then we go no further.
-			 */
-			*uri = NULL;
-			ast_log(LOG_WARNING,
-				"Dest: '%s' MSG SEND FAIL: Destinations beginning with 'PJSIP/' must be in the form of 'PJSIP/user@endpoint'\n",
-				to);
-			return NULL;
-		}
-		*atsign = '\0';
-		user = afterslash;
-		endpoint_name = atsign + 1;
-		ast_debug(3, "Dest: '%s' User: '%s'  Endpoint: '%s'\n", to, user, endpoint_name);
-	} else {
-		/*
-		 * Either...
-		 *	endpoint/aor
-		 *	endpoint/uri
-		 */
-		*slash = '\0';
-		endpoint_name = destination;
-		ast_debug(3, "Dest: '%s' Endpoint: '%s'\n", to, endpoint_name);
-	}
-
-	endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
-	if (!endpoint) {
-		*uri = NULL;
-		ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Didn't find endpoint with name '%s'\n",
-			to, endpoint_name);
-		return NULL;
-	}
-
-	if (scheme) {
-		/*
-		 * If we found a scheme, then everything after the slash MUST be a URI.
-		 * We don't need to do any further modification.
-		 */
-		*uri = ast_strdup(afterslash);
-		ast_debug(3, "Dest: '%s' Found endpoint '%s' and found URI '%s' after '/'\n",
-			to, endpoint_name, *uri);
-		return endpoint;
-	}
-
-	if (user) {
-		/*
-		 * This has to be the form PJSIP/user@endpoint
-		 */
-		int rc;
-
-		/*
-		 * Set the return URI to be the endpoint's contact URI with the user
-		 * portion set to the user that was specified before the endpoint name.
-		 */
-		rc = insert_user_in_contact_uri(to, endpoint_name, endpoint->aors, user, uri);
-		if (rc != 0) {
-			/*
-			 * insert_user_in_contact_uri prints the warning message.
-			 */
-			ao2_cleanup(endpoint);
-			endpoint = NULL;
-			*uri = NULL;
-		}
-		ast_debug(3, "Dest: '%s' User: '%s'  Endpoint: '%s'  URI: '%s'\n", to, user,
-			endpoint_name, *uri);
-
-		return endpoint;
-	}
-
-	/*
-	 * We're now left with two possibilities...
-	 * 	endpoint/aor
-	 *	endpoint/uri-without-scheme
-	 */
-	aor = ast_sip_location_retrieve_aor(afterslash);
-	if (!aor) {
-		/*
-		 * It's probably a URI without a scheme but we don't have a way to tell
-		 * for sure.  We're going to assume it is and prepend it with a scheme.
-		 */
-		*uri = ast_malloc(strlen(afterslash) + strlen("sip:") + 1);
-		sprintf(*uri, "sip:%s", afterslash);
-		ast_debug(3, "Dest: '%s' Found endpoint '%s' but didn't find aor after '/' so using URI '%s'\n",
-			to, endpoint_name, *uri);
-		return endpoint;
-	}
-
-	/*
-	 * Only one possibility left... There was an aor name after the slash.
-	 */
-	ast_debug(3, "Dest: '%s' Found endpoint '%s' and found aor '%s' after '/'\n",
-		to, endpoint_name, ast_sorcery_object_get_id(aor));
-
-	contact = ast_sip_location_retrieve_first_aor_contact(aor);
-	if (!contact) {
-		/*
-		 * An aor without a contact is useless and since
-		 * ast_sip_create_message() won't be able to find one
-		 * either, we just need to bail.
-		 */
-		ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Found endpoint '%s' but didn't find contact for aor '%s'\n",
-			to, endpoint_name, ast_sorcery_object_get_id(aor));
-		ao2_cleanup(aor);
-		ao2_cleanup(endpoint);
-		*uri = NULL;
-		return NULL;
-	}
-
-	*uri = ast_strdup(contact->uri);
-	ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' for aor '%s'\n",
-		to, endpoint_name, *uri, ast_sorcery_object_get_id(aor));
-	ao2_cleanup(contact);
-	ao2_cleanup(aor);
-
-	return endpoint;
-}
-
-/*!
- * \internal
- * \brief Get endpoint and URI when the destination contained a '\@' but no '/' or scheme
- *
- * "to" could be one of the following:
- * \verbatim
-		<sip[s]:user@host>
-		"Bob" <sip[s]:user@host>
-		sip[s]:user@host
-		user@host
- * \endverbatim
- *
- * \param to Destination specified in MessageSend
- * \param uri Pointer to URI variable.  Must be freed by caller
- * \param destination, slash, atsign, scheme
- * \return endpoint
- */
-static struct ast_sip_endpoint *handle_atsign(const char *to, char *destination, char **uri,
-	char *slash, char *atsign, char *scheme)
-{
-	char *endpoint_name = NULL;
-	struct ast_sip_endpoint *endpoint = NULL;
-	struct ast_sip_contact *contact = NULL;
-	char *afterat = atsign + 1;
-
-	*atsign = '\0';
-	endpoint_name = destination;
-
-	/* Apparently there may be ';<user_options>' after the endpoint name ??? */
-	AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(endpoint_name);
-	endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
-	if (!endpoint) {
-		/*
-		 * It's probably a uri with a user but without a scheme but we don't have a way to tell.
-		 * We're going to assume it is and prepend it with a scheme.
-		 */
-		*uri = ast_malloc(strlen(to) + strlen("sip:") + 1);
-		sprintf(*uri, "sip:%s", to);
-		endpoint = ast_sip_default_outbound_endpoint();
-		ast_debug(3, "Dest: '%s' Didn't find endpoint before the '@' so using URI '%s' with default endpoint\n",
-			to, *uri);
-		return endpoint;
-	}
-
-	/*
-	 * OK, it's an endpoint and a domain (which we ignore)
-	 */
-	contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
-	if (!contact) {
-		/*
-		 * We're getting the contact using the same method as
-		 * ast_sip_create_request() so if there's no contact
-		 * we can never send this message.
-		 */
-		ao2_cleanup(endpoint);
-		endpoint = NULL;
-		*uri = NULL;
-		ast_log(LOG_WARNING, "Dest: '%s' MSG SEND FAIL: Found endpoint '%s' but didn't find contact\n",
-			to, endpoint_name);
-		return NULL;
-	}
-
-	*uri = ast_strdup(contact->uri);
-	ao2_cleanup(contact);
-	ast_debug(3, "Dest: '%s' Found endpoint '%s' and found contact with URI '%s' (discarding domain %s)\n",
-		to, endpoint_name, *uri, afterat);
-
-	return endpoint;
-}
-
-/*!
- * \internal
- * \brief Retrieves an endpoint and URI from the "to" string.
- *
- * This URI is used as the Request URI.
- *
- * Expects the given 'to' to be in one of the following formats:
- * Why we allow so many is a mystery.
- *
- * Basic:
- *
- *      endpoint        : We'll get URI from the default aor/contact
- *      endpoint/aor    : We'll get the URI from the specific aor/contact
- *      endpoint@domain : We toss the domain part and just use the endpoint
- *
- *   These all use the endpoint and specified URI:
- * \verbatim
-        endpoint/<sip[s]:host>
-        endpoint/<sip[s]:user@host>
-        endpoint/"Bob" <sip[s]:host>
-        endpoint/"Bob" <sip[s]:user@host>
-        endpoint/sip[s]:host
-        endpoint/sip[s]:user@host
-        endpoint/host
-        endpoint/user@host
-   \endverbatim
- *
- *   These all use the default endpoint and specified URI:
- * \verbatim
-        <sip[s]:host>
-        <sip[s]:user@host>
-        "Bob" <sip[s]:host>
-        "Bob" <sip[s]:user@host>
-        sip[s]:host
-        sip[s]:user@host
-   \endverbatim
- *
- *   These use the default endpoint and specified host:
- * \verbatim
-        host
-        user@host
-   \endverbatim
- *
- *   This form is similar to a dialstring:
- * \verbatim
-        PJSIP/user@endpoint
-   \endverbatim
- *
- *   In this case, the user will be added to the endpoint contact's URI.
- *   If the contact URI already has a user, it will be replaced.
- *
- * The ones that have the sip[s] scheme are the easiest to parse.
- * The rest all have some issue.
- *
- *      endpoint vs host              : We have to test for endpoint first
- *      endpoint/aor vs endpoint/host : We have to test for aor first
- *                                      What if there's an aor with the same
- *                                      name as the host?
- *      endpoint@domain vs user@host  : We have to test for endpoint first.
- *                                      What if there's an endpoint with the
- *                                      same name as the user?
- *
- * \param to 'To' field with possible endpoint
- * \param uri Pointer to a char* which will be set to the URI.
- *            Must be ast_free'd by the caller.
- *
- * \note  The logic below could probably be condensed but then it wouldn't be
- * as clear.
- */
-static struct ast_sip_endpoint *get_outbound_endpoint(const char *to, char **uri)
-{
-	char *destination;
-	char *slash = NULL;
-	char *atsign = NULL;
-	char *scheme = NULL;
-	struct ast_sip_endpoint *endpoint = NULL;
-
-	destination = ast_strdupa(to);
-	slash = strchr(destination, '/');
-	atsign = strchr(destination, '@');
-	scheme = S_OR(strstr(destination, "sip:"), strstr(destination, "sips:"));
-
-	if (!slash && !atsign && !scheme) {
-		/*
-		 * If there's only a single token, it can be either...
-		 * 	endpoint
-		 * 	host
-		 */
-		return handle_single_token(to, destination, uri);
-	}
-
-	if (slash) {
-		/*
-		 * If there's a '/', then the form must be one of the following...
-		 * 	PJSIP/user@endpoint
-		 * 	endpoint/aor
-		 * 	endpoint/uri
-		 */
-		return handle_slash(to, destination, uri, slash, atsign, scheme);
-	}
-
-	if (!endpoint && atsign && !scheme) {
-		/*
-		 * If there's an '@' but no scheme then it's either following an endpoint name
-		 * and being followed by a domain name (which we discard).
-		 * OR is's a user@host uri without a scheme.  It's probably the latter but because
-		 * endpoint@domain looks just like user@host, we'll test for endpoint first.
-		 */
-		return handle_atsign(to, destination, uri, slash, atsign, scheme);
-	}
-
-	/*
-	 * If all else fails, we assume it's a URI or just a hostname.
-	 */
-	if (scheme) {
-		*uri = ast_strdup(destination);
-		ast_debug(3, "Dest: '%s' Didn't find an endpoint but did find a scheme so using URI '%s' with default endpoint\n",
-			to, *uri);
-	} else {
-		*uri = ast_malloc(strlen(destination) + strlen("sip:") + 1);
-		sprintf(*uri, "sip:%s", destination);
-		ast_debug(3, "Dest: '%s' Didn't find an endpoint and didn't find scheme so adding scheme and using URI '%s' with default endpoint\n",
-			to, *uri);
-	}
-	endpoint = ast_sip_default_outbound_endpoint();
-
-	return endpoint;
-}
-
-/*!
- * \internal
- * \brief Replace the To URI in the tdata with the supplied one
- *
- * \param tdata the outbound message data structure
- * \param to URI to replace the To URI with
- *
- * \return 0: success, -1: failure
- */
-static int update_to_uri(pjsip_tx_data *tdata, char *to)
-{
-	pjsip_name_addr *parsed_name_addr;
-	pjsip_sip_uri *sip_uri;
-	pjsip_name_addr *tdata_name_addr;
-	pjsip_sip_uri *tdata_sip_uri;
-	char *buf = NULL;
-#define DEBUG_BUF_SIZE 256
-
-	parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, to, strlen(to),
-		PJSIP_PARSE_URI_AS_NAMEADDR);
-
-	if (!parsed_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
-			&& !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri))) {
-		ast_log(LOG_WARNING, "To address '%s' is not a valid SIP/SIPS URI\n", to);
-		return -1;
-	}
-
-	sip_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
-	if (DEBUG_ATLEAST(3)) {
-		buf = ast_alloca(DEBUG_BUF_SIZE);
-		pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, sip_uri, buf, DEBUG_BUF_SIZE);
-		ast_debug(3, "Parsed To: %.*s  %s\n", (int)parsed_name_addr->display.slen,
-			parsed_name_addr->display.ptr, buf);
-	}
-
-	tdata_name_addr = (pjsip_name_addr *) PJSIP_MSG_TO_HDR(tdata->msg)->uri;
-	if (!tdata_name_addr || (!PJSIP_URI_SCHEME_IS_SIP(tdata_name_addr->uri)
-			&& !PJSIP_URI_SCHEME_IS_SIPS(tdata_name_addr->uri))) {
-		/* Highly unlikely but we have to check */
-		ast_log(LOG_WARNING, "tdata To address '%s' is not a valid SIP/SIPS URI\n", to);
-		return -1;
-	}
-
-	tdata_sip_uri = pjsip_uri_get_uri(tdata_name_addr->uri);
-	if (DEBUG_ATLEAST(3)) {
-		buf[0] = '\0';
-		pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, DEBUG_BUF_SIZE);
-		ast_debug(3, "Original tdata To: %.*s  %s\n", (int)tdata_name_addr->display.slen,
-			tdata_name_addr->display.ptr, buf);
-	}
-
-	/* Replace the uri */
-	pjsip_sip_uri_assign(tdata->pool, tdata_sip_uri, sip_uri);
-	/* The display name isn't part of the URI so we need to replace it separately */
-	pj_strdup(tdata->pool, &tdata_name_addr->display, &parsed_name_addr->display);
-
-	if (DEBUG_ATLEAST(3)) {
-		buf[0] = '\0';
-		pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, tdata_sip_uri, buf, 256);
-		ast_debug(3, "New tdata To: %.*s  %s\n", (int)tdata_name_addr->display.slen,
-			tdata_name_addr->display.ptr, buf);
-	}
-
-	return 0;
-#undef DEBUG_BUF_SIZE
-}
-
 /*!
  * \internal
  * \brief Update the display name in the To uri in the tdata with the one from the supplied uri
@@ -790,77 +225,6 @@ static int update_to_display_name(pjsip_tx_data *tdata, char *to)
 	return -1;
 }
 
-/*!
- * \internal
- * \brief Overwrite fields in the outbound 'From' header
- *
- * The outbound 'From' header is created/added in ast_sip_create_request with
- * default data.  If available that data may be info specified in the 'from_user'
- * and 'from_domain' options found on the endpoint.  That information will be
- * overwritten with data in the given 'from' parameter.
- *
- * \param tdata the outbound message data structure
- * \param from info to copy into the header
- *
- * \return 0: success, -1: failure
- */
-static int update_from(pjsip_tx_data *tdata, char *from)
-{
-	pjsip_name_addr *name_addr;
-	pjsip_sip_uri *uri;
-	pjsip_name_addr *parsed_name_addr;
-
-	if (ast_strlen_zero(from)) {
-		return 0;
-	}
-
-	name_addr = (pjsip_name_addr *) PJSIP_MSG_FROM_HDR(tdata->msg)->uri;
-	uri = pjsip_uri_get_uri(name_addr);
-
-	parsed_name_addr = (pjsip_name_addr *) pjsip_parse_uri(tdata->pool, from,
-		strlen(from), PJSIP_PARSE_URI_AS_NAMEADDR);
-	if (parsed_name_addr) {
-		pjsip_sip_uri *parsed_uri;
-
-		if (!PJSIP_URI_SCHEME_IS_SIP(parsed_name_addr->uri)
-				&& !PJSIP_URI_SCHEME_IS_SIPS(parsed_name_addr->uri)) {
-			ast_log(LOG_WARNING, "From address '%s' is not a valid SIP/SIPS URI\n", from);
-			return -1;
-		}
-
-		parsed_uri = pjsip_uri_get_uri(parsed_name_addr->uri);
-
-		if (pj_strlen(&parsed_name_addr->display)) {
-			pj_strdup(tdata->pool, &name_addr->display, &parsed_name_addr->display);
-		}
-
-		/* Unlike the To header, we only want to replace the user, host and port */
-		pj_strdup(tdata->pool, &uri->user, &parsed_uri->user);
-		pj_strdup(tdata->pool, &uri->host, &parsed_uri->host);
-		uri->port = parsed_uri->port;
-
-		return 0;
-	} else {
-		/* assume it is 'user[@domain]' format */
-		char *domain = strchr(from, '@');
-
-		if (domain) {
-			pj_str_t pj_from;
-
-			pj_strset3(&pj_from, from, domain);
-			pj_strdup(tdata->pool, &uri->user, &pj_from);
-
-			pj_strdup2(tdata->pool, &uri->host, domain + 1);
-		} else {
-			pj_strdup2(tdata->pool, &uri->user, from);
-		}
-
-		return 0;
-	}
-
-	return -1;
-}
-
 /*!
  * \internal
  * \brief Checks if the given msg var name should be blocked.
@@ -1252,7 +616,7 @@ static int msg_send(void *data)
 	ast_debug(3, "mdata From: %s msg From: %s mdata Destination: %s msg To: %s\n",
 		mdata->from, ast_msg_get_from(mdata->msg), mdata->destination, ast_msg_get_to(mdata->msg));
 
-	endpoint = get_outbound_endpoint(mdata->destination, &uri);
+	endpoint = ast_sip_get_endpoint(mdata->destination, 1, &uri);
 	if (!endpoint) {
 		ast_log(LOG_ERROR,
 			"PJSIP MESSAGE - Could not find endpoint '%s' and no default outbound endpoint configured\n",
@@ -1290,7 +654,7 @@ static int msg_send(void *data)
 		if (ast_begins_with(msg_to, "pjsip:")) {
 			msg_to += 6;
 		}
-		update_to_uri(tdata, msg_to);
+		ast_sip_update_to_uri(tdata, msg_to);
 	} else {
 		/*
 		 * If there was no To in the message, it's still possible
@@ -1301,9 +665,9 @@ static int msg_send(void *data)
 	}
 
 	if (!ast_strlen_zero(mdata->from)) {
-		update_from(tdata, mdata->from);
+		ast_sip_update_from(tdata, mdata->from);
 	} else if (!ast_strlen_zero(ast_msg_get_from(mdata->msg))) {
-		update_from(tdata, (char *)ast_msg_get_from(mdata->msg));
+		ast_sip_update_from(tdata, (char *)ast_msg_get_from(mdata->msg));
 	}
 
 #ifdef TEST_FRAMEWORK
diff --git a/res/res_pjsip_nat.c b/res/res_pjsip_nat.c
index 59223f7bbf08f11db80611440757097409448332..62f8aa0b2364ac0ff1f8fb3df5723ec77255d727 100644
--- a/res/res_pjsip_nat.c
+++ b/res/res_pjsip_nat.c
@@ -235,52 +235,6 @@ static pj_bool_t nat_on_rx_message(pjsip_rx_data *rdata)
 	return res;
 }
 
-/*! \brief Structure which contains information about a transport */
-struct request_transport_details {
-	/*! \brief Type of transport */
-	enum ast_transport type;
-	/*! \brief Potential pointer to the transport itself, if UDP */
-	pjsip_transport *transport;
-	/*! \brief Potential pointer to the transport factory itself, if TCP/TLS */
-	pjsip_tpfactory *factory;
-	/*! \brief Local address for transport */
-	pj_str_t local_address;
-	/*! \brief Local port for transport */
-	int local_port;
-};
-
-/*! \brief Callback function for finding the transport the request is going out on */
-static int find_transport_state_in_use(void *obj, void *arg, int flags)
-{
-	struct ast_sip_transport_state *transport_state = obj;
-	struct request_transport_details *details = arg;
-
-	/* If an explicit transport or factory matches then this is what is in use, if we are unavailable
-	 * to compare based on that we make sure that the type is the same and the source IP address/port are the same
-	 */
-	if (transport_state && ((details->transport && details->transport == transport_state->transport) ||
-		(details->factory && details->factory == transport_state->factory) ||
-		((details->type == transport_state->type) && (transport_state->factory) &&
-			!pj_strcmp(&transport_state->factory->addr_name.host, &details->local_address) &&
-			transport_state->factory->addr_name.port == details->local_port))) {
-		return CMP_MATCH;
-	}
-
-	return 0;
-}
-
-/*! \brief Helper function which returns the SIP URI of a Contact header */
-static pjsip_sip_uri *nat_get_contact_sip_uri(pjsip_tx_data *tdata)
-{
-	pjsip_contact_hdr *contact = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
-
-	if (!contact || (!PJSIP_URI_SCHEME_IS_SIP(contact->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact->uri))) {
-		return NULL;
-	}
-
-	return pjsip_uri_get_uri(contact->uri);
-}
-
 /*! \brief Structure which contains hook details */
 struct nat_hook_details {
 	/*! \brief Outgoing message itself */
@@ -363,55 +317,22 @@ static void restore_orig_contact_host(pjsip_tx_data *tdata)
 
 static pj_status_t process_nat(pjsip_tx_data *tdata)
 {
-	RAII_VAR(struct ao2_container *, transport_states, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup);
-	struct request_transport_details details = { 0, };
 	pjsip_via_hdr *via = NULL;
+	struct ast_sip_request_transport_details details;
 	struct ast_sockaddr addr = { { 0, } };
 	pjsip_sip_uri *uri = NULL;
 	RAII_VAR(struct ao2_container *, hooks, NULL, ao2_cleanup);
 
-	/* If a transport selector is in use we know the transport or factory, so explicitly find it */
-	if (tdata->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
-		details.transport = tdata->tp_sel.u.transport;
-	} else if (tdata->tp_sel.type == PJSIP_TPSELECTOR_LISTENER) {
-		details.factory = tdata->tp_sel.u.listener;
-	} else if (tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_UDP || tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_UDP6) {
-		/* Connectionless uses the same transport for all requests */
-		details.type = AST_TRANSPORT_UDP;
-		details.transport = tdata->tp_info.transport;
-	} else {
-		if (tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_TCP) {
-			details.type = AST_TRANSPORT_TCP;
-		} else if (tdata->tp_info.transport->key.type == PJSIP_TRANSPORT_TLS) {
-			details.type = AST_TRANSPORT_TLS;
-		} else {
-			/* Unknown transport type, we can't map and thus can't apply NAT changes */
-			return PJ_SUCCESS;
-		}
-
-		if ((uri = nat_get_contact_sip_uri(tdata))) {
-			details.local_address = uri->host;
-			details.local_port = uri->port;
-		} else if ((tdata->msg->type == PJSIP_REQUEST_MSG) &&
-			(via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL))) {
-			details.local_address = via->sent_by.host;
-			details.local_port = via->sent_by.port;
-		} else {
-			return PJ_SUCCESS;
-		}
-
-		if (!details.local_port) {
-			details.local_port = (details.type == AST_TRANSPORT_TLS) ? 5061 : 5060;
-		}
-	}
-
-	if (!(transport_states = ast_sip_get_transport_states())) {
+	if (ast_sip_set_request_transport_details(&details, tdata, 0)) {
 		return PJ_SUCCESS;
 	}
 
-	if (!(transport_state = ao2_callback(transport_states, 0, find_transport_state_in_use, &details))) {
+	uri = ast_sip_get_contact_sip_uri(tdata);
+	via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+
+	if (!(transport_state = ast_sip_find_transport_state_in_use(&details))) {
 		return PJ_SUCCESS;
 	}
 
@@ -443,7 +364,7 @@ static pj_status_t process_nat(pjsip_tx_data *tdata)
 		if (!cseq || tdata->msg->type == PJSIP_REQUEST_MSG ||
 			pjsip_method_cmp(&cseq->method, &pjsip_register_method)) {
 			/* We can only rewrite the URI when one is present */
-			if (uri || (uri = nat_get_contact_sip_uri(tdata))) {
+			if (uri || (uri = ast_sip_get_contact_sip_uri(tdata))) {
 				pj_strdup2(tdata->pool, &uri->host, ast_sockaddr_stringify_host(&transport_state->external_signaling_address));
 				if (transport->external_signaling_port) {
 					uri->port = transport->external_signaling_port;
diff --git a/res/res_pjsip_refer.c b/res/res_pjsip_refer.c
index a20bf6ba4de04da5cc7e68fb0ef9a5a3943e515e..a10a9fde506e2ac2ae1da6f527b52a21aa567601 100644
--- a/res/res_pjsip_refer.c
+++ b/res/res_pjsip_refer.c
@@ -39,6 +39,11 @@
 #include "asterisk/stasis_bridges.h"
 #include "asterisk/stasis_channels.h"
 #include "asterisk/causes.h"
+#include "asterisk/refer.h"
+
+static struct ast_taskprocessor *refer_serializer;
+
+static pj_status_t refer_on_tx_request(pjsip_tx_data *tdata);
 
 /*! \brief REFER Progress structure */
 struct refer_progress {
@@ -786,6 +791,499 @@ static void refer_blind_callback(struct ast_channel *chan, struct transfer_chann
 		ast_channel_unlock((session)->channel);											\
 	} while (0)																			\
 
+struct refer_data {
+	struct ast_refer *refer;
+	char *destination;
+	char *from;
+	char *refer_to;
+	int to_self;
+};
+
+static void refer_data_destroy(void *obj)
+{
+	struct refer_data *rdata = obj;
+
+	ast_free(rdata->destination);
+	ast_free(rdata->from);
+	ast_free(rdata->refer_to);
+
+	ast_refer_destroy(rdata->refer);
+}
+
+static struct refer_data *refer_data_create(const struct ast_refer *refer)
+{
+	char *uri_params;
+	const char *destination;
+	struct refer_data *rdata = ao2_alloc_options(sizeof(*rdata), refer_data_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+
+	if (!rdata) {
+		return NULL;
+	}
+
+	/* typecast to suppress const warning */
+	rdata->refer = ast_refer_ref((struct ast_refer *) refer);
+	destination = ast_refer_get_to(refer);
+
+	/* To starts with 'pjsip:' which needs to be removed. */
+	if (!(destination = strchr(destination, ':'))) {
+		goto failure;
+	}
+	++destination;/* Now skip the ':' */
+
+	rdata->destination = ast_strdup(destination);
+	if (!rdata->destination) {
+		goto failure;
+	}
+
+	rdata->from = ast_strdup(ast_refer_get_from(refer));
+	if (!rdata->from) {
+		goto failure;
+	}
+
+	rdata->refer_to = ast_strdup(ast_refer_get_refer_to(refer));
+	if (!rdata->refer_to) {
+		goto failure;
+	}
+	rdata->to_self = ast_refer_get_to_self(refer);
+
+	/*
+	 * Sometimes from URI can contain URI parameters, so remove them.
+	 *
+	 * sip:user;user-options@domain;uri-parameters
+	 */
+	uri_params = strchr(rdata->from, '@');
+	if (uri_params && (uri_params = strchr(uri_params, ';'))) {
+		*uri_params = '\0';
+	}
+	return rdata;
+
+failure:
+	ao2_cleanup(rdata);
+	return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Checks if the given refer var name should be blocked.
+ *
+ * \details Some headers are not allowed to be overridden by the user.
+ *  Determine if the given var header name from the user is blocked for
+ *  an outgoing REFER.
+ *
+ * \param name name of header to see if it is blocked.
+ *
+ * \retval TRUE if the given header is blocked.
+ */
+static int is_refer_var_blocked(const char *name)
+{
+	int i;
+
+	/* Don't block the Max-Forwards header because the user can override it */
+	static const char *hdr[] = {
+		"To",
+		"From",
+		"Via",
+		"Route",
+		"Contact",
+		"Call-ID",
+		"CSeq",
+		"Allow",
+		"Content-Length",
+		"Content-Type",
+		"Request-URI",
+	};
+
+	for (i = 0; i < ARRAY_LEN(hdr); ++i) {
+		if (!strcasecmp(name, hdr[i])) {
+			/* Block addition of this header. */
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Copies any other refer vars over to the request headers.
+ *
+ * \param refer The refer structure to copy headers from
+ * \param tdata The SIP transmission data
+ */
+static enum pjsip_status_code vars_to_headers(const struct ast_refer *refer, pjsip_tx_data *tdata)
+{
+	const char *name;
+	const char *value;
+	struct ast_refer_var_iterator *iter;
+
+	for (iter = ast_refer_var_iterator_init(refer);
+		ast_refer_var_iterator_next(iter, &name, &value);
+		ast_refer_var_unref_current(iter)) {
+		if (!is_refer_var_blocked(name)) {
+			ast_sip_add_header(tdata, name, value);
+		}
+	}
+	ast_refer_var_iterator_destroy(iter);
+
+	return PJSIP_SC_OK;
+}
+
+struct refer_out_of_dialog {
+	pjsip_dialog *dlg;
+	int authentication_challenge_count;
+};
+
+/*! \brief REFER Out-of-dialog module, used to attach session data structure to subscription */
+static pjsip_module refer_out_of_dialog_module = {
+	.name = { "REFER Out-of-dialog Module", 26 },
+	.id = -1,
+	.on_tx_request = refer_on_tx_request,
+	/* Ensure that we are called after res_pjsp_nat module and before transport priority */
+	.priority = PJSIP_MOD_PRIORITY_TSX_LAYER - 4,
+};
+
+/*! \brief Helper function which returns the name-addr of the Refer-To header or NULL */
+static pjsip_uri *get_refer_to_uri(pjsip_tx_data *tdata)
+{
+	const pj_str_t REFER_TO = { "Refer-To", 8 };
+	pjsip_generic_string_hdr *refer_to;
+	pjsip_uri *parsed_uri;
+
+	if (!(refer_to = pjsip_msg_find_hdr_by_name(tdata->msg, &REFER_TO, NULL))
+		|| !(parsed_uri = pjsip_parse_uri(tdata->pool, refer_to->hvalue.ptr, refer_to->hvalue.slen, 0))
+		|| (!PJSIP_URI_SCHEME_IS_SIP(parsed_uri) && !PJSIP_URI_SCHEME_IS_SIPS(parsed_uri))) {
+		return NULL;
+	}
+
+	return parsed_uri;
+}
+
+static pj_status_t refer_on_tx_request(pjsip_tx_data *tdata) {
+	RAII_VAR(struct ast_str *, refer_to_str, ast_str_create(PJSIP_MAX_URL_SIZE), ast_free_ptr);
+	const pj_str_t REFER_TO = { "Refer-To", 8 };
+	pjsip_generic_string_hdr *refer_to_hdr;
+	pjsip_dialog *dlg;
+	struct refer_data *refer_data;
+	pjsip_uri *parsed_uri;
+	pjsip_sip_uri *refer_to_uri;
+
+	/*
+	 * If this is a request in response to a 401/407 Unauthorized challenge, the
+	 * Refer-To URI has been rewritten already, so don't attempt to re-write it again.
+	 * Checking for presence of the Authorization header is not an ideal solution. We do this because
+	 * there exists some race condition where this dialog is not the same as the one used
+	 * to send the original request in which case we don't have the correct refer_data.
+	 */
+	if (!refer_to_str
+		|| pjsip_msg_find_hdr(tdata->msg, PJSIP_H_AUTHORIZATION, NULL)
+		|| !(dlg = pjsip_tdata_get_dlg(tdata))
+		|| !(refer_data = pjsip_dlg_get_mod_data(dlg, refer_out_of_dialog_module.id))
+		|| !refer_data->to_self
+		|| !(parsed_uri = get_refer_to_uri(tdata))) {
+		goto out;
+	}
+	refer_to_uri = pjsip_uri_get_uri(parsed_uri);
+	ast_sip_rewrite_uri_to_local(refer_to_uri, tdata);
+
+	pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, parsed_uri, ast_str_buffer(refer_to_str), ast_str_size(refer_to_str));
+	refer_to_hdr = pjsip_msg_find_hdr_by_name(tdata->msg, &REFER_TO, NULL);
+	pj_strdup2(tdata->pool, &refer_to_hdr->hvalue, ast_str_buffer(refer_to_str));
+
+out:
+	return PJ_SUCCESS;
+}
+
+static int refer_unreference_dialog(void *obj)
+{
+	struct refer_out_of_dialog *data = obj;
+
+	/* This is why we keep the dialog on the subscription. When the subscription
+	 * is destroyed, there is no guarantee that the underlying dialog is ready
+	 * to be destroyed. Furthermore, there's no guarantee in the opposite direction
+	 * either. The dialog could be destroyed before our subscription is. We fix
+	 * this problem by keeping a reference to the dialog until it is time to
+	 * destroy the subscription.
+	 */
+	pjsip_dlg_dec_session(data->dlg, &refer_out_of_dialog_module);
+	data->dlg = NULL;
+
+	return 0;
+}
+/*! \brief Destructor for REFER out of dialog structure */
+static void refer_out_of_dialog_destroy(void *obj) {
+	struct refer_out_of_dialog *data = obj;
+
+	if (data->dlg) {
+		/* ast_sip_push_task_wait_servant should not be called in a destructor,
+		 * however in this case it seems to be fine.
+		 */
+		ast_sip_push_task_wait_servant(refer_serializer, refer_unreference_dialog, data);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Callback function to report status of implicit REFER-NOTIFY subscription.
+ *
+ * This function will be called on any state change in the REFER-NOTIFY subscription.
+ * Its primary purpose is to report SUCCESS/FAILURE of a refer initiated via
+ * \ref refer_send as well as to terminate the subscription, if necessary.
+ */
+static void refer_client_on_evsub_state(pjsip_evsub *sub, pjsip_event *event)
+{
+	pjsip_tx_data *tdata;
+	RAII_VAR(struct ast_sip_endpoint *, endpt, NULL, ao2_cleanup);
+	struct refer_out_of_dialog *refer_data;
+	int refer_success;
+	int res = 0;
+
+	if (!event) {
+		return;
+	}
+
+	refer_data = pjsip_evsub_get_mod_data(sub, refer_out_of_dialog_module.id);
+	if (!refer_data || !refer_data->dlg) {
+		return;
+	}
+
+	endpt = ast_sip_dialog_get_endpoint(refer_data->dlg);
+
+	if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACCEPTED) {
+		/* Check if subscription is suppressed and terminate and send completion code, if so. */
+		pjsip_rx_data *rdata;
+		pjsip_generic_string_hdr *refer_sub;
+		const pj_str_t REFER_SUB = { "Refer-Sub", 9 };
+
+		ast_debug(3, "Refer accepted by %s\n", endpt ? ast_sorcery_object_get_id(endpt) : "(unknown endpoint)");
+
+		/* Check if response message */
+		if (event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+			rdata = event->body.tsx_state.src.rdata;
+
+			/* Find Refer-Sub header */
+			refer_sub = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &REFER_SUB, NULL);
+
+			/* Check if subscription is suppressed. If it is, the far end will not terminate it,
+			 * and the subscription will remain active until it times out.  Terminating it here
+			 * eliminates the unnecessary timeout.
+			 */
+			if (refer_sub && !pj_stricmp2(&refer_sub->hvalue, "false")) {
+				/* Since no subscription is desired, assume that call has been referred successfully
+				 * and terminate subscription.
+				 */
+				pjsip_evsub_set_mod_data(sub, refer_out_of_dialog_module.id, NULL);
+				pjsip_evsub_terminate(sub, PJ_TRUE);
+				res = -1;
+			}
+		}
+	} else if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACTIVE ||
+			pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+		/* Check for NOTIFY complete or error. */
+		pjsip_msg *msg;
+		pjsip_msg_body *body;
+		pjsip_status_line status_line = { .code = 0 };
+		pj_bool_t is_last;
+		pj_status_t status;
+
+		if (event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+			pjsip_rx_data *rdata;
+			pj_str_t refer_str;
+			pj_cstr(&refer_str, "REFER");
+
+			rdata = event->body.tsx_state.src.rdata;
+			msg = rdata->msg_info.msg;
+
+			if (msg->type == PJSIP_RESPONSE_MSG
+				&& (event->body.tsx_state.tsx->status_code == 401
+					|| event->body.tsx_state.tsx->status_code == 407)
+				&& pj_stristr(&refer_str, &event->body.tsx_state.tsx->method.name)
+				&& ++refer_data->authentication_challenge_count < MAX_RX_CHALLENGES
+				&& endpt) {
+
+				if (!ast_sip_create_request_with_auth(&endpt->outbound_auths,
+					event->body.tsx_state.src.rdata, event->body.tsx_state.tsx->last_tx, &tdata)) {
+					/* Send authed REFER */
+					ast_sip_send_request(tdata, refer_data->dlg, NULL, NULL, NULL);
+					goto out;
+				}
+			}
+
+			if (msg->type == PJSIP_REQUEST_MSG) {
+				if (!pjsip_method_cmp(&msg->line.req.method, pjsip_get_notify_method())) {
+					body = msg->body;
+					if (body && !pj_stricmp2(&body->content_type.type, "message")
+						&& !pj_stricmp2(&body->content_type.subtype, "sipfrag")) {
+						pjsip_parse_status_line((char *)body->data, body->len, &status_line);
+					}
+				}
+			} else {
+				status_line.code = msg->line.status.code;
+				status_line.reason = msg->line.status.reason;
+			}
+		} else {
+			status_line.code = 500;
+			status_line.reason = *pjsip_get_status_text(500);
+		}
+
+		is_last = (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED);
+		/* If the status code is >= 200, the subscription is finished. */
+		if (status_line.code >= 200 || is_last) {
+			res = -1;
+
+			refer_success = status_line.code >= 200 && status_line.code < 300;
+
+			/* If subscription not terminated and subscription is finished (status code >= 200)
+			 * terminate it */
+			if (!is_last) {
+				pjsip_tx_data *tdata;
+
+				status = pjsip_evsub_initiate(sub, pjsip_get_subscribe_method(), 0, &tdata);
+				if (status == PJ_SUCCESS) {
+					pjsip_evsub_send_request(sub, tdata);
+				}
+			}
+			ast_debug(3, "Refer completed: %d %.*s (%s)\n",
+					status_line.code,
+					(int)status_line.reason.slen, status_line.reason.ptr,
+					refer_success ? "Success" : "Failure");
+		}
+	}
+
+out:
+	if (res) {
+		ao2_cleanup(refer_data);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Send a REFER
+ *
+ * \param data The outbound refer data structure
+ *
+ * \return 0: success, -1: failure
+ */
+static int refer_send(void *data)
+{
+	struct refer_data *rdata = data; /* The caller holds a reference */
+	pjsip_tx_data *tdata;
+	pjsip_evsub *sub;
+	pj_str_t tmp;
+	char refer_to_str[PJSIP_MAX_URL_SIZE];
+	char disp_name_escaped[128];
+	struct refer_out_of_dialog *refer;
+	struct pjsip_evsub_user xfer_cb;
+	RAII_VAR(char *, uri, NULL, ast_free);
+	RAII_VAR(char *, tmp_str, NULL, ast_free);
+	RAII_VAR(char *, display_name, NULL, ast_free);
+	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_sip_endpoint *, refer_to_endpoint, NULL, ao2_cleanup);
+
+	endpoint = ast_sip_get_endpoint(rdata->destination, 1, &uri);
+	if (!endpoint) {
+		ast_log(LOG_ERROR,
+			"PJSIP REFER - Could not find endpoint '%s' and no default outbound endpoint configured\n",
+			rdata->destination);
+		return -1;
+	}
+	ast_debug(3, "Request URI: %s\n", uri);
+
+	refer_to_endpoint = ast_sip_get_endpoint(rdata->refer_to, 0, &tmp_str);
+	if (!tmp_str) {
+		ast_log(LOG_WARNING, "PJSIP REFER - Refer to not a valid resource identifier or SIP URI\n");
+		return -1;
+	}
+	if (!(refer = ao2_alloc(sizeof(struct refer_out_of_dialog), refer_out_of_dialog_destroy))) {
+		ast_log(LOG_ERROR, "PJSIP REFER - Could not allocate resources.\n");
+		return -1;
+	}
+	/* The dialog will be terminated in the subscription event callback
+	 * when the subscription has terminated. */
+	refer->authentication_challenge_count = 0;
+	refer->dlg = ast_sip_create_dialog_uac(endpoint, uri, NULL);
+	if (!refer->dlg) {
+		ast_log(LOG_WARNING, "PJSIP REFER - Could not create dialog\n");
+		ao2_cleanup(refer);
+		return -1;
+	}
+	ast_sip_dialog_set_endpoint(refer->dlg, endpoint);
+
+	pj_bzero(&xfer_cb, sizeof(xfer_cb));
+	xfer_cb.on_evsub_state = &refer_client_on_evsub_state;
+	if (pjsip_xfer_create_uac(refer->dlg, &xfer_cb, &sub) != PJ_SUCCESS) {
+		ast_log(LOG_WARNING, "PJSIP REFER - Could not create uac\n");
+		ao2_cleanup(refer);
+		return -1;
+	}
+
+	display_name = ast_refer_get_var_and_unlink(rdata->refer, "display_name");
+	if (display_name) {
+		ast_escape_quoted(display_name, disp_name_escaped, sizeof(disp_name_escaped));
+		snprintf(refer_to_str, sizeof(refer_to_str), "\"%s\" <%s>", disp_name_escaped, tmp_str);
+	} else {
+		snprintf(refer_to_str, sizeof(refer_to_str), "%s", tmp_str);
+	}
+
+	/* refer_out_of_dialog_module requires a reference to dlg
+	 * which will be released in refer_client_on_evsub_state()
+	 * when the implicit REFER subscription terminates */
+	pjsip_evsub_set_mod_data(sub, refer_out_of_dialog_module.id, refer);
+	if (pjsip_xfer_initiate(sub, pj_cstr(&tmp, refer_to_str), &tdata) != PJ_SUCCESS) {
+		ast_log(LOG_WARNING, "PJSIP REFER - Could not create request\n");
+		goto failure;
+	}
+
+	if (refer_to_endpoint && rdata->to_self) {
+		pjsip_dlg_add_usage(refer->dlg, &refer_out_of_dialog_module, rdata);
+	}
+
+	ast_sip_update_to_uri(tdata, uri);
+	ast_sip_update_from(tdata, rdata->from);
+
+	/*
+	 * This copies any headers found in the refer's variables to
+	 * tdata.
+	 */
+	vars_to_headers(rdata->refer, tdata);
+	ast_debug(1, "Sending REFER to '%s' (via endpoint %s) from '%s'\n",
+		rdata->destination, ast_sorcery_object_get_id(endpoint), rdata->from);
+
+	if (pjsip_xfer_send_request(sub, tdata) == PJ_SUCCESS) {
+		return 0;
+	}
+
+failure:
+	ao2_cleanup(refer);
+	pjsip_evsub_set_mod_data(sub, refer_out_of_dialog_module.id, NULL);
+	pjsip_evsub_terminate(sub, PJ_FALSE);
+	return -1;
+}
+
+static int sip_refer_send(const struct ast_refer *refer)
+{
+	struct refer_data *rdata;
+	int res;
+
+	if (ast_strlen_zero(ast_refer_get_to(refer))) {
+		ast_log(LOG_ERROR, "SIP REFER - a 'To' URI  must be specified\n");
+		return -1;
+	}
+
+	rdata = refer_data_create(refer);
+	if (!rdata) {
+		return -1;
+	}
+
+	res = ast_sip_push_task_wait_serializer(refer_serializer, refer_send, rdata);
+	ao2_ref(rdata, -1);
+
+	return res;
+}
+
+static const struct ast_refer_tech refer_tech = {
+	.name = "pjsip",
+	.refer_send = sip_refer_send,
+};
+
 static int refer_incoming_attended_request(struct ast_sip_session *session, pjsip_rx_data *rdata, pjsip_sip_uri *target_uri,
 	pjsip_param *replaces_param, struct refer_progress *progress)
 {
@@ -1274,6 +1772,17 @@ static int load_module(void)
 		pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_SUPPORTED, NULL, 1, &str_norefersub);
 	}
 
+	if (ast_refer_tech_register(&refer_tech)) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	refer_serializer = ast_sip_create_serializer("pjsip/refer");
+	if (!refer_serializer) {
+		ast_refer_tech_unregister(&refer_tech);
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	ast_sip_register_service(&refer_out_of_dialog_module);
 	ast_sip_register_service(&refer_progress_module);
 	ast_sip_session_register_supplement(&refer_supplement);
 
@@ -1285,7 +1794,9 @@ static int load_module(void)
 static int unload_module(void)
 {
 	ast_sip_session_unregister_supplement(&refer_supplement);
+	ast_sip_unregister_service(&refer_out_of_dialog_module);
 	ast_sip_unregister_service(&refer_progress_module);
+	ast_taskprocessor_unreference(refer_serializer);
 
 	return 0;
 }
diff --git a/rest-api/api-docs/endpoints.json b/rest-api/api-docs/endpoints.json
index 80baf97bcc7a7ee14f03eb6273c6028195f84595..3f3f98cec197b29f5dca6072ed1e31cf626ddee8 100644
--- a/rest-api/api-docs/endpoints.json
+++ b/rest-api/api-docs/endpoints.json
@@ -55,7 +55,7 @@
 						},
 						{
 							"name": "variables",
-							"descriptioni": "The \"variables\" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip resource types will add the key/value pairs as SIP headers,",
+							"descriptioni": "The \"variables\" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip and sip resource types will add the key/value pairs as SIP headers,",
 							"paramType": "body",
 							"required": false,
 							"dataType": "containers",
@@ -75,6 +75,71 @@
 				}
 			]
 		},
+		{
+			"path": "/endpoints/refer",
+			"description": "Refer an endpoint or technology URI to some technology URI or endpoint.",
+			"operations": [
+				{
+					"httpMethod": "POST",
+					"summary": "Refer an endpoint or technology URI to some technology URI or endpoint.",
+					"nickname": "refer",
+					"responseClass": "void",
+					"parameters": [
+						{
+							"name": "to",
+							"description": "The endpoint resource or technology specific URI that should be referred to somewhere. Valid resource is pjsip.",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "from",
+							"description": "The endpoint resource or technology specific identity to refer from.",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "refer_to",
+							"description": "The endpoint resource or technology specific URI to refer to.",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "to_self",
+							"description": "If true and \"refer_to\" refers to an Asterisk endpoint, the \"refer_to\" value is set to point to this Asterisk endpoint - so the referee is referred to Asterisk. Otherwise, use the contact URI associated with the endpoint.",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "boolean",
+							"defaultValue": false
+						},
+						{
+							"name": "variables",
+							"description": "The \"variables\" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, the pjsip resource type will add the key/value pairs as SIP headers. The \"display_name\" key is used by the PJSIP technology. Its value will be prepended as a display name to the Refer-To URI.",
+							"paramType": "body",
+							"required": false,
+							"dataType": "containers",
+							"allowMultiple": false
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 400,
+							"reason": "Invalid parameters for referring."
+						},
+						{
+							"code": 404,
+							"reason": "Endpoint not found"
+						}
+					]
+				}
+			]
+		},
 		{
 			"path": "/endpoints/{tech}",
 			"description": "Asterisk endpoints",
@@ -177,7 +242,7 @@
 						},
 						{
 							"name": "variables",
-							"descriptioni": "The \"variables\" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip resource types will add the key/value pairs as SIP headers,",
+							"descriptioni": "The \"variables\" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, pjsip and sip resource types will add the key/value pairs as SIP headers,",
 							"paramType": "body",
 							"required": false,
 							"dataType": "containers",
@@ -196,6 +261,75 @@
 					]
 				}
 			]
+		},
+		{
+			"path": "/endpoints/{tech}/{resource}/refer",
+			"description": "Refer an endpoint in a technology to some technology URI or endpoint..",
+			"operations": [
+				{
+					"httpMethod": "POST",
+					"summary": "Refer an endpoint or technology URI to some technology URI or endpoint.",
+					"nickname": "referToEndpoint",
+					"responseClass": "void",
+					"parameters": [
+						{
+							"name": "tech",
+							"description": "Technology of the endpoint",
+							"paramType": "path",
+							"dataType": "string"
+						},
+						{
+							"name": "resource",
+							"description": "ID of the endpoint",
+							"paramType": "path",
+							"dataType": "string"
+						},
+						{
+							"name": "from",
+							"description": "The endpoint resource or technology specific identity to refer from.",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "refer_to",
+							"description": "The endpoint resource or technology specific URI to refer to.",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "to_self",
+							"description": "If true and \"refer_to\" refers to an Asterisk endpoint, the \"refer_to\" value is set to point to this Asterisk endpoint - so the referee is referred to Asterisk. Otherwise, use the contact URI associated with the endpoint.",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "boolean",
+							"defaultValue": false
+						},
+						{
+							"name": "variables",
+							"description": "The \"variables\" key in the body object holds technology specific key/value pairs to append to the message. These can be interpreted and used by the various resource types; for example, the pjsip resource type will add the key/value pairs as SIP headers,",
+							"paramType": "body",
+							"required": false,
+							"dataType": "containers",
+							"allowMultiple": false
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 400,
+							"reason": "Invalid parameters for referring."
+						},
+						{
+							"code": 404,
+							"reason": "Endpoint not found"
+						}
+					]
+				}
+			]
 		}
 	],
 	"models": {