diff --git a/CHANGES b/CHANGES
index 4237c82e29e714740a74d366879105649b5d21bf..7e54a2095660b89531c1e548b7d559eac2697d1e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -139,6 +139,14 @@ res_pjsip
  * A new CLI command has been added: "pjsip show settings", which shows
    both the global and system configuration settings.
 
+ * A new aor option has been added: "qualify_timeout", which sets the timeout
+   in seconds for a qualify.  The default is 3 seconds.  This overrides the
+   hard coded 32 seconds in pjproject.
+
+ * Endpoint status will now change to "Unreachable" when all contacts are
+   unavailable.  When any contact becomes available, the endpoint will status
+   will change back to "Reachable".
+
 res_ari_channels
 ------------------
  * Two new events, 'ChannelHold' and 'ChannelUnhold', have been added to the
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index d3bb518f15fbed45a19359ca30379b0a114840e4..57b712a873a983306f86a4051a9646e92e92ed94 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -812,6 +812,7 @@
                         ; (default: "no")
 ;type=  ; Must be of type aor (default: "")
 ;qualify_frequency=0    ; Interval at which to qualify an AoR (default: "0")
+;qualify_timeout=3.0      ; Qualify timeout in fractional seconds (default: "3.0")
 ;authenticate_qualify=no        ; Authenticates a qualify request if needed
                                 ; (default: "no")
 ;outbound_proxy=        ; Outbound proxy used when sending OPTIONS request
