diff --git a/CHANGES b/CHANGES
index dc261055e9a17cac045ca42998e6414e2b5fa4db..f8712b38cdd320ba37f7adde7ce3c2c3c7a90f77 100644
--- a/CHANGES
+++ b/CHANGES
@@ -12,6 +12,23 @@
 --- Functionality changes from Asterisk 12 to Asterisk 13 --------------------
 ------------------------------------------------------------------------------
 
+------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 12.2.0 to Asterisk 12.3.0 ------------
+------------------------------------------------------------------------------
+
+ARI
+------------------
+ * A new Playback URI 'tone' has been added. Tones are specified either as
+   an indication name (e.g. 'tone:busy') from indications.conf or as a tone
+   pattern (e.g. 'tone:240/250,0/250'). Tones differ from normal playback
+   URIs in that they must be stopped manually and will continue to occupy
+   a channel's ARI control queue until they are stopped. They also can not
+   be rewound or fastforwarded.
+
+------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 12.1.0 to Asterisk 12.2.0 ------------
+------------------------------------------------------------------------------
+
 Applications
 --------------------------
  * Record application now has an option 'o' which allows 0 to act as an exit
@@ -143,7 +160,7 @@ AMI
    used to set the UniqueId on creation.  The other id is assigned to the
    second channel when dialing LOCAL, or defaults to appending ;2 if only
    the single Id is given.
- 
+
  * The Mixmonitor action now has a "Command" header that can be used to
    indicate a post-process command to run once recording finishes.
 
diff --git a/include/asterisk/app.h b/include/asterisk/app.h
index 70cf7527091f76172da1836cb9bb9e40ec64584b..65d74dcd138ad3ebeae2d67ae5583c8e793e341a 100644
--- a/include/asterisk/app.h
+++ b/include/asterisk/app.h
@@ -879,6 +879,14 @@ int ast_control_streamfile_lang(struct ast_channel *chan, const char *file,
 	const char *fwd, const char *rev, const char *stop, const char *suspend,
 	const char *restart, int skipms, const char *lang, long *offsetms);
 
