diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h
index 0863f9f98c24f6cb23fa79f009adf85a445d109b..a73461547e0f977356744361dee518f435606419 100644
--- a/include/asterisk/stasis_app.h
+++ b/include/asterisk/stasis_app.h
@@ -672,6 +672,18 @@ int stasis_app_control_queue_control(struct stasis_app_control *control,
  */
 struct ast_bridge *stasis_app_bridge_create(const char *type, const char *name, const char *id);
 
+/*!
+ * \brief Create an invisible bridge of the specified type.
+ *
+ * \param type The type of bridge to be created
+ * \param name Optional name to give to the bridge
+ * \param id Optional Unique ID to give to the bridge
+ *
+ * \return New bridge.
+ * \return \c NULL on error.
+ */
+struct ast_bridge *stasis_app_bridge_create_invisible(const char *type, const char *name, const char *id);
+
 /*!
  * \brief Returns the bridge with the given id.
  * \param bridge_id Uniqueid of the bridge.
@@ -855,20 +867,23 @@ int stasis_app_channel_unreal_set_internal(struct ast_channel *chan);
  */
 int stasis_app_channel_set_internal(struct ast_channel *chan);
 
-struct ast_dial;
-
 /*!
  * \brief Dial a channel
  * \param control Control for \c res_stasis.
- * \param dial The ast_dial for the outbound channel
+ * \param dialstring The dialstring to pass to the channel driver
+ * \param timeout Optional timeout in milliseconds
  */
-int stasis_app_control_dial(struct stasis_app_control *control, struct ast_dial *dial);
+int stasis_app_control_dial(struct stasis_app_control *control,
+		const char *dialstring, unsigned int timeout);
 
 /*!
- * \brief Get dial structure on a control
+ * \brief Let Stasis app internals shut down
+ *
+ * This is called when res_stasis is unloaded. It ensures that
+ * the Stasis app internals can free any resources they may have
+ * allocated during the time that res_stasis was loaded.
  */
-struct ast_dial *stasis_app_get_dial(struct stasis_app_control *control);
-
+void stasis_app_control_shutdown(void);
 /*! @} */
 
 #endif /* _ASTERISK_STASIS_APP_H */
diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c
index b42581c8463511891aaf13ec43a03c88e23fd4cf..0f18b2dc1d5117e18d2425b5547ed2f63419a8db 100644
--- a/res/ari/resource_channels.c
+++ b/res/ari/resource_channels.c
@@ -48,6 +48,7 @@ ASTERISK_REGISTER_FILE()
 #include "asterisk/format_cache.h"
 #include "asterisk/core_local.h"
 #include "asterisk/dial.h"
+#include "asterisk/max_forwards.h"
 #include "resource_channels.h"
 
 #include <limits.h>
@@ -1503,6 +1504,67 @@ static void *ari_channel_thread(void *data)
 	return NULL;
 }
 
+struct ast_datastore_info dialstring_info = {
+	.type = "ARI Dialstring",
+	.destroy = ast_free_ptr,
+};
+
+/*!
+ * \brief Save dialstring onto a channel datastore
+ *
+ * This will later be retrieved when it comes time to actually dial the channel
+ *
+ * \param chan The channel on which to save the dialstring
+ * \param dialstring The dialstring to save
+ * \retval 0 SUCCESS!
+ * \reval -1 Failure :(
+ */
+static int save_dialstring(struct ast_channel *chan, const char *dialstring)
+{
+	struct ast_datastore *datastore;
+
+	datastore = ast_datastore_alloc(&dialstring_info, NULL);
+	if (!datastore) {
+		return -1;
+	}
+
+	datastore->data = ast_strdup(dialstring);
+	if (!datastore->data) {
+		ast_datastore_free(datastore);
+		return -1;
+	}
+
+	ast_channel_lock(chan);
+	if (ast_channel_datastore_add(chan, datastore)) {
+		ast_channel_unlock(chan);
+		ast_datastore_free(datastore);
+		return -1;
+	}
+	ast_channel_unlock(chan);
+
+	return 0;
+}
+
+/*!
+ * \brief Retrieve the dialstring from the channel datastore
+ *
+ * \pre chan is locked
+ * \param chan Channel that was previously created in ARI
+ * \retval NULL Failed to find datastore
+ * \retval non-NULL The dialstring
+ */
+static char *restore_dialstring(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore;
+
+	datastore = ast_channel_datastore_find(chan, &dialstring_info, NULL);
+	if (!datastore) {
+		return NULL;
+	}
+
+	return datastore->data;
+}
+
 void ast_ari_channels_create(struct ast_variable *headers,
 	struct ast_ari_channels_create_args *args,
 	struct ast_ari_response *response)
