diff --git a/main/pbx.c b/main/pbx.c
index d0a836ab94f9c2fb725fead953b9dfa2c4c7ec58..be003286ddcf53f6ca09a12e6c0b8c71c5428adb 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -5926,12 +5926,28 @@ static int ast_add_hint(struct ast_exten *e)
 /*! \brief Change hint for an extension */
 static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne)
 {
+	struct ast_str *hint_app;
 	struct ast_hint *hint;
+	int previous_device_state;
+	char *previous_message = NULL;
+	char *message = NULL;
+	char *previous_subtype = NULL;
+	char *subtype = NULL;
+	int previous_presence_state;
+	int presence_state;
+	int presence_state_changed = 0;
 
 	if (!oe || !ne) {
 		return -1;
 	}
 
+	hint_app = ast_str_create(1024);
+	if (!hint_app) {
+		return -1;
+	}
+
+	ast_mutex_lock(&context_merge_lock); /* Hold off ast_merge_contexts_and_delete and state changes */
+
 	ao2_lock(hints);/* Locked to hold off others while we move the hint around. */
 
 	/*
@@ -5941,6 +5957,8 @@ static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne)
 	hint = ao2_find(hints, oe, OBJ_UNLINK);
 	if (!hint) {
 		ao2_unlock(hints);
+		ast_mutex_unlock(&context_merge_lock);
+		ast_free(hint_app);
 		return -1;
 	}
 
@@ -5949,7 +5967,28 @@ static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne)
 	/* Update the hint and put it back in the hints container. */
 	ao2_lock(hint);
 	hint->exten = ne;
+
+	/* Store the previous states so we know whether we need to notify state callbacks */
+	previous_device_state = hint->laststate;
+	previous_presence_state = hint->last_presence_state;
+	previous_message = hint->last_presence_message;
+	previous_subtype = hint->last_presence_subtype;
+
+	/* Update the saved device and presence state with the new extension */
+	hint->laststate = ast_extension_state2(ne, NULL);
+	hint->last_presence_state = AST_PRESENCE_INVALID;
+	hint->last_presence_subtype = NULL;
+	hint->last_presence_message = NULL;
+
+	presence_state = extension_presence_state_helper(ne, &subtype, &message);
+	if (presence_state > 0) {
+		hint->last_presence_state = presence_state;
+		hint->last_presence_subtype = subtype;
+		hint->last_presence_message = message;
+	}
+
 	ao2_unlock(hint);
+
 	ao2_link(hints, hint);
 	if (add_hintdevice(hint, ast_get_extension_app(ne))) {
 		ast_log(LOG_WARNING, "Could not add devices for hint: %s@%s.\n",
@@ -5958,8 +5997,98 @@ static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne)
 	}
 
 	ao2_unlock(hints);
+
+	/* Locking for state callbacks is respected here and only the context_merge_lock lock is
+	 * held during the state callback invocation. This will stop the normal state callback
+	 * thread from being able to handle incoming state changes if they occur.
+	 */
+
+	/* Determine if presence state has changed due to the change of the hint extension */
+	if ((hint->last_presence_state != previous_presence_state) ||
+		strcmp(S_OR(hint->last_presence_subtype, ""), S_OR(previous_subtype, "")) ||
+		strcmp(S_OR(hint->last_presence_message, ""), S_OR(previous_message, ""))) {
+		presence_state_changed = 1;
+	}
+
+	/* Notify any existing state callbacks if the device or presence state has changed */
+	if ((hint->laststate != previous_device_state) || presence_state_changed) {
+		struct ao2_iterator cb_iter;
+		struct ast_state_cb *state_cb;
+		struct ao2_container *device_state_info;
+		int first_extended_cb_call = 1;
+
+		/* For general callbacks */
+		cb_iter = ao2_iterator_init(statecbs, 0);
+		for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+			/* Unlike the normal state callbacks since something has explicitly provided us this extension
+			 * it will remain valid and unchanged for the lifetime of this function invocation.
+			 */
+			if (hint->laststate != previous_device_state) {
+				execute_state_callback(state_cb->change_cb,
+					ast_get_context_name(ast_get_extension_context(ne)),
+					ast_get_extension_name(ne),
+					state_cb->data,
+					AST_HINT_UPDATE_DEVICE,
+					hint,
+					NULL);
+			}
+			if (presence_state_changed) {
+				execute_state_callback(state_cb->change_cb,
+					ast_get_context_name(ast_get_extension_context(ne)),
+					ast_get_extension_name(ne),
+					state_cb->data,
+					AST_HINT_UPDATE_PRESENCE,
+					hint,
+					NULL);
+			}
+		}
+		ao2_iterator_destroy(&cb_iter);
+
+		ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(ne));
+
+		device_state_info = alloc_device_state_info();
+		ast_extension_state3(hint_app, device_state_info);
+
+		/* For extension callbacks */
+		cb_iter = ao2_iterator_init(hint->callbacks, 0);
+		for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+			if (hint->laststate != previous_device_state) {
+				if (state_cb->extended && first_extended_cb_call) {
+				/* Fill detailed device_state_info now that we know it is used by extd. callback */
+					first_extended_cb_call = 0;
+					get_device_state_causing_channels(device_state_info);
+				}
+				execute_state_callback(state_cb->change_cb,
+					ast_get_context_name(ast_get_extension_context(ne)),
+					ast_get_extension_name(ne),
+					state_cb->data,
+					AST_HINT_UPDATE_DEVICE,
+					hint,
+					state_cb->extended ? device_state_info : NULL);
+			}
+			if (presence_state_changed) {
+				execute_state_callback(state_cb->change_cb,
+					ast_get_context_name(ast_get_extension_context(ne)),
+					ast_get_extension_name(ne),
+					state_cb->data,
+					AST_HINT_UPDATE_PRESENCE,
+					hint,
+					NULL);
+			}
+		}
+		ao2_iterator_destroy(&cb_iter);
+
+		ao2_cleanup(device_state_info);
+	}
+
 	ao2_ref(hint, -1);
 
+	ast_mutex_unlock(&context_merge_lock);
+
+	ast_free(hint_app);
+	ast_free(previous_message);
+	ast_free(previous_subtype);
+
 	return 0;
 }