diff --git a/contrib/ast-db-manage/config/versions/461d7d691209_add_pjsip_qualify_timeout.py b/contrib/ast-db-manage/config/versions/461d7d691209_add_pjsip_qualify_timeout.py
new file mode 100644
index 0000000000000000000000000000000000000000..9600c04611b1934e9c6f5d31b47be81f22f6f5dd
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/461d7d691209_add_pjsip_qualify_timeout.py
@@ -0,0 +1,25 @@
+"""add pjsip qualify_timeout
+
+Revision ID: 461d7d691209
+Revises: 31cd4f4891ec
+Create Date: 2015-04-15 13:54:08.047851
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '461d7d691209'
+down_revision = '31cd4f4891ec'
+
+from alembic import op
+import sqlalchemy as sa
+
+def upgrade():
+    op.add_column('ps_aors', sa.Column('qualify_timeout', sa.Integer))
+    op.add_column('ps_contacts', sa.Column('qualify_timeout', sa.Integer))
+    pass
+
+
+def downgrade():
+    op.drop_column('ps_aors', 'qualify_timeout')
+    op.drop_column('ps_contacts', 'qualify_timeout')
+    pass
diff --git a/include/asterisk/endpoints.h b/include/asterisk/endpoints.h
index 663dd94d9c80fcb5211201010f8c61ffcdd12b74..c9cb6b9de73da9eeb2f5a86dfa6fe8f3cb8a5dd6 100644
--- a/include/asterisk/endpoints.h
+++ b/include/asterisk/endpoints.h
@@ -159,6 +159,16 @@ const char *ast_endpoint_get_resource(const struct ast_endpoint *endpoint);
  */
 const char *ast_endpoint_get_id(const struct ast_endpoint *endpoint);
 
+/*!
+ * \brief Gets the state of the given endpoint.
+ *
+ * \param endpoint The endpoint.
+ * \return state.
+ * \return \c AST_ENDPOINT_UNKNOWN if endpoint is \c NULL.
+ * \since 13.4
+ */
+enum ast_endpoint_state ast_endpoint_get_state(const struct ast_endpoint *endpoint);
+
 /*!
  * \brief Updates the state of the given endpoint.
  *
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 0610c95e72f376e594d5fac137d05e795fca0c63..184cb57451ba09ad4e2501f31307d078f35f5a8a 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -166,6 +166,8 @@ struct ast_sip_contact {
 	unsigned int qualify_frequency;
 	/*! If true authenticate the qualify if needed */
 	int authenticate_qualify;
+	/*! Qualify timeout. 0 is diabled. */
+	double qualify_timeout;
 };
 
 #define CONTACT_STATUS "contact_status"
@@ -192,6 +194,8 @@ struct ast_sip_contact_status {
 	struct timeval rtt_start;
 	/*! The round trip time in microseconds */
 	int64_t rtt;
+	/*! Last status for a contact (default - unavailable) */
+	enum ast_sip_contact_status_type last_status;
 };
 
 /*!
@@ -224,6 +228,8 @@ struct ast_sip_aor {
 	struct ao2_container *permanent_contacts;
 	/*! Determines whether SIP Path headers are supported */
 	unsigned int support_path;
+	/*! Qualify timeout. 0 is diabled. */
+	double qualify_timeout;
 };
 
 /*!
@@ -904,6 +910,15 @@ struct ao2_container *ast_sip_location_retrieve_aor_contacts(const struct ast_si
  */
 struct ast_sip_contact *ast_sip_location_retrieve_contact_from_aor_list(const char *aor_list);
 
+/*!
+ * \brief Retrieve all contacts from a list of AORs
+ *
+ * \param aor_list A comma-separated list of AOR names
+ * \retval NULL if no contacts available
+ * \retval non-NULL container (which must be freed) if contacts available
+ */
+struct ao2_container *ast_sip_location_retrieve_contacts_from_aor_list(const char *aor_list);
+
 /*!
  * \brief Retrieve the first bound contact AND the AOR chosen from a list of AORs
  *
diff --git a/main/endpoints.c b/main/endpoints.c
index c70170b41f4f4e3cea8fce626917fa0217961ff9..df9d289c7f0950c2ce8ba4e872709f0ed022e322 100644
--- a/main/endpoints.c
+++ b/main/endpoints.c
@@ -415,6 +415,14 @@ const char *ast_endpoint_get_id(const struct ast_endpoint *endpoint)
 	return endpoint->id;
 }
 
+enum ast_endpoint_state ast_endpoint_get_state(const struct ast_endpoint *endpoint)
+{
+	if (!endpoint) {
+		return AST_ENDPOINT_UNKNOWN;
+	}
+	return endpoint->state;
+}
+
 void ast_endpoint_set_state(struct ast_endpoint *endpoint,
 	enum ast_endpoint_state state)
 {
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 108c5b32dd8c536d6fe1996810bdcb44c5c8713d..4f77b51021575bf998144beac1c78a84727147d5 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -1011,6 +1011,14 @@
 						If <literal>0</literal> never qualify. Time in seconds.
 					</para></description>
 				</configOption>
+				<configOption name="qualify_timeout" default="3.0">
+					<synopsis>Timeout for qualify</synopsis>
+					<description><para>
+						If the contact doesn't repond to the OPTIONS request before the timeout,
+						the contact is marked unavailable.
+						If <literal>0</literal> no timeout. Time in fractional seconds.
+					</para></description>
+				</configOption>
 				<configOption name="outbound_proxy">
 					<synopsis>Outbound proxy used when sending OPTIONS request</synopsis>
 					<description><para>
@@ -1125,6 +1133,14 @@
 						If <literal>0</literal> never qualify. Time in seconds.
 					</para></description>
 				</configOption>
+				<configOption name="qualify_timeout" default="3.0">
+					<synopsis>Timeout for qualify</synopsis>
+					<description><para>
+						If the contact doesn't repond to the OPTIONS request before the timeout,
+						the contact is marked unavailable.
+						If <literal>0</literal> no timeout. Time in fractional seconds.
+					</para></description>
+				</configOption>
 				<configOption name="authenticate_qualify" default="no">
 					<synopsis>Authenticates a qualify request if needed</synopsis>
 					<description><para>
diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c
index 73ffdca0e1f3a00dd349a65ef20fdac0e2a6404f..f784cb40fbeaa5581e492f3f3eb53fd30395c5ef 100644
--- a/res/res_pjsip/location.c
+++ b/res/res_pjsip/location.c
@@ -188,6 +188,40 @@ struct ast_sip_contact *ast_sip_location_retrieve_contact_from_aor_list(const ch
 	return contact;
 }
 
+static int permanent_uri_sort_fn(const void *obj_left, const void *obj_right, int flags);
+static int cli_contact_populate_container(void *obj, void *arg, int flags);
+
+static int gather_contacts_for_aor(void *obj, void *arg, int flags)
+{
+	struct ao2_container *aor_contacts;
+	struct ast_sip_aor *aor = obj;
+	struct ao2_container *container = arg;
+
+	aor_contacts = ast_sip_location_retrieve_aor_contacts(aor);
+	if (!aor_contacts) {
+		return 0;
+	}
+	ao2_callback(aor_contacts, OBJ_MULTIPLE | OBJ_NODATA, cli_contact_populate_container,
+		container);
+	ao2_ref(aor_contacts, -1);
+	return CMP_MATCH;
+}
+
+struct ao2_container *ast_sip_location_retrieve_contacts_from_aor_list(const char *aor_list)
+{
+	struct ao2_container *contacts;
+
+	contacts = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK,
+		AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, permanent_uri_sort_fn, NULL);
+	if (!contacts) {
+		return NULL;
+	}
+
+	ast_sip_for_each_aor(aor_list, gather_contacts_for_aor, contacts);
+
+	return contacts;
+}
+
 struct ast_sip_contact *ast_sip_location_retrieve_contact(const char *contact_name)
 {
 	return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "contact", contact_name);
@@ -208,6 +242,7 @@ int ast_sip_location_add_contact(struct ast_sip_aor *aor, const char *uri,
 	ast_string_field_set(contact, uri, uri);
 	contact->expiration_time = expiration_time;
 	contact->qualify_frequency = aor->qualify_frequency;
+	contact->qualify_timeout = aor->qualify_timeout;
 	contact->authenticate_qualify = aor->authenticate_qualify;
 	if (path_info && aor->support_path) {
 		ast_string_field_set(contact, path, path_info);
@@ -853,7 +888,8 @@ int ast_sip_initialize_sorcery_location(void)
 	ast_sorcery_object_field_register(sorcery, "contact", "path", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, path));
 	ast_sorcery_object_field_register_custom(sorcery, "contact", "expiration_time", "", expiration_str2struct, expiration_struct2str, NULL, 0, 0);
 	ast_sorcery_object_field_register(sorcery, "contact", "qualify_frequency", 0, OPT_UINT_T,
-					  PARSE_IN_RANGE, FLDSET(struct ast_sip_contact, qualify_frequency), 0, 86400);
+		PARSE_IN_RANGE, FLDSET(struct ast_sip_contact, qualify_frequency), 0, 86400);
+	ast_sorcery_object_field_register(sorcery, "contact", "qualify_timeout", "3.0", OPT_DOUBLE_T, 0, FLDSET(struct ast_sip_contact, qualify_timeout));
 	ast_sorcery_object_field_register(sorcery, "contact", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, outbound_proxy));
 	ast_sorcery_object_field_register(sorcery, "contact", "user_agent", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, user_agent));
 
@@ -862,6 +898,7 @@ int ast_sip_initialize_sorcery_location(void)
 	ast_sorcery_object_field_register(sorcery, "aor", "maximum_expiration", "7200", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, maximum_expiration));
 	ast_sorcery_object_field_register(sorcery, "aor", "default_expiration", "3600", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, default_expiration));
 	ast_sorcery_object_field_register(sorcery, "aor", "qualify_frequency", 0, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_aor, qualify_frequency), 0, 86400);
+	ast_sorcery_object_field_register(sorcery, "aor", "qualify_timeout", "3.0", OPT_DOUBLE_T, 0, FLDSET(struct ast_sip_aor, qualify_timeout));
 	ast_sorcery_object_field_register(sorcery, "aor", "authenticate_qualify", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, authenticate_qualify));
 	ast_sorcery_object_field_register(sorcery, "aor", "max_contacts", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, max_contacts));
 	ast_sorcery_object_field_register(sorcery, "aor", "remove_existing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, remove_existing));
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index 0eecb5e0a22fc1c2caff910cef94ed3ae714c331..ab0d08449433a5f241a30518294dae84ea5228bc 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -19,6 +19,7 @@
 #include "asterisk/utils.h"
 #include "asterisk/sorcery.h"
 #include "asterisk/callerid.h"
+#include "asterisk/test.h"
 
 /*! \brief Number of buckets for persistent endpoint information */
 #define PERSISTENT_BUCKETS 53
@@ -59,31 +60,66 @@ static int persistent_endpoint_cmp(void *obj, void *arg, int flags)
 static int persistent_endpoint_update_state(void *obj, void *arg, int flags)
 {
 	struct sip_persistent_endpoint *persistent = obj;
+	struct ast_endpoint *endpoint = persistent->endpoint;
 	char *aor = arg;
-	RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
-	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+	struct ao2_container *contacts;
+	struct ast_json *blob;
+	struct ao2_iterator i;
+	struct ast_sip_contact *contact;
+	enum ast_endpoint_state state = AST_ENDPOINT_OFFLINE;
 
 	if (!ast_strlen_zero(aor) && !strstr(persistent->aors, aor)) {
 		return 0;
 	}
 
-	if ((contact = ast_sip_location_retrieve_contact_from_aor_list(persistent->aors))) {
-		ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_ONLINE);
+	/* Find all the contacts for this endpoint.  If ANY are available,
+	 * mark the endpoint as ONLINE.
+	 */
+	contacts = ast_sip_location_retrieve_contacts_from_aor_list(persistent->aors);
+	if (contacts) {
+		i = ao2_iterator_init(contacts, 0);
+		while ((contact = ao2_iterator_next(&i))
+			&& state == AST_ENDPOINT_OFFLINE) {
+			struct ast_sip_contact_status *contact_status;
+			const char *contact_id = ast_sorcery_object_get_id(contact);
+
+			contact_status = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(),
+				CONTACT_STATUS, contact_id);
+
+			if (contact_status && contact_status->status == AVAILABLE) {
+				state = AST_ENDPOINT_ONLINE;
+			}
+			ao2_cleanup(contact_status);
+			ao2_ref(contact, -1);
+		}
+		ao2_iterator_destroy(&i);
+		ao2_ref(contacts, -1);
+	}
+
+	/* If there was no state change, don't publish anything. */
+	if (ast_endpoint_get_state(endpoint) == state) {
+		return 0;
+	}
+
+	if (state == AST_ENDPOINT_ONLINE) {
+		ast_endpoint_set_state(endpoint, AST_ENDPOINT_ONLINE);
 		blob = ast_json_pack("{s: s}", "peer_status", "Reachable");
+		ast_verb(1, "Endpoint %s is now Reachable\n", ast_endpoint_get_resource(endpoint));
 	} else {
-		ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_OFFLINE);
+		ast_endpoint_set_state(endpoint, AST_ENDPOINT_OFFLINE);
 		blob = ast_json_pack("{s: s}", "peer_status", "Unreachable");
+		ast_verb(1, "Endpoint %s is now Unreachable\n", ast_endpoint_get_resource(endpoint));
 	}
 
-	ast_endpoint_blob_publish(persistent->endpoint, ast_endpoint_state_type(), blob);
-
-	ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_endpoint_get_resource(persistent->endpoint));
+	ast_endpoint_blob_publish(endpoint, ast_endpoint_state_type(), blob);
+	ast_json_unref(blob);
+	ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_endpoint_get_resource(endpoint));
 
 	return 0;
 }
 
 /*! \brief Function called when stuff relating to a contact happens (created/deleted) */
-static void persistent_endpoint_contact_observer(const void *object)
+static void persistent_endpoint_contact_created_observer(const void *object)
 {
 	char *id = ast_strdupa(ast_sorcery_object_get_id(object)), *aor = NULL;
 
@@ -92,12 +128,74 @@ static void persistent_endpoint_contact_observer(const void *object)
 	ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, aor);
 }
 
+/*! \brief Function called when stuff relating to a contact happens (created/deleted) */
+static void persistent_endpoint_contact_deleted_observer(const void *object)
+{
+	char *id = ast_strdupa(ast_sorcery_object_get_id(object));
+	char *aor = NULL;
+	char *contact = NULL;
+
+	aor = id;
+	/* Dynamic contacts are delimited with ";@" and static ones with "@@" */
+	if ((contact = strstr(id, ";@")) || (contact = strstr(id, "@@"))) {
+		*contact = '\0';
+		contact += 2;
+	} else {
+		contact = id;
+	}
+
+	ast_verb(1, "Contact %s/%s is now Unavailable\n", aor, contact);
+
+	ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, aor);
+}
+
 /*! \brief Observer for contacts so state can be updated on respective endpoints */
 static const struct ast_sorcery_observer state_contact_observer = {
-	.created = persistent_endpoint_contact_observer,
-	.deleted = persistent_endpoint_contact_observer,
+	.created = persistent_endpoint_contact_created_observer,
+	.deleted = persistent_endpoint_contact_deleted_observer,
 };
 
+/*! \brief Function called when stuff relating to a contact status happens (updated) */
+static void persistent_endpoint_contact_status_observer(const void *object)
+{
+	const struct ast_sip_contact_status *contact_status = object;
+	char *id = ast_strdupa(ast_sorcery_object_get_id(object));
+	char *aor = NULL;
+	char *contact = NULL;
+
+	/* If rtt_start is set (this is the outgoing OPTIONS) or
+	 * there's no status change, ignore.
+	 */
+	if (contact_status->rtt_start.tv_sec > 0
+		|| contact_status->status == contact_status->last_status) {
+		return;
+	}
+
+	aor = id;
+	/* Dynamic contacts are delimited with ";@" and static ones with "@@" */
+	if ((contact = strstr(id, ";@")) || (contact = strstr(id, "@@"))) {
+		*contact = '\0';
+		contact += 2;
+	} else {
+		contact = id;
+	}
+
+	ast_test_suite_event_notify("AOR_CONTACT_UPDATE",
+		"Contact: %s\r\n"
+			"Status: %s",
+		ast_sorcery_object_get_id(contact_status),
+		(contact_status->status == AVAILABLE ? "Available" : "Unavailable"));
+
+	ast_verb(1, "Contact %s/%s is now %s\n", aor, contact,
+		contact_status->status == AVAILABLE ? "Available" : "Unavailable");
+
+	ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, aor);
+}
+
+/*! \brief Observer for contacts so state can be updated on respective endpoints */
+static const struct ast_sorcery_observer state_contact_status_observer = {
+	.updated = persistent_endpoint_contact_status_observer,
+};
 
 static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
@@ -1796,6 +1894,7 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
 	}
 
 	ast_sorcery_observer_add(sip_sorcery, "contact", &state_contact_observer);
+	ast_sorcery_observer_add(sip_sorcery, CONTACT_STATUS, &state_contact_status_observer);
 
 	if (ast_sip_initialize_sorcery_domain_alias()) {
 		ast_log(LOG_ERROR, "Failed to register SIP domain aliases support with sorcery\n");
@@ -1852,6 +1951,8 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
 
 void ast_res_pjsip_destroy_configuration(void)
 {
+	ast_sorcery_observer_remove(sip_sorcery, CONTACT_STATUS, &state_contact_status_observer);
+	ast_sorcery_observer_remove(sip_sorcery, "contact", &state_contact_observer);
 	ast_sip_destroy_sorcery_global();
 	ast_sip_destroy_sorcery_location();
 	ast_sip_destroy_sorcery_auth();
diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c
index 9794827b56864d6cc2c281f3e98386a69340ae35..dc5a70175bd05a2ef7c16736ba8fbdc6307cd002 100644
--- a/res/res_pjsip/pjsip_options.c
+++ b/res/res_pjsip/pjsip_options.c
@@ -28,6 +28,7 @@
 #include "asterisk/astobj2.h"
 #include "asterisk/cli.h"
 #include "asterisk/time.h"
+#include "asterisk/test.h"
 #include "include/res_pjsip_private.h"
 
 #define DEFAULT_LANGUAGE "en"
@@ -110,18 +111,20 @@ static void update_contact_status(const struct ast_sip_contact *contact,
 
 	status = find_or_create_contact_status(contact);
 	if (!status) {
+		ast_log(LOG_ERROR, "Unable to find ast_sip_contact_status for contact %s\n",
+			contact->uri);
 		return;
 	}
 
 	update = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,
 		ast_sorcery_object_get_id(status));
 	if (!update) {
-		ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n",
+		ast_log(LOG_ERROR, "Unable to allocate ast_sip_contact_status for contact %s\n",
 			contact->uri);
-		ao2_ref(status, -1);
 		return;
 	}
 
+	update->last_status = status->status;
 	update->status = value;
 
 	/* if the contact is available calculate the rtt as
@@ -131,13 +134,21 @@ static void update_contact_status(const struct ast_sip_contact *contact,
 
 	update->rtt_start = ast_tv(0, 0);
 
+	ast_test_suite_event_notify("AOR_CONTACT_QUALIFY_RESULT",
+		"Contact: %s\r\n"
+			"Status: %s\r\n"
+			"RTT: %ld",
+		ast_sorcery_object_get_id(update),
+		(update->status == AVAILABLE ? "Available" : "Unavailable"),
+		update->rtt);
+
 	if (ast_sorcery_update(ast_sip_get_sorcery(), update)) {
 		ast_log(LOG_ERROR, "Unable to update ast_sip_contact_status for contact %s\n",
 			contact->uri);
 	}
 
-	ao2_ref(update, -1);
 	ao2_ref(status, -1);
+	ao2_ref(update, -1);
 }
 
 /*!
@@ -152,18 +163,22 @@ static void init_start_time(const struct ast_sip_contact *contact)
 
 	status = find_or_create_contact_status(contact);
 	if (!status) {
+		ast_log(LOG_ERROR, "Unable to find ast_sip_contact_status for contact %s\n",
+			contact->uri);
 		return;
 	}
 
 	update = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,
 		ast_sorcery_object_get_id(status));
 	if (!update) {
-		ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n",
+		ast_log(LOG_ERROR, "Unable to copy ast_sip_contact_status for contact %s\n",
 			contact->uri);
-		ao2_ref(status, -1);
 		return;
 	}
 
+	update->status = status->status;
+	update->last_status = status->last_status;
+	update->rtt = status->rtt;
 	update->rtt_start = ast_tvnow();
 
 	if (ast_sorcery_update(ast_sip_get_sorcery(), update)) {
@@ -171,8 +186,8 @@ static void init_start_time(const struct ast_sip_contact *contact)
 			contact->uri);
 	}
 
-	ao2_ref(update, -1);
 	ao2_ref(status, -1);
+	ao2_ref(update, -1);
 }
 
 /*!
@@ -320,7 +335,7 @@ static int qualify_contact(struct ast_sip_endpoint *endpoint, struct ast_sip_con
 	init_start_time(contact);
 
 	ao2_ref(contact, +1);
-	if (ast_sip_send_request(tdata, NULL, endpoint_local, contact, qualify_contact_cb)
+	if (ast_sip_send_out_of_dialog_request(tdata, endpoint_local, (int)(contact->qualify_timeout * 1000), contact, qualify_contact_cb)
 		!= PJ_SUCCESS) {
 		ast_log(LOG_ERROR, "Unable to send request to qualify contact %s\n",
 			contact->uri);
@@ -923,6 +938,32 @@ static int sched_qualifies_cmp_fn(void *obj, void *arg, int flags)
 	return CMP_MATCH;
 }
 
+static int rtt_start_handler(const struct aco_option *opt,
+	struct ast_variable *var, void *obj)
+{
+	struct ast_sip_contact_status *status = obj;
+	long int sec, usec;
+
+	if (sscanf(var->value, "%ld.%06ld", &sec, &usec) != 2) {
+		return -1;
+	}
+
+	status->rtt_start = ast_tv(sec, usec);
+
+	return 0;
+}
+
+static int rtt_start_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_contact_status *status = obj;
+
+	if (ast_asprintf(buf, "%ld.%06ld", status->rtt_start.tv_sec, status->rtt_start.tv_usec) == -1) {
+		return -1;
+	}
+
+	return 0;
+}
+
 int ast_sip_initialize_sorcery_qualify(void)
 {
 	struct ast_sorcery *sorcery = ast_sip_get_sorcery();
@@ -936,10 +977,14 @@ int ast_sip_initialize_sorcery_qualify(void)
 		return -1;
 	}
 
-	ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "rtt", "0", OPT_UINT_T,
-					  1, FLDSET(struct ast_sip_contact_status, status));
-	ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "rtt", "0", OPT_UINT_T,
-					  1, FLDSET(struct ast_sip_contact_status, rtt));
+	ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "last_status",
+		"0", OPT_UINT_T, 1, FLDSET(struct ast_sip_contact_status, last_status));
+	ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "status",
+		"0", OPT_UINT_T, 1, FLDSET(struct ast_sip_contact_status, status));
+	ast_sorcery_object_field_register_custom_nodoc(sorcery, CONTACT_STATUS, "rtt_start",
+		"0.0", rtt_start_handler, rtt_start_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "rtt",
+		"0", OPT_UINT_T, 1, FLDSET(struct ast_sip_contact_status, rtt));
 
 	return 0;
 }
@@ -951,6 +996,7 @@ static int qualify_and_schedule_cb(void *obj, void *arg, int flags)
 	int initial_interval;
 
 	contact->qualify_frequency = aor->qualify_frequency;
+	contact->qualify_timeout = aor->qualify_timeout;
 	contact->authenticate_qualify = aor->authenticate_qualify;
 
 	/* Delay initial qualification by a random fraction of the specified interval */