+/*!
+ * \brief Controls playback of a tone
+ *
+ * \retval 0 on success
+ * \retval Non-zero on failure
+ */
+int ast_control_tone(struct ast_channel *chan, const char *tone);
+
 /*!
  * \brief Stream a file with fast forward, pause, reverse, restart.
  * \param chan
diff --git a/main/app.c b/main/app.c
index b6c9882583f28e98094511f1bc2e1ecadfb551d7..f7bb0048ca573f69073d081f8cb6b58c7b027b24 100644
--- a/main/app.c
+++ b/main/app.c
@@ -1070,6 +1070,143 @@ int ast_control_streamfile_lang(struct ast_channel *chan, const char *file,
 	return control_streamfile(chan, file, fwd, rev, stop, suspend, restart, skipms, offsetms, lang, NULL);
 }
 
+enum control_tone_frame_response_result {
+	CONTROL_TONE_RESPONSE_FAILED = -1,
+	CONTROL_TONE_RESPONSE_NORMAL = 0,
+	CONTROL_TONE_RESPONSE_FINISHED = 1,
+};
+
+static enum control_tone_frame_response_result control_tone_frame_response(struct ast_channel *chan, struct ast_frame *fr, struct ast_tone_zone_sound *ts, const char *tone, int *paused)
+{
+	switch (fr->subclass.integer) {
+	case AST_CONTROL_STREAM_STOP:
+		ast_playtones_stop(chan);
+		return CONTROL_TONE_RESPONSE_FINISHED;
+	case AST_CONTROL_STREAM_SUSPEND:
+		if (*paused) {
+			*paused = 0;
+			if (ast_playtones_start(chan, 0, ts ? ts->data : tone, 0)) {
+				return CONTROL_TONE_RESPONSE_FAILED;
+			}
+		} else {
+			*paused = 1;
+			ast_playtones_stop(chan);
+		}
+		return CONTROL_TONE_RESPONSE_NORMAL;
+	case AST_CONTROL_STREAM_RESTART:
+		ast_playtones_stop(chan);
+		if (ast_playtones_start(chan, 0, ts ? ts->data : tone, 0)) {
+			return CONTROL_TONE_RESPONSE_FAILED;
+		}
+		return CONTROL_TONE_RESPONSE_NORMAL;
+	case AST_CONTROL_STREAM_REVERSE:
+		ast_log(LOG_NOTICE, "Media control operation 'reverse' not supported for media type 'tone'\n");
+		return CONTROL_TONE_RESPONSE_NORMAL;
+	case AST_CONTROL_STREAM_FORWARD:
+		ast_log(LOG_NOTICE, "Media control operation 'forward' not supported for media type 'tone'\n");
+		return CONTROL_TONE_RESPONSE_NORMAL;
+	case AST_CONTROL_HANGUP:
+	case AST_CONTROL_BUSY:
+	case AST_CONTROL_CONGESTION:
+		return CONTROL_TONE_RESPONSE_FINISHED;
+	}
+
+	return CONTROL_TONE_RESPONSE_NORMAL;
+}
+
+static int parse_tone_uri(char *tone_parser,
+	const char **tone_indication,
+	const char **tone_zone)
+{
+	*tone_indication = strsep(&tone_parser, ";");
+
+	if (ast_strlen_zero(tone_parser)) {
+		/* Only the indication is included */
+		return 0;
+	}
+
+	if (!(strncmp(tone_parser, "tonezone=", 9))) {
+		*tone_zone = tone_parser + 9;
+	} else {
+		ast_log(LOG_ERROR, "Unexpected Tone URI component: %s\n", tone_parser);
+		return -1;
+	}
+
+	return 0;
+}
+
+int ast_control_tone(struct ast_channel *chan, const char *tone)
+{
+	struct ast_tone_zone *zone = NULL;
+	struct ast_tone_zone_sound *ts;
+	int paused = 0;
+	int res;
+
+	const char *tone_indication = NULL;
+	const char *tone_zone = NULL;
+	char *tone_uri_parser;
+
+	if (ast_strlen_zero(tone)) {
+		return -1;
+	}
+
+	tone_uri_parser = ast_strdupa(tone);
+
+	if (parse_tone_uri(tone_uri_parser, &tone_indication, &tone_zone)) {
+		return -1;
+	}
+
+	if (tone_zone) {
+		zone = ast_get_indication_zone(tone_zone);
+	}
+
+	ts = ast_get_indication_tone(zone ? zone : ast_channel_zone(chan), tone_indication);
+
+	if (ast_playtones_start(chan, 0, ts ? ts->data : tone_indication, 0)) {
+		return -1;
+	}
+
+	for (;;) {
+		struct ast_frame *fr;
+		int res;
+
+		if (ast_waitfor(chan, -1) < 0) {
+			res = -1;
+			break;
+		}
+
+		fr = ast_read_noaudio(chan);
+
+		if (!fr) {
+			res = -1;
+			break;
+		}
+
+		if (fr->frametype != AST_FRAME_CONTROL) {
+			continue;
+		}
+
+		res = control_tone_frame_response(chan, fr, ts, tone_indication, &paused);
+		if (res == CONTROL_TONE_RESPONSE_FINISHED) {
+			res = 0;
+			break;
+		} else if (res == CONTROL_TONE_RESPONSE_FAILED) {
+			res = -1;
+			break;
+		}
+	}
+
+	if (ts) {
+		ast_tone_zone_sound_unref(ts);
+	}
+
+	if (zone) {
+		ast_tone_zone_unref(zone);
+	}
+
+	return res;
+}
+
 int ast_play_and_wait(struct ast_channel *chan, const char *fn)
 {
 	int d = 0;
diff --git a/res/ari/resource_bridges.h b/res/ari/resource_bridges.h
index 5dca82781198001e94690f03594f9fbbf7b80efa..404760c91414f501c23338bff1a0d79654022656 100644
--- a/res/ari/resource_bridges.h
+++ b/res/ari/resource_bridges.h
@@ -268,7 +268,7 @@ int ast_ari_bridges_play_parse_body(
 /*!
  * \brief Start playback of media on a bridge.
  *
- * The media URI may be any of a number of URI's. Currently sound: and recording: URI's are supported. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)
+ * The media URI may be any of a number of URI's. Currently sound:, recording:, number:, digits:, characters:, and tone: URI's are supported. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)
  *
  * \param headers HTTP headers
  * \param args Swagger parameters
diff --git a/res/ari/resource_channels.h b/res/ari/resource_channels.h
index cf48cee7545fae5143a1301fc8fd4392d6c3051a..bc50c3da4cc9986b2891bd6aac0bfa1051ebefd3 100644
--- a/res/ari/resource_channels.h
+++ b/res/ari/resource_channels.h
@@ -460,7 +460,7 @@ int ast_ari_channels_play_parse_body(
 /*!
  * \brief Start playback of media.
  *
- * The media URI may be any of a number of URI's. Currently sound: and recording: URI's are supported. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)
+ * The media URI may be any of a number of URI's. Currently sound:, recording:, number:, digits:, characters:, and tone: URI's are supported. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)
  *
  * \param headers HTTP headers
  * \param args Swagger parameters
diff --git a/res/res_stasis_playback.c b/res/res_stasis_playback.c
index 79c2bab9ce879ff24dde1dbfecce6e04a2c9f253..960f2e3342f6de0bc6798a68c282d099e447565a 100644
--- a/res/res_stasis_playback.c
+++ b/res/res_stasis_playback.c
@@ -48,6 +48,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/stringfields.h"
 #include "asterisk/uuid.h"
 #include "asterisk/say.h"
+#include "asterisk/indications.h"
 
 /*! Number of hash buckets for playback container. Keep it prime! */
 #define PLAYBACK_BUCKETS 127
@@ -60,6 +61,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #define NUMBER_URI_SCHEME "number:"
 #define DIGITS_URI_SCHEME "digits:"
 #define CHARACTERS_URI_SCHEME "characters:"
+#define TONE_URI_SCHEME "tone:"
 
 /*! Container of all current playbacks */
 static struct ao2_container *playbacks;
@@ -323,6 +325,9 @@ static void play_on_channel(struct stasis_app_playback *playback,
 	} else if (ast_begins_with(playback->media, CHARACTERS_URI_SCHEME)) {
 		res = ast_say_character_str(chan, playback->media + strlen(CHARACTERS_URI_SCHEME),
 			stop, playback->language, AST_SAY_CASE_NONE);
+	} else if (ast_begins_with(playback->media, TONE_URI_SCHEME)) {
+		playback->controllable = 1;
+		res = ast_control_tone(chan, playback->media + strlen(TONE_URI_SCHEME));
 	} else {
 		/* Play URL */
 		ast_log(LOG_ERROR, "Attempted to play URI '%s' on channel '%s' but scheme is unsupported",
diff --git a/rest-api/api-docs/bridges.json b/rest-api/api-docs/bridges.json
index 56a7a941e07bfe4fbb87b1e3056ffb5abecdf25f..4d2c77cea71ad4bfac8565e6caa82c9bdf0b7b0a 100644
--- a/rest-api/api-docs/bridges.json
+++ b/rest-api/api-docs/bridges.json
@@ -314,7 +314,7 @@
 				{
 					"httpMethod": "POST",
 					"summary": "Start playback of media on a bridge.",
-					"notes": "The media URI may be any of a number of URI's. Currently sound: and recording: URI's are supported. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)",
+					"notes": "The media URI may be any of a number of URI's. Currently sound:, recording:, number:, digits:, characters:, and tone: URI's are supported. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)",
 					"nickname": "play",
 					"responseClass": "Playback",
 					"parameters": [
diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json
index d940f438349ae318b0278aedb347cc54ec2f2cb9..cc7feb6a919e6f97152d4bc7a7a00712a6088dec 100644
--- a/rest-api/api-docs/channels.json
+++ b/rest-api/api-docs/channels.json
@@ -808,7 +808,7 @@
 				{
 					"httpMethod": "POST",
 					"summary": "Start playback of media.",
-					"notes": "The media URI may be any of a number of URI's. Currently sound: and recording: URI's are supported. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)",
+					"notes": "The media URI may be any of a number of URI's. Currently sound:, recording:, number:, digits:, characters:, and tone: URI's are supported. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)",
 					"nickname": "play",
 					"responseClass": "Playback",
 					"parameters": [