@@ -1562,6 +1624,12 @@ void ast_ari_channels_create(struct ast_variable *headers,
 		return;
 	}
 
+	if (save_dialstring(chan_data->chan, stuff)) {
+		ast_ari_response_alloc_failed(response);
+		chan_data_destroy(chan_data);
+		return;
+	}
+
 	snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan_data->chan));
 
 	if (ast_pthread_create_detached(&thread, NULL, ari_channel_thread, chan_data)) {
@@ -1580,8 +1648,8 @@ void ast_ari_channels_dial(struct ast_variable *headers,
 {
 	RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_channel *, caller, NULL, ast_channel_cleanup);
-	struct ast_channel *callee;
-	struct ast_dial *dial;
+	RAII_VAR(struct ast_channel *, callee, NULL, ast_channel_cleanup);
+	char *dialstring;
 
 	control = find_control(response, args->channel_id);
 	if (control == NULL) {
@@ -1598,43 +1666,64 @@ void ast_ari_channels_dial(struct ast_variable *headers,
 		return;
 	}
 
-	if (ast_channel_state(callee) != AST_STATE_DOWN) {
-		ast_channel_unref(callee);
+	if (ast_channel_state(callee) != AST_STATE_DOWN
+		&& ast_channel_state(callee) != AST_STATE_RESERVED) {
 		ast_ari_response_error(response, 409, "Conflict",
 			"Channel is not in the 'Down' state");
 		return;
 	}
 
-	dial = ast_dial_create();
-	if (!dial) {
-		ast_channel_unref(callee);
-		ast_ari_response_alloc_failed(response);
-		return;
+	/* XXX This is straight up copied from main/dial.c. It's probably good
+	 * to separate this to some common method.
+	 */
+	if (caller) {
+		ast_channel_lock_both(caller, callee);
+	} else {
+		ast_channel_lock(callee);
 	}
 
-	if (ast_dial_append_channel(dial, callee) < 0) {
-		ast_channel_unref(callee);
-		ast_dial_destroy(dial);
-		ast_ari_response_alloc_failed(response);
+	dialstring = restore_dialstring(callee);
+	if (!dialstring) {
+		ast_channel_unlock(callee);
+		if (caller) {
+			ast_channel_unlock(caller);
+		}
+		ast_ari_response_error(response, 409, "Conflict",
+			"Dialing a channel not created by ARI");
 		return;
 	}
-
-	/* From this point, we don't have to unref the callee channel on
-	 * failure paths because the dial owns the reference to the called
-	 * channel and will unref the channel for us
+	/* Make a copy of the dialstring just in case some jerk tries to hang up the
+	 * channel before we can actually dial
 	 */
+	dialstring = ast_strdupa(dialstring);
 
-	if (ast_dial_prerun(dial, caller, NULL)) {
-		ast_dial_destroy(dial);
-		ast_ari_response_alloc_failed(response);
-		return;
+	ast_channel_stage_snapshot(callee);
+	if (caller) {
+		ast_channel_inherit_variables(caller, callee);
+		ast_channel_datastore_inherit(caller, callee);
+		ast_max_forwards_decrement(callee);
+
+		/* Copy over callerid information */
+		ast_party_redirecting_copy(ast_channel_redirecting(callee), ast_channel_redirecting(caller));
+
+		ast_channel_dialed(callee)->transit_network_select = ast_channel_dialed(caller)->transit_network_select;
+
+		ast_connected_line_copy_from_caller(ast_channel_connected(callee), ast_channel_caller(caller));
+
+		ast_channel_language_set(callee, ast_channel_language(caller));
+		ast_channel_req_accountcodes(callee, caller, AST_CHANNEL_REQUESTOR_BRIDGE_PEER);
+		if (ast_strlen_zero(ast_channel_musicclass(callee)))
+			ast_channel_musicclass_set(callee, ast_channel_musicclass(caller));
+
+		ast_channel_adsicpe_set(callee, ast_channel_adsicpe(caller));
+		ast_channel_transfercapability_set(callee, ast_channel_transfercapability(caller));
+		ast_channel_unlock(caller);
 	}
 
-	ast_dial_set_user_data(dial, control);
-	ast_dial_set_global_timeout(dial, args->timeout * 1000);
+	ast_channel_stage_snapshot_done(callee);
+	ast_channel_unlock(callee);
 
-	if (stasis_app_control_dial(control, dial)) {
-		ast_dial_destroy(dial);
+	if (stasis_app_control_dial(control, dialstring, args->timeout)) {
 		ast_ari_response_alloc_failed(response);
 		return;
 	}
diff --git a/res/res_stasis.c b/res/res_stasis.c
index 346be563c1ae9d7a9fb9c4dd1b0a2462eeedd81b..e7e6bcaa3179903992c58b8b2a11253b9782f197 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -749,7 +749,7 @@ static void control_unlink(struct stasis_app_control *control)
 	ao2_cleanup(control);
 }
 
-struct ast_bridge *stasis_app_bridge_create(const char *type, const char *name, const char *id)
+static struct ast_bridge *bridge_create_common(const char *type, const char *name, const char *id, int invisible)
 {
 	struct ast_bridge *bridge;
 	char *requested_type, *requested_types = ast_strdupa(S_OR(type, "mixing"));
@@ -758,6 +758,10 @@ struct ast_bridge *stasis_app_bridge_create(const char *type, const char *name,
 		| AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_SWAP_INHIBIT_TO
 		| AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY;
 
+	if (invisible) {
+		flags |= AST_BRIDGE_FLAG_INVISIBLE;
+	}
+
 	while ((requested_type = strsep(&requested_types, ","))) {
 		requested_type = ast_strip(requested_type);
 
@@ -789,6 +793,16 @@ struct ast_bridge *stasis_app_bridge_create(const char *type, const char *name,
 	return bridge;
 }
 
+struct ast_bridge *stasis_app_bridge_create(const char *type, const char *name, const char *id)
+{
+	return bridge_create_common(type, name, id, 0);
+}
+
+struct ast_bridge *stasis_app_bridge_create_invisible(const char *type, const char *name, const char *id)
+{
+	return bridge_create_common(type, name, id, 1);
+}
+
 void stasis_app_bridge_destroy(const char *bridge_id)
 {
 	struct ast_bridge *bridge = stasis_app_bridge_find_by_id(bridge_id);
@@ -1287,7 +1301,6 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
 		int r;
 		int command_count;
 		RAII_VAR(struct ast_bridge *, last_bridge, NULL, ao2_cleanup);
-		struct ast_dial *dial;
 
 		/* Check to see if a bridge absorbed our hangup frame */
 		if (ast_check_hangup_locked(chan)) {
@@ -1297,7 +1310,6 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
 
 		last_bridge = bridge;
 		bridge = ao2_bump(stasis_app_get_bridge(control));
-		dial = stasis_app_get_dial(control);
 
 		if (bridge != last_bridge) {
 			app_unsubscribe_bridge(app, last_bridge);
@@ -1306,7 +1318,7 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
 			}
 		}
 
-		if (bridge || dial) {
+		if (bridge) {
 			/* Bridge/dial is handling channel frames */
 			control_wait(control);
 			control_dispatch_all(control, chan);
@@ -1951,6 +1963,8 @@ static int unload_module(void)
 	ao2_cleanup(app_bridges_playback);
 	app_bridges_playback = NULL;
 
+	stasis_app_control_shutdown();
+
 	STASIS_MESSAGE_TYPE_CLEANUP(end_message_type);
 	STASIS_MESSAGE_TYPE_CLEANUP(start_message_type);
 
diff --git a/res/stasis/control.c b/res/stasis/control.c
index aa6866aee6d3a63190f01a558ffe8128fd7e97e5..b255477bf730e374c3920572419520329e26cc55 100644
--- a/res/stasis/control.c
+++ b/res/stasis/control.c
@@ -28,6 +28,7 @@
 ASTERISK_REGISTER_FILE()
 
 #include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_app.h"
 
 #include "command.h"
 #include "control.h"
@@ -43,6 +44,11 @@ ASTERISK_REGISTER_FILE()
 
 AST_LIST_HEAD(app_control_rules, stasis_app_control_rule);
 
+/*!
+ * \brief Indicates if the Stasis app internals are being shut down
+ */
+static int shutting_down;
+
 struct stasis_app_control {
 	ast_cond_t wait_cond;
 	/*! Queue of commands to dispatch on the channel */
@@ -77,10 +83,6 @@ struct stasis_app_control {
 	 * The app for which this control was created
 	 */
 	struct stasis_app *app;
-	/*!
-	 * If channel is being dialed, the dial structure.
-	 */
-	struct ast_dial *dial;
 	/*!
 	 * When set, /c app_stasis should exit and continue in the dialplan.
 	 */
@@ -825,6 +827,128 @@ struct ast_bridge *stasis_app_get_bridge(struct stasis_app_control *control)
 	}
 }
 
+/*!
+ * \brief Singleton dial bridge
+ *
+ * The dial bridge is a holding bridge used to hold all
+ * outbound dialed channels that are not in any "real" ARI-created
+ * bridge. The dial bridge is invisible, meaning that it does not
+ * show up in channel snapshots, AMI or ARI output, and no events
+ * get raised for it.
+ *
+ * This is used to keep dialed channels confined to the bridging system
+ * and unify the threading model used for dialing outbound channels.
+ */
+static struct ast_bridge *dial_bridge;
+AST_MUTEX_DEFINE_STATIC(dial_bridge_lock);
+
+/*!
+ * \brief Retrieve a reference to the dial bridge.
+ *
+ * If the dial bridge has not been created yet, it will
+ * be created, otherwise, a reference to the existing bridge
+ * will be returned.
+ *
+ * The caller will need to unreference the dial bridge once
+ * they are finished with it.
+ *
+ * \retval NULL Unable to find/create the dial bridge
+ * \retval non-NULL A reference to teh dial bridge
+ */
+static struct ast_bridge *get_dial_bridge(void)
+{
+	struct ast_bridge *ret_bridge = NULL;
+
+	ast_mutex_lock(&dial_bridge_lock);
+
+	if (shutting_down) {
+		goto end;
+	}
+
+	if (dial_bridge) {
+		ret_bridge = ao2_bump(dial_bridge);
+		goto end;
+	}
+
+	dial_bridge = stasis_app_bridge_create_invisible("holding", "dial_bridge", NULL);
+	if (!dial_bridge) {
+		goto end;
+	}
+	ret_bridge = ao2_bump(dial_bridge);
+
+end:
+	ast_mutex_unlock(&dial_bridge_lock);
+	return ret_bridge;
+}
+
+/*!
+ * \brief after bridge callback for the dial bridge
+ *
+ * The only purpose of this callback is to ensure that the control structure's
+ * bridge pointer is NULLed
+ */
+static void dial_bridge_after_cb(struct ast_channel *chan, void *data)
+{
+	struct stasis_app_control *control = data;
+
+	control->bridge = NULL;
+}
+
+static void dial_bridge_after_cb_failed(enum ast_bridge_after_cb_reason reason, void *data)
+{
+	struct stasis_app_control *control = data;
+
+	dial_bridge_after_cb(control->channel, data);
+}
+
+/*!
+ * \brief Add a channel to the singleton dial bridge.
+ *
+ * \param control The Stasis control structure
+ * \param chan The channel to add to the bridge
+ * \retval -1 Failed
+ * \retval 0 Success
+ */
+static int add_to_dial_bridge(struct stasis_app_control *control, struct ast_channel *chan)
+{
+	struct ast_bridge *bridge;
+
+	bridge = get_dial_bridge();
+	if (!bridge) {
+		return -1;
+	}
+
+	control->bridge = bridge;
+	ast_bridge_set_after_callback(chan, dial_bridge_after_cb, dial_bridge_after_cb_failed, control);
+	if (ast_bridge_impart(bridge, chan, NULL, NULL, AST_BRIDGE_IMPART_CHAN_DEPARTABLE)) {
+		control->bridge = NULL;
+		ao2_ref(bridge, -1);
+		return -1;
+	}
+
+	ao2_ref(bridge, -1);
+
+	return 0;
+}
+
+/*!
+ * \brief Depart a channel from a bridge, and potentially add it back to the dial bridge
+ *
+ * \param control Take a guess
+ * \param chan Take another guess
+ */
+static int depart_channel(struct stasis_app_control *control, struct ast_channel *chan)
+{
+	ast_bridge_depart(chan);
+
+	if (!ast_check_hangup(chan) && ast_channel_state(chan) != AST_STATE_UP) {
+		/* Channel is still being dialed, so put it back in the dialing bridge */
+		add_to_dial_bridge(control, chan);
+	}
+
+	return 0;
+}
+
 static int bridge_channel_depart(struct stasis_app_control *control,
 	struct ast_channel *chan, void *data)
 {
@@ -843,7 +967,7 @@ static int bridge_channel_depart(struct stasis_app_control *control,
 	ast_debug(3, "%s: Channel departing bridge\n",
 		ast_channel_uniqueid(chan));
 
-	ast_bridge_depart(chan);
+	depart_channel(control, chan);
 
 	return 0;
 }
@@ -903,6 +1027,107 @@ static void bridge_after_cb_failed(enum ast_bridge_after_cb_reason reason,
 		ast_bridge_after_cb_reason_string(reason));
 }
 
+/*!
+ * \brief Dial timeout datastore
+ *
+ * A datastore is used because a channel may change
+ * bridges during the course of a dial attempt. This
+ * may be because the channel changes from the dial bridge
+ * to a standard bridge, or it may move between standard
+ * bridges. In order to keep the dial timeout, we need
+ * to keep the timeout information local to the channel.
+ * That is what this datastore is for
+ */
+struct ast_datastore_info timeout_datastore = {
+	.type = "ARI dial timeout",
+};
+
+static int hangup_channel(struct stasis_app_control *control,
+	struct ast_channel *chan, void *data)
+{
+	ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT);
+	return 0;
+}
+
+/*!
+ * \brief Dial timeout
+ *
+ * This is a bridge interval hook callback. The interval hook triggering
+ * means that the dial timeout has been reached. If the channel has not
+ * been answered by the time this callback is called, then the channel
+ * is hung up
+ *
+ * \param bridge_channel Bridge channel on which interval hook has been called
+ * \param ignore Ignored
+ * \return -1 (i.e. remove the interval hook)
+ */
+static int bridge_timeout(struct ast_bridge_channel *bridge_channel, void *ignore)
+{
+	struct ast_datastore *datastore;
+	RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+
+	control = stasis_app_control_find_by_channel(bridge_channel->chan);
+
+	ast_channel_lock(bridge_channel->chan);
+	if (ast_channel_state(bridge_channel->chan) != AST_STATE_UP) {
+		/* Don't bother removing the datastore because it will happen when the channel is hung up */
+		ast_channel_unlock(bridge_channel->chan);
+		stasis_app_send_command_async(control, hangup_channel, NULL, NULL);
+		return -1;
+	}
+
+	datastore = ast_channel_datastore_find(bridge_channel->chan, &timeout_datastore, NULL);
+	if (!datastore) {
+		ast_channel_unlock(bridge_channel->chan);
+		return -1;
+	}
+	ast_channel_datastore_remove(bridge_channel->chan, datastore);
+	ast_channel_unlock(bridge_channel->chan);
+	ast_datastore_free(datastore);
+
+	return -1;
+}
+
+/*!
+ * \brief Set a dial timeout interval hook on the channel.
+ *
+ * The absolute time that the timeout should occur is stored on
+ * a datastore on the channel. This time is converted into a relative
+ * number of milliseconds in the future. Then an interval hook is set
+ * to trigger in that number of milliseconds.
+ *
+ * \pre chan is locked
+ *
+ * \param chan The channel on which to set the interval hook
+ */
+static void set_interval_hook(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore;
+	struct timeval *hangup_time;
+	int64_t ms;
+	struct ast_bridge_channel *bridge_channel;
+
+	datastore = ast_channel_datastore_find(chan, &timeout_datastore, NULL);
+	if (!datastore) {
+		return;
+	}
+
+	hangup_time = datastore->data;
+
+	ms = ast_tvdiff_ms(*hangup_time, ast_tvnow());
+	bridge_channel = ast_channel_get_bridge_channel(chan);
+	if (!bridge_channel) {
+		return;
+	}
+
+	if (ast_bridge_interval_hook(bridge_channel->features, 0, ms > 0 ? ms : 1,
+			bridge_timeout, NULL, NULL, 0)) {
+		return;
+	}
+
+	ast_queue_frame(bridge_channel->chan, &ast_null_frame);
+}
+
 int control_swap_channel_in_bridge(struct stasis_app_control *control, struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap)
 {
 	int res;
@@ -969,6 +1194,10 @@ int control_swap_channel_in_bridge(struct stasis_app_control *control, struct as
 
 		ast_assert(stasis_app_get_bridge(control) == NULL);
 		control->bridge = bridge;
+
+		ast_channel_lock(chan);
+		set_interval_hook(chan);
+		ast_channel_unlock(chan);
 	}
 	return 0;
 }
@@ -1011,7 +1240,7 @@ static int app_control_remove_channel_from_bridge(
 		return -1;
 	}
 
-	ast_bridge_depart(chan);
+	depart_channel(control, chan);
 	return 0;
 }
 
@@ -1132,83 +1361,110 @@ struct stasis_app *control_app(struct stasis_app_control *control)
 	return control->app;
 }
 
-static void app_control_dial_destroy(void *data)
+struct control_dial_args {
+	unsigned int timeout;
+	char dialstring[0];
+};
+
+static struct control_dial_args *control_dial_args_alloc(const char *dialstring,
+	unsigned int timeout)
 {
-	struct ast_dial *dial = data;
+	struct control_dial_args *args;
+
+	args = ast_malloc(sizeof(*args) + strlen(dialstring) + 1);
+	if (!args) {
+		return NULL;
+	}
+
+	args->timeout = timeout;
+	/* Safe */
+	strcpy(args->dialstring, dialstring);
 
-	ast_dial_join(dial);
-	ast_dial_destroy(dial);
+	return args;
 }
 
-static int app_control_remove_dial(struct stasis_app_control *control,
-	struct ast_channel *chan, void *data)
+static void control_dial_args_destroy(void *data)
 {
-	if (ast_dial_state(control->dial) != AST_DIAL_RESULT_ANSWERED) {
-		ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT);
-	}
-	control->dial = NULL;
-	return 0;
+	struct control_dial_args *args = data;
+
+	ast_free(args);
 }
 
-static void on_dial_state(struct ast_dial *dial)
+/*!
+ * \brief Set dial timeout on a channel to be dialed.
+ *
+ * \param chan The channel on which to set the dial timeout
+ * \param timeout The timeout in seconds
+ */
+static int set_timeout(struct ast_channel *chan, unsigned int timeout)
 {
-	enum ast_dial_result state;
-	struct stasis_app_control *control;
-	struct ast_channel *chan;
+	struct ast_datastore *datastore;
+	struct timeval *hangup_time;
 
-	state = ast_dial_state(dial);
-	control = ast_dial_get_user_data(dial);
+	hangup_time = ast_malloc(sizeof(struct timeval));
 
-	switch (state) {
-	case AST_DIAL_RESULT_ANSWERED:
-		/* Need to steal the reference to the answered channel so that dial doesn't
-		 * try to hang it up when we destroy the dial structure.
-		 */
-		chan = ast_dial_answered_steal(dial);
-		ast_channel_unref(chan);
-		/* Fall through intentionally */
-	case AST_DIAL_RESULT_INVALID:
-	case AST_DIAL_RESULT_FAILED:
-	case AST_DIAL_RESULT_TIMEOUT:
-	case AST_DIAL_RESULT_HANGUP:
-	case AST_DIAL_RESULT_UNANSWERED:
-		/* The dial has completed, so we need to break the Stasis loop so
-		 * that the channel's frames are handled in the proper place now.
-		 */
-		stasis_app_send_command_async(control, app_control_remove_dial, dial, app_control_dial_destroy);
-		break;
-	case AST_DIAL_RESULT_TRYING:
-	case AST_DIAL_RESULT_RINGING:
-	case AST_DIAL_RESULT_PROGRESS:
-	case AST_DIAL_RESULT_PROCEEDING:
-		break;
+	datastore = ast_datastore_alloc(&timeout_datastore, NULL);
+	if (!datastore) {
+		return -1;
+	}
+	*hangup_time = ast_tvadd(ast_tvnow(), ast_samp2tv(timeout, 1));
+	datastore->data = hangup_time;
+
+	ast_channel_lock(chan);
+	ast_channel_datastore_add(chan, datastore);
+
+	if (ast_channel_is_bridged(chan)) {
+		set_interval_hook(chan);
 	}
+	ast_channel_unlock(chan);
+
+	return 0;
 }
 
 static int app_control_dial(struct stasis_app_control *control,
 	struct ast_channel *chan, void *data)
 {
-	struct ast_dial *dial = data;
+	struct control_dial_args *args = data;
+	int bridged;
 
-	ast_dial_set_state_callback(dial, on_dial_state);
-	/* The dial API gives the option of providing a caller channel, but for
-	 * Stasis, we really don't want to do that. The Dial API will take liberties such
-	 * as passing frames along to the calling channel (think ringing, progress, etc.).
-	 * This is not desirable in ARI applications since application writers should have
-	 * control over what does/does not get indicated to the calling channel
-	 */
-	ast_dial_run(dial, NULL, 1);
-	control->dial = dial;
+	ast_channel_lock(chan);
+	bridged = ast_channel_is_bridged(chan);
+	ast_channel_unlock(chan);
+
+	if (!bridged && add_to_dial_bridge(control, chan)) {
+		return -1;
+	}
+
+	if (args->timeout && set_timeout(chan, args->timeout)) {
+		return -1;
+	}
+
+	if (ast_call(chan, args->dialstring, 0)) {
+		return -1;
+	}
 
 	return 0;
 }
 
-struct ast_dial *stasis_app_get_dial(struct stasis_app_control *control)
+int stasis_app_control_dial(struct stasis_app_control *control,
+		const char *dialstring, unsigned int timeout)
 {
-	return control->dial;
+	struct control_dial_args *args;
+
+	args = control_dial_args_alloc(dialstring, timeout);
+	if (!args) {
+		return -1;
+	}
+
+	return stasis_app_send_command_async(control, app_control_dial,
+		args, control_dial_args_destroy);
 }
 
-int stasis_app_control_dial(struct stasis_app_control *control, struct ast_dial *dial)
+void stasis_app_control_shutdown(void)
 {
-	return stasis_app_send_command_async(control, app_control_dial, dial, NULL);
+	ast_mutex_lock(&dial_bridge_lock);
+	shutting_down = 1;
+	ao2_cleanup(dial_bridge);
+	dial_bridge = NULL;
+	ast_mutex_unlock(&dial_bridge_lock);
 }