Commit 19eb1897da for asterisk.org

commit 19eb1897da6a3eff1ff8bb3b8413df3ddc4449ac
Author: Mike Bradeen <mbradeen@sangoma.com>
Date:   Tue Mar 31 10:56:51 2026 -0600

    res ari: Add attachable states to Channels and Bridges

    Adds the ability to attach multiple states to both Channels and Bridges in the form
    of variables that are included in all events on the associated object.

    First, this adds an optional boolean field to channel variables 'report_events'
    that causes the variable to automatically be included in all events on that channel.

    To allow this, variables can now be either name value pairs (the current format):
    `<variable_name>: '<value_string>'`
     - or -
    `<variable_name>: {value: '<value_string>', report_events: [true|false]}`

    If the old format is used or 'report_events' is not included, it will default to
    false and retain current behavior.

    Second, this extends both reported and unreported variables to Bridges so they too
    may have stateful information.

    Resolves: #1910

    UserNote: Bridge variables now can be set and retrieved via the following paths:
    `/bridges/{bridgeId}/variable`
    `/bridges/{bridgeId}/variables`
    Both Bridge and Channel variables can now be set with an optional 'report_events'
    boolean flag that will cause those variables to be included on all events on that
    object. The 'report_events' flag will default to False if not set to maintain
    backwards capability.
    To allow this, variables can now be either name value pairs (the current format):
    `<variable_name>: '<value_string>'`
     - or -
    `<variable_name>: {value: '<value_string>', report_events: [true|false]}`

diff --git a/include/asterisk/bridge.h b/include/asterisk/bridge.h
index 58f627a253..a45bed31e9 100644
--- a/include/asterisk/bridge.h
+++ b/include/asterisk/bridge.h
@@ -345,6 +345,8 @@ struct ast_bridge_snapshot {
 	enum ast_bridge_video_mode_type video_mode;
 	/*! The time of bridge creation */
 	struct timeval creationtime;
+	/*! Variables to be included in ARI events */
+	struct varshead *bridgevars;
 };

 /*!
@@ -410,6 +412,10 @@ struct ast_bridge {
 	struct ast_bridge_snapshot *current_snapshot;
 	/*! The time of bridge creation */
 	struct timeval creationtime;
+	/*! A linked list for bridge variables */
+	struct varshead bridgevars;
+	/*! A vector of variable names to be included in ARI events on this bridge */
+	AST_VECTOR(, char *) ari_reportable_variable_names;
 };

 /*! \brief Bridge base class virtual method table. */
@@ -854,6 +860,57 @@ int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan);
  */
 void ast_bridge_vars_set(struct ast_channel *chan, const char *name, const char *pvtid);

+/*!
+ * \brief Set a variable on the bridge.
+ * \since 20.20.0
+ * \since 22.10.0
+ * \since 23.4.0
+ *
+ * \pre Bridge is locked
+ *
+ * \param bridge Bridge to operate on.
+ * \param name Name of variable to set.
+ * \param value Value of variable to set.  (NULL to delete variable.)
+ * \param report_events If non-zero, the variable change will be reported in ARI events.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int ast_bridge_set_variable(struct ast_bridge *bridge, const char *name, const char *value, int report_events);
+
+/*!
+ * \brief Get value a variable from the bridge by name.
+ * \since 20.20.0
+ * \since 22.10.0
+ * \since 23.4.0
+ *
+ * \pre Bridge is locked
+ *
+ * \param bridge Bridge to operate on.
+ * \param name Name of variable to get.
+ *
+ * \retval Value of the variable on success.
+ * \retval NULL on failure.
+ */
+const char *ast_bridge_get_variable(const struct ast_bridge *bridge, const char *name);
+
+/*!
+ * \brief Get a list of variables that should be included in ARI events for this bridge.
+ * \since 20.20.0
+ * \since 22.10.0
+ * \since 23.4.0
+ *
+ * \pre Bridge is locked
+ *
+ * \param bridge Bridge to operate on.
+ *
+ * \note The returned list must be freed by the caller.
+ *
+ * \retval A pointer to the head of a linked list of variables to include in ARI events on success.
+ * \retval NULL on failure or if no variables are set to be reported.
+ */
+struct varshead *ast_bridge_get_ari_reportable_variables(struct ast_bridge *bridge);
+
 struct ast_unreal_pvt;

 /*!
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index f9849bda18..afc97606b5 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -4414,6 +4414,12 @@ int ast_channel_fd_count(const struct ast_channel *chan);
  */
 int ast_channel_fd_add(struct ast_channel *chan, int value);

+/* ARI reportable variables accessors */
+size_t ast_channel_internal_ari_reportable_vars_count(const struct ast_channel *chan);
+char *ast_channel_internal_ari_reportable_vars_get(const struct ast_channel *chan, size_t index);
+int ast_channel_internal_ari_reportable_vars_append(struct ast_channel *chan, char *key);
+char *ast_channel_internal_ari_reportable_vars_remove(struct ast_channel *chan, size_t index);
+
 pthread_t ast_channel_blocker(const struct ast_channel *chan);
 void ast_channel_blocker_set(struct ast_channel *chan, pthread_t value);

@@ -4603,6 +4609,20 @@ void ast_channel_set_ari_vars(size_t varc, char **vars);
  */
 struct varshead *ast_channel_get_ari_vars(struct ast_channel *chan);

+/*!
+ * \since 20.20.0
+ * \since 22.10.0
+ * \since 23.4.0
+ * \brief Set whether a channel variable should be included in REST events on the channel.
+ *
+ * \param chan Channel to update.
+ * \param variable Variable name or dialplan function expression.
+ * \param report_events Non-zero to include in REST events, zero to omit.
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int ast_channel_set_ari_var_reportable(struct ast_channel *chan, const char *variable, int report_events);
+
 /*!
  * \since 12
  * \brief Gets the variables for a given channel, as set using pbx_builtin_setvar_helper().
diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h
index 44f811cf7f..d522a1ff68 100644
--- a/include/asterisk/stasis_app.h
+++ b/include/asterisk/stasis_app.h
@@ -636,6 +636,19 @@ int stasis_app_control_answer(struct stasis_app_control *control);
  */
 int stasis_app_control_set_channel_var(struct stasis_app_control *control, const char *variable, const char *value);

+/*!
+ * \brief Set a variable on the channel associated with this control to value with option of including in events.
+ * \param control Control for \c res_stasis.
+ * \param variable The name of the variable
+ * \param value The value to set the variable to
+ * \param report_events Whether to include this variable in channel events.
+ *
+ * \return 0 for success.
+ * \return -1 for error.
+ */
+int stasis_app_control_set_channel_var_reportable(struct stasis_app_control *control, const char *variable, const char *value, int report_events);
+
+
 /*!
  * \brief Place the channel associated with the control on hold.
  * \param control Control for \c res_stasis.
@@ -911,6 +924,20 @@ struct ast_bridge *stasis_app_get_bridge(struct stasis_app_control *control);
  */
 void stasis_app_bridge_destroy(const char *bridge_id);

+/*!
+ * \brief Set or clear a variable on a bridge and control ARI event reporting for it.
+ *
+ * \param bridge_id Uniqueid of bridge
+ * \param variable Variable name
+ * \param value Variable value (NULL/empty clears)
+ * \param report_events Non-zero to include in ARI bridge events
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int stasis_app_bridge_set_var_reportable(const char *bridge_id, const char *variable,
+	const char *value, int report_events);
+
 /*!
  * \brief Get the Stasis message sanitizer for app_stasis applications
  *
diff --git a/main/bridge.c b/main/bridge.c
index cc2183b526..7bcc32cb54 100644
--- a/main/bridge.c
+++ b/main/bridge.c
@@ -149,6 +149,17 @@ static unsigned int optimization_id;
 /* Variable name - stores peer information about the most recent attended transfer */
 #define ATTENDEDTRANSFER "ATTENDEDTRANSFER"

+/*!
+ * \brief Compare a bridge variable name with a given name.
+ *
+ * \param var_str The variable name to compare.
+ * \param name The name to compare against.
+ *
+ * \retval 0 if the names do not match.
+ * \retval 1 if the names match.
+ */
+#define BV_NAME_CMP(var_str, name) !strcmp(var_str, name)
+
 static void cleanup_video_mode(struct ast_bridge *bridge);

 /*! Default DTMF keys for built in features */
@@ -659,6 +670,7 @@ static void bridge_handle_actions(struct ast_bridge *bridge)
 static void destroy_bridge(void *obj)
 {
 	struct ast_bridge *bridge = obj;
+	struct ast_var_t *var;

 	ast_debug(1, "Bridge " BRIDGE_PRINTF_SPEC ": actually destroying %s bridge, nobody wants it anymore\n",
 		BRIDGE_PRINTF_VARS(bridge), bridge->v_table->name);
@@ -708,6 +720,12 @@ static void destroy_bridge(void *obj)
 	ast_string_field_free_memory(bridge);
 	ao2_cleanup(bridge->current_snapshot);
 	bridge->current_snapshot = NULL;
+	while ((var = AST_LIST_REMOVE_HEAD(&bridge->bridgevars, entries))) {
+		ast_var_delete(var);
+	}
+	AST_LIST_HEAD_INIT_NOLOCK(&bridge->bridgevars);
+	AST_VECTOR_RESET(&bridge->ari_reportable_variable_names, ast_free);
+	AST_VECTOR_FREE(&bridge->ari_reportable_variable_names);
 }

 struct ast_bridge *bridge_register(struct ast_bridge *bridge)
@@ -776,6 +794,8 @@ struct ast_bridge *bridge_alloc(size_t size, const struct ast_bridge_methods *v_
 	bridge->v_table = v_table;

 	AST_VECTOR_INIT(&bridge->media_types, AST_MEDIA_TYPE_END);
+	AST_LIST_HEAD_INIT_NOLOCK(&bridge->bridgevars);
+	AST_VECTOR_INIT(&bridge->ari_reportable_variable_names, 8);

 	return bridge;
 }
@@ -1286,6 +1306,116 @@ void ast_bridge_vars_set(struct ast_channel *chan, const char *name, const char
 	ast_channel_stage_snapshot_done(chan);
 }

+static int bridge_set_ari_var_reportable(struct ast_bridge *bridge, const char *variable,
+	int report_events)
+{
+	char *var_str;
+
+	if (ast_strlen_zero(variable)) {
+		return -1;
+	}
+
+	if (!report_events) {
+		AST_VECTOR_REMOVE_CMP_UNORDERED(&bridge->ari_reportable_variable_names, variable,
+			BV_NAME_CMP, ast_free);
+		return 0;
+	}
+
+	if (AST_VECTOR_GET_CMP(&bridge->ari_reportable_variable_names, variable, BV_NAME_CMP)) {
+		return 0; /* already present */
+	}
+
+	var_str = ast_strdup(variable);
+	if (!var_str) {
+		return -1;
+	}
+
+	if (AST_VECTOR_APPEND(&bridge->ari_reportable_variable_names, var_str)) {
+		ast_free(var_str);
+		return -1;
+	}
+
+	return 0;
+}
+
+int ast_bridge_set_variable(struct ast_bridge *bridge, const char *name, const char *value,
+	int report_events)
+{
+	struct ast_var_t *var;
+
+	if (ast_strlen_zero(name)) {
+		return -1;
+	}
+
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&bridge->bridgevars, var, entries) {
+		if (!strcmp(ast_var_name(var), name)) {
+			AST_LIST_REMOVE_CURRENT(entries);
+			ast_var_delete(var);
+			break;
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+
+	if (ast_strlen_zero(value)) {
+		/* If the value is empty, remove the variable from the reportable list by
+		   forcing report_events to 0 */
+		bridge_set_ari_var_reportable(bridge, name, 0);
+		return 0;
+	}
+
+	var = ast_var_assign(name, value);
+	if (!var) {
+		return -1;
+	}
+
+	if (bridge_set_ari_var_reportable(bridge, name, report_events)) {
+		ast_var_delete(var);
+		return -1;
+	}
+
+	AST_LIST_INSERT_TAIL(&bridge->bridgevars, var, entries);
+	return 0;
+}
+
+const char *ast_bridge_get_variable(const struct ast_bridge *bridge, const char *name)
+{
+	return ast_var_find(&bridge->bridgevars, name);
+}
+
+struct varshead *ast_bridge_get_ari_reportable_variables(struct ast_bridge *bridge)
+{
+	struct varshead *ret;
+	char *var_str;
+	size_t i;
+
+	if (AST_VECTOR_SIZE(&bridge->ari_reportable_variable_names) == 0) {
+		return NULL;
+	}
+
+	ret = ast_var_list_create();
+	if (!ret) {
+		return NULL;
+	}
+
+	for (i = 0; i < AST_VECTOR_SIZE(&bridge->ari_reportable_variable_names); ++i) {
+		const char *val = NULL;
+		struct ast_var_t *var;
+
+		var_str = AST_VECTOR_GET(&bridge->ari_reportable_variable_names, i);
+		val = ast_bridge_get_variable(bridge, var_str);
+
+		var = ast_var_assign(var_str, val ? val : "");
+		if (!var) {
+			ast_var_list_destroy(ret);
+			return NULL;
+		}
+
+		AST_LIST_INSERT_TAIL(ret, var, entries);
+	}
+
+	return ret;
+}
+
 /*!
  * \internal
  * \brief Set BRIDGEPEER and BRIDGEPVTCALLID channel variables in a 2 party bridge.
diff --git a/main/channel.c b/main/channel.c
index 4de4351b70..453f3689f1 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -7903,6 +7903,46 @@ void ast_channel_set_ari_vars(size_t varc, char **vars)
 	channel_set_external_vars(&ari_vars, varc, vars);
 }

+int ast_channel_set_ari_var_reportable(struct ast_channel *chan, const char *variable, int report_events)
+{
+	char *var_str;
+	size_t i, count;
+
+	SCOPED_CHANNELLOCK(lock, chan);
+
+	if (ast_strlen_zero(variable)) {
+		return -1;
+	}
+
+	count = ast_channel_internal_ari_reportable_vars_count(chan);
+	for (i = 0; i < count; ++i) {
+		var_str = ast_channel_internal_ari_reportable_vars_get(chan, i);
+		if (!strcmp(var_str, variable)) {
+			if (!report_events) {
+				var_str = ast_channel_internal_ari_reportable_vars_remove(chan, i);
+				ast_free(var_str);
+			}
+			return 0;
+		}
+	}
+
+	if (!report_events) {
+		return 0;
+	}
+
+	var_str = ast_strdup(variable);
+	if (!var_str) {
+		return -1;
+	}
+
+	if (ast_channel_internal_ari_reportable_vars_append(chan, var_str)) {
+		ast_free(var_str);
+		return -1;
+	}
+
+	return 0;
+}
+
 /*!
  * \brief Destructor for lists of variables.
  * \param obj AO2 object.
@@ -7996,7 +8036,75 @@ struct varshead *ast_channel_get_manager_vars(struct ast_channel *chan)

 struct varshead *ast_channel_get_ari_vars(struct ast_channel *chan)
 {
-	return channel_get_external_vars(&ari_vars, chan);
+	RAII_VAR(struct varshead *, ret, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_str *, tmp, NULL, ast_free);
+	char *var_str;
+	size_t i;
+
+	SCOPED_CHANNELLOCK(lock, chan);
+
+	ret = channel_get_external_vars(&ari_vars, chan);
+
+	if (ast_channel_internal_ari_reportable_vars_count(chan) == 0) {
+		if (!ret) {
+			return NULL;
+		}
+
+		ao2_ref(ret, +1);
+		return ret;
+	}
+
+	if (!ret) {
+		ret = ao2_alloc(sizeof(*ret), varshead_dtor);
+		if (!ret) {
+			return NULL;
+		}
+	}
+
+	tmp = ast_str_create(16);
+	if (!tmp) {
+		return NULL;
+	}
+
+	for (i = 0; i < ast_channel_internal_ari_reportable_vars_count(chan); ++i) {
+		const char *val = NULL;
+		struct ast_var_t *var;
+		int already_present = 0;
+		struct ast_var_t *existing;
+
+		var_str = ast_channel_internal_ari_reportable_vars_get(chan, i);
+
+		AST_LIST_TRAVERSE(ret, existing, entries) {
+			if (!strcmp(ast_var_name(existing), var_str)) {
+				already_present = 1;
+				break;
+			}
+		}
+		if (already_present) {
+			continue;
+		}
+
+		if (strchr(var_str, '(')) {
+			if (ast_func_read2(chan, var_str, &tmp, 0) == 0) {
+				val = ast_str_buffer(tmp);
+			} else {
+				ast_log(LOG_ERROR,
+					"Error invoking function %s\n", var_str);
+			}
+		} else {
+			val = pbx_builtin_getvar_helper(chan, var_str);
+		}
+
+		var = ast_var_assign(var_str, val ? val : "");
+		if (!var) {
+			return NULL;
+		}
+
+		AST_RWLIST_INSERT_TAIL(ret, var, entries);
+	}
+
+	ao2_ref(ret, +1);
+	return ret;
 }

 void ast_channel_close_storage(void)
diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c
index f9b400d321..6c33a19f3f 100644
--- a/main/channel_internal_api.c
+++ b/main/channel_internal_api.c
@@ -1064,6 +1064,35 @@ int ast_channel_fd_count(const struct ast_channel *chan)
 	return AST_VECTOR_SIZE(&chan->fds);
 }

+size_t ast_channel_internal_ari_reportable_vars_count(const struct ast_channel *chan)
+{
+	return AST_VECTOR_SIZE(&chan->ari_report_vars);
+}
+
+char *ast_channel_internal_ari_reportable_vars_get(
+	const struct ast_channel *chan, size_t index)
+{
+	return index < AST_VECTOR_SIZE(&chan->ari_report_vars)
+		? AST_VECTOR_GET(&chan->ari_report_vars, index)
+		: NULL;
+}
+
+int ast_channel_internal_ari_reportable_vars_append(struct ast_channel *chan,
+	char *key)
+{
+	return AST_VECTOR_APPEND(&chan->ari_report_vars, key);
+}
+
+char *ast_channel_internal_ari_reportable_vars_remove(
+	struct ast_channel *chan, size_t index)
+{
+	if (index >= AST_VECTOR_SIZE(&chan->ari_report_vars)) {
+		return NULL;
+	}
+
+	return AST_VECTOR_REMOVE(&chan->ari_report_vars, index, 0);
+}
+
 int ast_channel_fd_add(struct ast_channel *chan, int value)
 {
 	int pos = AST_EXTENDED_FDS;
@@ -1269,6 +1298,7 @@ struct ast_channel *__ast_channel_internal_alloc_with_initializers(void (*destru
 	}

 	AST_VECTOR_INIT(&tmp->fds, AST_MAX_FDS);
+	AST_VECTOR_INIT(&tmp->ari_report_vars, 8);

 	/* Force all channel snapshot segments to be created on first use, so we don't have to check if
 	 * an old snapshot exists.
@@ -1402,6 +1432,8 @@ void ast_channel_internal_cleanup(struct ast_channel *chan)

 	ast_channel_internal_set_stream_topology(chan, NULL);

+	AST_VECTOR_RESET(&chan->ari_report_vars, ast_free);
+	AST_VECTOR_FREE(&chan->ari_report_vars);
 	AST_VECTOR_FREE(&chan->fds);
 }

diff --git a/main/channel_private.h b/main/channel_private.h
index 3ff56128fa..19c1aff7db 100644
--- a/main/channel_private.h
+++ b/main/channel_private.h
@@ -200,6 +200,7 @@ struct ast_channel {
 	struct ast_flags snapshot_segment_flags; /*!< Flags regarding the segments of the snapshot */
 	int linked_in_container; /*!< Whether this channel is linked in a storage container */
 	struct ast_endpoint *endpoint; /*!< The endpoint associated with this channel */
+	AST_VECTOR(, char *) ari_report_vars;	/*!< Channel variable names to be included in ARI events */
 };

 #if defined(__cplusplus) || defined(c_plusplus)
diff --git a/main/stasis_bridges.c b/main/stasis_bridges.c
index b03724b832..ef2b87a5cc 100644
--- a/main/stasis_bridges.c
+++ b/main/stasis_bridges.c
@@ -30,6 +30,7 @@
 #include "asterisk.h"

 #include "asterisk/astobj2.h"
+#include "asterisk/json.h"
 #include "asterisk/stasis.h"
 #include "asterisk/stasis_cache_pattern.h"
 #include "asterisk/channel.h"
@@ -224,6 +225,8 @@ static void bridge_snapshot_dtor(void *obj)
 	ast_string_field_free_memory(snapshot);
 	ao2_cleanup(snapshot->channels);
 	snapshot->channels = NULL;
+	ast_var_list_destroy(snapshot->bridgevars);
+	snapshot->bridgevars = NULL;
 }

 struct ast_bridge_snapshot *ast_bridge_snapshot_create(struct ast_bridge *bridge)
@@ -285,6 +288,8 @@ struct ast_bridge_snapshot *ast_bridge_snapshot_create(struct ast_bridge *bridge
 			ast_channel_uniqueid(bridge->softmix.video_mode.mode_data.talker_src_data.chan_vsrc));
 	}

+	snapshot->bridgevars = ast_bridge_get_ari_reportable_variables(bridge);
+
 	return snapshot;
 }

@@ -783,6 +788,11 @@ struct ast_json *ast_bridge_snapshot_to_json(
 			ast_json_string_create(snapshot->video_source_id));
 	}

+	if (snapshot->bridgevars && !AST_LIST_EMPTY(snapshot->bridgevars)) {
+		ast_json_object_set(json_bridge, "bridgevars",
+			ast_json_channel_vars(snapshot->bridgevars));
+	}
+
 	return json_bridge;
 }

diff --git a/main/stasis_channels.c b/main/stasis_channels.c
index 3913a246f6..0094e03aab 100644
--- a/main/stasis_channels.c
+++ b/main/stasis_channels.c
@@ -1218,9 +1218,13 @@ void ast_channel_publish_varset(struct ast_channel *chan, const char *name, cons
 		return;
 	}

-	/*! If there are manager variables, force a cache update */
-	if (chan && ast_channel_has_manager_vars()) {
-		ast_channel_publish_snapshot(chan);
+	/*! If there are manager or ARI variables, force a cache update */
+	if (chan) {
+		struct varshead *ari_vars = ast_channel_get_ari_vars(chan);
+		if (ast_channel_has_manager_vars() || (ari_vars && !AST_LIST_EMPTY(ari_vars))) {
+			ast_channel_publish_snapshot(chan);
+		}
+		ao2_cleanup(ari_vars);
 	}

 	/* This function is NULL safe for global variables */
diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c
index c80f8bc740..f742aeaad9 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -1809,6 +1809,15 @@ int ast_ari_validate_bridge(struct ast_json *json)
 				res = 0;
 			}
 		} else
+		if (strcmp("bridgevars", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ast_ari_validate_object(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Bridge field bridgevars failed validation\n");
+				res = 0;
+			}
+		} else
 		if (strcmp("channels", ast_json_object_iter_key(iter)) == 0) {
 			int prop_is_valid;
 			has_channels = 1;
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index fd25e0b9e2..4780765868 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -1642,6 +1642,7 @@ ari_validator ast_ari_validate_application_fn(void);
  * Bridge
  * - bridge_class: string (required)
  * - bridge_type: string (required)
+ * - bridgevars: object
  * - channels: List[string] (required)
  * - creationtime: Date (required)
  * - creator: string (required)
diff --git a/res/ari/resource_bridges.c b/res/ari/resource_bridges.c
index 712f6a5ac1..5ae152bd1f 100644
--- a/res/ari/resource_bridges.c
+++ b/res/ari/resource_bridges.c
@@ -1023,12 +1023,114 @@ void ast_ari_bridges_list(struct ast_variable *headers,
 	ast_ari_response_ok(response, ast_json_ref(json));
 }

+static int json_to_ast_variables(struct ast_ari_response *response, struct ast_json *json_variables,
+	struct ast_variable **variables, struct ast_variable **report_event_variables)
+{
+	struct ast_json_iter *it_json_var;
+	struct ast_variable *var_tail = NULL;
+	struct ast_variable *report_var_tail = NULL;
+
+	*variables = NULL;
+	*report_event_variables = NULL;
+
+	for (it_json_var = ast_json_object_iter(json_variables); it_json_var;
+		it_json_var = ast_json_object_iter_next(json_variables, it_json_var)) {
+		struct ast_variable *new_var;
+		const char *key = ast_json_object_iter_key(it_json_var);
+		const char *value = NULL;
+		struct ast_json *json_value = ast_json_object_iter_value(it_json_var);
+		int report_events = 0;
+
+		if (ast_strlen_zero(key)) {
+			continue;
+		}
+
+		if (ast_json_typeof(json_value) == AST_JSON_STRING) {
+			value = ast_json_string_get(json_value);
+		} else if (ast_json_typeof(json_value) == AST_JSON_OBJECT) {
+			struct ast_json *value_field = ast_json_object_get(json_value, "value");
+			struct ast_json *report_field = ast_json_object_get(json_value, "report_events");
+			ast_log(LOG_DEBUG, "Processing variable '%s' with report_events: %s\n", key,
+				report_field ? (ast_json_is_true(report_field) ? "true" : "false") : "not set");
+
+			if (!value_field || ast_json_typeof(value_field) != AST_JSON_STRING) {
+				ast_ari_response_error(response, 400, "Bad Request",
+					"Each object value in 'variables' must include string field 'value'");
+				ast_log(LOG_WARNING, "Missing or invalid 'value' field for variable '%s'\n", key);
+				if (!value_field) {
+					ast_log(LOG_WARNING, "Missing 'value' field for variable '%s'\n", key);
+				} else if (ast_json_typeof(value_field) != AST_JSON_STRING) {
+					ast_log(LOG_WARNING, "Invalid 'value' field for variable '%s' (bad type)\n", key);
+				}
+				goto error;
+			}
+
+			value = ast_json_string_get(value_field);
+
+			if (report_field) {
+				enum ast_json_type report_type = ast_json_typeof(report_field);
+
+				if (report_type != AST_JSON_TRUE && report_type != AST_JSON_FALSE) {
+					ast_ari_response_error(response, 400, "Bad Request",
+						"Field 'report_events' in 'variables' entries must be boolean");
+					ast_log(LOG_WARNING, "Invalid 'report_events' field for variable '%s' (bad type)\n", key);
+					goto error;
+				}
+
+				report_events = ast_json_is_true(report_field);
+			}
+		} else {
+			ast_ari_response_error(response, 400, "Bad Request",
+				"Each value in 'variables' must be a string or an object with 'value' and optional 'report_events'");
+			ast_log(LOG_WARNING, "Invalid value for variable '%s'\n", key);
+			goto error;
+		}
+
+		if (!value) {
+			continue;
+		}
+
+		new_var = ast_variable_new(key, value, "");
+		if (!new_var) {
+			ast_ari_response_alloc_failed(response);
+			goto error;
+		}
+
+		var_tail = ast_variable_list_append_hint(variables, var_tail, new_var);
+
+		if (report_events && report_event_variables) {
+			struct ast_variable *report_var = ast_variable_new(key, "1", "");
+
+			if (!report_var) {
+				ast_ari_response_alloc_failed(response);
+				goto error;
+			}
+
+			report_var_tail = ast_variable_list_append_hint(report_event_variables,
+				report_var_tail, report_var);
+		}
+	}
+
+	return 0;
+
+error:
+	ast_variables_destroy(*variables);
+	*variables = NULL;
+	if (report_event_variables) {
+		ast_variables_destroy(*report_event_variables);
+		*report_event_variables = NULL;
+	}
+	return -1;
+}
+
 void ast_ari_bridges_create(struct ast_variable *headers,
 	struct ast_ari_bridges_create_args *args,
 	struct ast_ari_response *response)
 {
 	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_bridge_snapshot *, snapshot, NULL, ao2_cleanup);
+	struct ast_variable *variables = NULL;
+	struct ast_variable *report_event_variables = NULL;

 	if (ast_bridge_topic_exists(args->bridge_id)) {
 		ast_ari_response_error(
@@ -1037,6 +1139,8 @@ void ast_ari_bridges_create(struct ast_variable *headers,
 		return;
 	}

+	ast_ari_bridges_create_parse_body(args->variables, args);
+
 	bridge = stasis_app_bridge_create(args->type, args->name, args->bridge_id);
 	if (!bridge) {
 		ast_ari_response_error(
@@ -1045,9 +1149,45 @@ void ast_ari_bridges_create(struct ast_variable *headers,
 		return;
 	}

+	if (args->variables && json_to_ast_variables(response, args->variables,
+			&variables, &report_event_variables)) {
+		return;
+	}
+
 	ast_bridge_lock(bridge);
+	if (variables) {
+		struct ast_variable *var;
+
+		for (var = variables; var; var = var->next) {
+			int report_events = 0;
+			struct ast_variable *report_var;
+			char buf[strlen(var->name) + 1];
+			char *variable;
+			strcpy(buf, var->name);
+			/* Strip whitespace from the variable name */
+			variable = ast_strip(buf);
+
+			for (report_var = report_event_variables; report_var;
+				report_var = report_var->next) {
+				if (!strcmp(report_var->name, var->name)) {
+					report_events = 1;
+					break;
+				}
+			}
+
+			if (ast_bridge_set_variable(bridge, variable, var->value, report_events)) {
+				ast_bridge_unlock(bridge);
+				ast_variables_destroy(variables);
+				ast_variables_destroy(report_event_variables);
+				ast_ari_response_alloc_failed(response);
+				return;
+			}
+		}
+	}
 	snapshot = ast_bridge_snapshot_create(bridge);
 	ast_bridge_unlock(bridge);
+	ast_variables_destroy(variables);
+	ast_variables_destroy(report_event_variables);

 	if (!snapshot) {
 		ast_ari_response_error(
@@ -1066,6 +1206,8 @@ void ast_ari_bridges_create_with_id(struct ast_variable *headers,
 {
 	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_bridge_snapshot *, snapshot, NULL, ao2_cleanup);
+	struct ast_variable *variables = NULL;
+	struct ast_variable *report_event_variables = NULL;

 	if (ast_bridge_topic_exists(args->bridge_id)) {
 		ast_ari_response_error(
@@ -1074,6 +1216,8 @@ void ast_ari_bridges_create_with_id(struct ast_variable *headers,
 		return;
 	}

+	ast_ari_bridges_create_with_id_parse_body(args->variables, args);
+
 	bridge = stasis_app_bridge_create(args->type, args->name, args->bridge_id);
 	if (!bridge) {
 		ast_ari_response_error(
@@ -1082,9 +1226,56 @@ void ast_ari_bridges_create_with_id(struct ast_variable *headers,
 		return;
 	}

+	if (args->variables) {
+		struct ast_json *json_variables;
+
+		json_variables = ast_json_object_get(args->variables, "variables");
+		if (json_variables && json_to_ast_variables(response, json_variables,
+			&variables, &report_event_variables)) {
+			if (args->variables) {
+				ast_log(LOG_WARNING, "Failed to parse variables for new bridge '%s'\n", args->bridge_id);
+			} else {
+				ast_log(LOG_WARNING, "Failed to find variables for new bridge '%s'\n", args->bridge_id);
+			}
+			return;
+		}
+	}
+
 	ast_bridge_lock(bridge);
+	if (variables) {
+		struct ast_variable *var;
+
+		for (var = variables; var; var = var->next) {
+			int report_events = 0;
+			struct ast_variable *report_var;
+			char buf[strlen(var->name) + 1];
+			char *variable;
+			strcpy(buf, var->name);
+			/* Strip whitespace from the variable name */
+			variable = ast_strip(buf);
+
+			report_events = 0;
+			for (report_var = report_event_variables; report_var;
+				report_var = report_var->next) {
+				if (!strcmp(report_var->name, var->name)) {
+					report_events = 1;
+					break;
+				}
+			}
+
+			if (ast_bridge_set_variable(bridge, variable, var->value, report_events)) {
+				ast_bridge_unlock(bridge);
+				ast_variables_destroy(variables);
+				ast_variables_destroy(report_event_variables);
+				ast_ari_response_alloc_failed(response);
+				return;
+			}
+		}
+	}
 	snapshot = ast_bridge_snapshot_create(bridge);
 	ast_bridge_unlock(bridge);
+	ast_variables_destroy(variables);
+	ast_variables_destroy(report_event_variables);

 	if (!snapshot) {
 		ast_ari_response_error(
@@ -1161,3 +1352,245 @@ void ast_ari_bridges_clear_video_source(struct ast_variable *headers,
 	ao2_ref(bridge, -1);
 	ast_ari_response_no_content(response);
 }
+
+void ast_ari_bridges_get_bridge_var(struct ast_variable *headers,
+	struct ast_ari_bridges_get_bridge_var_args *args,
+	struct ast_ari_response *response)
+{
+	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+	struct ast_bridge *bridge;
+	const char *value;
+
+	if (ast_strlen_zero(args->variable)) {
+		ast_ari_response_error(response, 400, "Bad Request",
+			"Variable name is required");
+		return;
+	}
+
+	bridge = find_bridge(response, args->bridge_id);
+	if (!bridge) {
+		return;
+	}
+
+	ast_bridge_lock(bridge);
+	value = ast_bridge_get_variable(bridge, args->variable);
+	ast_bridge_unlock(bridge);
+
+	if (!value) {
+		ao2_ref(bridge, -1);
+		ast_ari_response_error(response, 404, "Not Found",
+			"Provided variable was not found");
+		return;
+	}
+
+	json = ast_json_pack("{s: s}", "value", value);
+	ao2_ref(bridge, -1);
+
+	if (!json) {
+		ast_ari_response_alloc_failed(response);
+		return;
+	}
+
+	ast_ari_response_ok(response, ast_json_ref(json));
+}
+
+void ast_ari_bridges_set_bridge_var(struct ast_variable *headers,
+	struct ast_ari_bridges_set_bridge_var_args *args,
+	struct ast_ari_response *response)
+{
+	struct ast_bridge *bridge;
+	char buf[strlen(args->variable) + 1];
+	char *variable;
+
+	if (ast_strlen_zero(args->variable)) {
+		ast_ari_response_error(response, 400, "Bad Request",
+			"Variable name is required");
+		return;
+	}
+
+	bridge = find_bridge(response, args->bridge_id);
+	if (!bridge) {
+		return;
+	}
+	ao2_ref(bridge, -1);
+
+	strcpy(buf, args->variable);
+	/* Strip whitespace from the variable name */
+	variable = ast_strip(buf);
+
+	if (stasis_app_bridge_set_var_reportable(args->bridge_id, variable, args->value,
+			args->report_events)) {
+		ast_ari_response_error(response, 400, "Bad Request",
+			"Failed to execute function");
+		return;
+	}
+
+	ast_ari_response_no_content(response);
+}
+
+void ast_ari_bridges_get_bridge_vars(struct ast_variable *headers,
+	struct ast_ari_bridges_get_bridge_vars_args *args,
+	struct ast_ari_response *response)
+{
+	int res;
+	RAII_VAR(struct ast_json *, json, ast_json_object_create(), ast_json_unref);
+	RAII_VAR(struct ast_json *, inner_json, ast_json_object_create(), ast_json_unref);
+	RAII_VAR(struct ast_str *, value, ast_str_create(32), ast_free);
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+
+	ast_assert(response != NULL);
+
+	if (!json || !inner_json || !value) {
+		ast_ari_response_alloc_failed(response);
+		return;
+	}
+
+	if (args->variables_count == 0) {
+		ast_ari_response_error(
+			response, 400, "Bad Request",
+			"At least one variable name is required");
+		return;
+	}
+
+	if (ast_strlen_zero(args->bridge_id)) {
+		ast_ari_response_error(
+			response, 400, "Bad Request",
+			"Bridge ID is required");
+		return;
+	}
+
+	bridge = stasis_app_bridge_find_by_id(args->bridge_id);
+	if (!bridge) {
+		ast_ari_response_error(
+			response, 404, "Bridge Not Found",
+			"Provided bridge was not found");
+		return;
+	}
+
+	for (int i = 0; i < args->variables_count; i++) {
+		struct ast_json *json_str;
+		char buf[strlen(args->variables[i]) + 1];
+		char *variable;
+		const char *var_value;
+
+		strcpy(buf, args->variables[i]);
+		variable = ast_strip(buf);
+		if (ast_strlen_zero(variable)) {
+			ast_ari_response_error(
+				response, 400, "Bad Request",
+				"Variable names are required");
+			return;
+		}
+
+		if (variable[strlen(variable) - 1] == ')') {
+			if (ast_func_read2(NULL, variable, &value, 0)) {
+				ast_ari_response_error(
+					response, 500, "Error With Function",
+					"Unable to read provided function");
+				return;
+			}
+		} else {
+			ast_bridge_lock(bridge);
+			var_value = ast_bridge_get_variable(bridge, variable);
+			ast_bridge_unlock(bridge);
+			if (!var_value) {
+				ast_ari_response_error(
+					response, 404, "Variable Not Found",
+					"Provided variable was not found");
+				return;
+			}
+			ast_str_set(&value, 0, "%s", var_value);
+		}
+
+		json_str = ast_json_string_create(ast_str_buffer(value));
+		if (!json_str) {
+			ast_ari_response_alloc_failed(response);
+			return;
+		}
+
+		res = ast_json_object_set(inner_json, variable, json_str);
+		if (res) {
+			ast_ari_response_alloc_failed(response);
+			ast_json_unref(json_str);
+			return;
+		}
+	}
+
+	res = ast_json_object_set(json, "variables", ast_json_ref(inner_json));
+	if (res) {
+		ast_ari_response_alloc_failed(response);
+		return;
+	}
+
+	ast_ari_response_ok(response, ast_json_ref(json));
+}
+
+void ast_ari_bridges_set_bridge_vars(struct ast_variable *headers,
+	struct ast_ari_bridges_set_bridge_vars_args *args,
+	struct ast_ari_response *response)
+{
+	struct ast_json *json_variables;
+	struct ast_variable *var;
+	RAII_VAR(struct ast_variable *, variables, NULL, ast_variables_destroy);
+	RAII_VAR(struct ast_variable *, report_event_variables, NULL, ast_variables_destroy);
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+
+	ast_assert(response != NULL);
+
+	if (!args->variables) {
+		ast_ari_response_error(
+			response, 400, "Bad Request",
+			"The 'variables' field is required");
+		return;
+	}
+
+	bridge = stasis_app_bridge_find_by_id(args->bridge_id);
+	if (!bridge) {
+		ast_ari_response_error(
+			response, 404, "Bridge Not Found",
+			"Provided bridge was not found");
+		return;
+	}
+
+	json_variables = ast_json_object_get(args->variables, "variables");
+	if (!json_variables || ast_json_typeof(json_variables) != AST_JSON_OBJECT) {
+		ast_ari_response_error(
+			response, 400, "Bad Request",
+			"The 'variables' field must be a JSON object");
+		return;
+	}
+
+	if (json_to_ast_variables(response, json_variables, &variables,
+		&report_event_variables)) {
+		return;
+	}
+
+	for (var = variables; var; var = var->next) {
+		int report_events = 0;
+		struct ast_variable *report_var;
+		char buf[strlen(var->name) + 1];
+		char *variable;
+		strcpy(buf, var->name);
+		/* Strip whitespace from the variable name */
+		variable = ast_strip(buf);
+
+		/* See if the variable is in the report event list */
+		for (report_var = report_event_variables; report_var;
+			report_var = report_var->next) {
+			if (!strcmp(report_var->name, var->name)) {
+				report_events = 1;
+				break;
+			}
+		}
+
+		if (stasis_app_bridge_set_var_reportable(args->bridge_id, variable, var->value,
+			report_events)) {
+			ast_ari_response_error(
+				response, 400, "Bad Request",
+				"Failed to execute function");
+			return;
+		}
+	}
+
+	ast_ari_response_no_content(response);
+}
diff --git a/res/ari/resource_bridges.h b/res/ari/resource_bridges.h
index 068ac28d60..72ec5ce4e5 100644
--- a/res/ari/resource_bridges.h
+++ b/res/ari/resource_bridges.h
@@ -58,6 +58,8 @@ struct ast_ari_bridges_create_args {
 	const char *bridge_id;
 	/*! Name to give to the bridge being created. */
 	const char *name;
+	/*! The "variables" key in the body object holds variable key/value pairs to set on the bridge on creation. Each variable is an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in bridge events (defaults to false).   Ex. { "name": "SupportBridge", "variables": {  "Bridge_State": { "value": "WaitingForAgent", "report_events": true } } } */
+	struct ast_json *variables;
 };
 /*!
  * \brief Body parsing function for /bridges.
@@ -88,6 +90,8 @@ struct ast_ari_bridges_create_with_id_args {
 	const char *bridge_id;
 	/*! Set the name of the bridge. */
 	const char *name;
+	/*! The "variables" key in the body object holds variable key/value pairs to set on the bridge on creation. Each variable is an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in bridge events (defaults to false).   Ex. { "name": "SupportBridge", "variables": {  "Bridge_State": { "value": "WaitingForAgent", "report_events": true } } } */
+	struct ast_json *variables;
 };
 /*!
  * \brief Body parsing function for /bridges/{bridgeId}.
@@ -138,6 +142,118 @@ struct ast_ari_bridges_destroy_args {
  * \param[out] response HTTP response
  */
 void ast_ari_bridges_destroy(struct ast_variable *headers, struct ast_ari_bridges_destroy_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_bridges_get_bridge_var() */
+struct ast_ari_bridges_get_bridge_var_args {
+	/*! Bridge's id */
+	const char *bridge_id;
+	/*! The bridge variable or function to get */
+	const char *variable;
+};
+/*!
+ * \brief Body parsing function for /bridges/{bridgeId}/variable.
+ * \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_bridges_get_bridge_var_parse_body(
+	struct ast_json *body,
+	struct ast_ari_bridges_get_bridge_var_args *args);
+
+/*!
+ * \brief Get the value of a bridge variable or function.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_bridges_get_bridge_var(struct ast_variable *headers, struct ast_ari_bridges_get_bridge_var_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_bridges_set_bridge_var() */
+struct ast_ari_bridges_set_bridge_var_args {
+	/*! Bridge's id */
+	const char *bridge_id;
+	/*! The bridge variable or function to set */
+	const char *variable;
+	/*! The value to set the variable to */
+	const char *value;
+	/*! Whether this variable should be included in bridge events. Defaults to false. */
+	int report_events;
+};
+/*!
+ * \brief Body parsing function for /bridges/{bridgeId}/variable.
+ * \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_bridges_set_bridge_var_parse_body(
+	struct ast_json *body,
+	struct ast_ari_bridges_set_bridge_var_args *args);
+
+/*!
+ * \brief Set the value of a bridge variable or function.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_bridges_set_bridge_var(struct ast_variable *headers, struct ast_ari_bridges_set_bridge_var_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_bridges_get_bridge_vars() */
+struct ast_ari_bridges_get_bridge_vars_args {
+	/*! Bridge's id */
+	const char *bridge_id;
+	/*! Array of The bridge variables or functions to get */
+	const char **variables;
+	/*! Length of variables array. */
+	size_t variables_count;
+	/*! Parsing context for variables. */
+	char *variables_parse;
+};
+/*!
+ * \brief Body parsing function for /bridges/{bridgeId}/variables.
+ * \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_bridges_get_bridge_vars_parse_body(
+	struct ast_json *body,
+	struct ast_ari_bridges_get_bridge_vars_args *args);
+
+/*!
+ * \brief Get the value of multiple bridge variables or functions.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_bridges_get_bridge_vars(struct ast_variable *headers, struct ast_ari_bridges_get_bridge_vars_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_bridges_set_bridge_vars() */
+struct ast_ari_bridges_set_bridge_vars_args {
+	/*! Bridge's id */
+	const char *bridge_id;
+	/*! The "variables" key in the body object holds variable key/value pairs to set on the bridge. Each variable value may be either a string or an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in bridge events (defaults to false). Ex. { "variables": { "Bridge_State": "WaitingForAgent", "Support_Level": { "value": "Premium", "report_events": true } } } */
+	struct ast_json *variables;
+};
+/*!
+ * \brief Body parsing function for /bridges/{bridgeId}/variables.
+ * \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_bridges_set_bridge_vars_parse_body(
+	struct ast_json *body,
+	struct ast_ari_bridges_set_bridge_vars_args *args);
+
+/*!
+ * \brief Set the values of multiple bridge variables or functions.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_bridges_set_bridge_vars(struct ast_variable *headers, struct ast_ari_bridges_set_bridge_vars_args *args, struct ast_ari_response *response);
 /*! Argument struct for ast_ari_bridges_add_channel() */
 struct ast_ari_bridges_add_channel_args {
 	/*! Bridge's id */
diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c
index ac4a33f092..c9d4a417fc 100644
--- a/res/ari/resource_channels.c
+++ b/res/ari/resource_channels.c
@@ -1122,6 +1122,7 @@ static struct ast_channel *ari_channels_handle_originate_with_id(const char *arg
 	const char *args_caller_id,
 	int args_timeout,
 	struct ast_variable *variables,
+	struct ast_variable *report_event_variables,
 	const char *args_channel_id,
 	const char *args_other_channel_id,
 	const char *args_originator,
@@ -1390,6 +1391,18 @@ static struct ast_channel *ari_channels_handle_originate_with_id(const char *arg
 		}
 	}

+	if (report_event_variables) {
+		struct ast_variable *var;
+
+		for (var = report_event_variables; var; var = var->next) {
+			if (ast_channel_set_ari_var_reportable(chan, var->name, 1)) {
+				ast_ari_response_alloc_failed(response);
+				ast_dial_destroy(dial);
+				ast_free(origination);
+				return NULL;
+			}
+		}
+	}
 	snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan));

 	/* Before starting the async dial bump the ref in case the dial quickly goes away and takes
@@ -1420,24 +1433,94 @@ static struct ast_channel *ari_channels_handle_originate_with_id(const char *arg
  * \retval 0 on success.
  * \retval -1 on error.
  */
-static int json_to_ast_variables(struct ast_ari_response *response, struct ast_json *json_variables, struct ast_variable **variables)
+static int json_to_ast_variables(struct ast_ari_response *response, struct ast_json *json_variables,
+	struct ast_variable **variables, struct ast_variable **report_event_variables)
 {
-	enum ast_json_to_ast_vars_code res;
+	struct ast_json_iter *it_json_var;
+	struct ast_variable *var_tail = NULL;
+	struct ast_variable *report_var_tail = NULL;

-	res = ast_json_to_ast_variables(json_variables, variables);
-	switch (res) {
-	case AST_JSON_TO_AST_VARS_CODE_SUCCESS:
-		return 0;
-	case AST_JSON_TO_AST_VARS_CODE_INVALID_TYPE:
-		ast_ari_response_error(response, 400, "Bad Request",
-			"Only string values in the 'variables' object allowed");
-		break;
-	case AST_JSON_TO_AST_VARS_CODE_OOM:
-		ast_ari_response_alloc_failed(response);
-		break;
+	*variables = NULL;
+	*report_event_variables = NULL;
+
+	for (it_json_var = ast_json_object_iter(json_variables); it_json_var;
+		it_json_var = ast_json_object_iter_next(json_variables, it_json_var)) {
+		struct ast_variable *new_var;
+		const char *key = ast_json_object_iter_key(it_json_var);
+		const char *value = NULL;
+		struct ast_json *json_value = ast_json_object_iter_value(it_json_var);
+		int report_events = 0;
+
+		if (ast_strlen_zero(key)) {
+			continue;
+		}
+
+		if (ast_json_typeof(json_value) == AST_JSON_STRING) {
+			value = ast_json_string_get(json_value);
+		} else if (ast_json_typeof(json_value) == AST_JSON_OBJECT) {
+			struct ast_json *value_field = ast_json_object_get(json_value, "value");
+			struct ast_json *report_field = ast_json_object_get(json_value, "report_events");
+			ast_log(LOG_DEBUG, "Processing variable '%s' with report_events: %s\n", key,
+				report_field ? (ast_json_is_true(report_field) ? "true" : "false") : "not set");
+
+			if (!value_field || ast_json_typeof(value_field) != AST_JSON_STRING) {
+				ast_ari_response_error(response, 400, "Bad Request",
+					"Each object value in 'variables' must include string field 'value'");
+				goto error;
+			}
+
+			value = ast_json_string_get(value_field);
+			ast_log(LOG_DEBUG, "Variable '%s' has value '%s'\n", key, value);
+
+			if (report_field) {
+				enum ast_json_type report_type = ast_json_typeof(report_field);
+
+				if (report_type != AST_JSON_TRUE && report_type != AST_JSON_FALSE) {
+					ast_ari_response_error(response, 400, "Bad Request",
+						"Field 'report_events' in 'variables' entries must be boolean");
+					goto error;
+				}
+
+				report_events = ast_json_is_true(report_field);
+			}
+		} else {
+			ast_ari_response_error(response, 400, "Bad Request",
+				"Each value in 'variables' must be a string or an object with 'value' and optional 'report_events'");
+			goto error;
+		}
+
+		if (!value) {
+			continue;
+		}
+
+		new_var = ast_variable_new(key, value, "");
+		if (!new_var) {
+			ast_ari_response_alloc_failed(response);
+			goto error;
+		}
+
+		var_tail = ast_variable_list_append_hint(variables, var_tail, new_var);
+
+		if (report_events) {
+			struct ast_variable *report_var = ast_variable_new(key, "1", "");
+
+			if (!report_var) {
+				ast_ari_response_alloc_failed(response);
+				goto error;
+			}
+
+			report_var_tail = ast_variable_list_append_hint(report_event_variables,
+				report_var_tail, report_var);
+		}
 	}
-	ast_log(AST_LOG_ERROR, "Unable to convert 'variables' in JSON body to channel variables\n");

+	return 0;
+
+error:
+	ast_variables_destroy(*variables);
+	*variables = NULL;
+	ast_variables_destroy(*report_event_variables);
+	*report_event_variables = NULL;
 	return -1;
 }

@@ -1446,6 +1529,7 @@ void ast_ari_channels_originate_with_id(struct ast_variable *headers,
 	struct ast_ari_response *response)
 {
 	struct ast_variable *variables = NULL;
+	struct ast_variable *report_event_variables = NULL;
 	struct ast_channel *chan;

 	/* Parse any query parameters out of the body parameter */
@@ -1455,7 +1539,8 @@ void ast_ari_channels_originate_with_id(struct ast_variable *headers,
 		ast_ari_channels_originate_with_id_parse_body(args->variables, args);
 		json_variables = ast_json_object_get(args->variables, "variables");
 		if (json_variables
-			&& json_to_ast_variables(response, json_variables, &variables)) {
+			&& json_to_ast_variables(response, json_variables, &variables,
+				&report_event_variables)) {
 			return;
 		}
 	}
@@ -1471,12 +1556,14 @@ void ast_ari_channels_originate_with_id(struct ast_variable *headers,
 		args->caller_id,
 		args->timeout,
 		variables,
+		report_event_variables,
 		args->channel_id,
 		args->other_channel_id,
 		args->originator,
 		args->formats,
 		response);
 	ast_channel_cleanup(chan);
+	ast_variables_destroy(report_event_variables);
 	ast_variables_destroy(variables);
 }

@@ -1485,6 +1572,7 @@ void ast_ari_channels_originate(struct ast_variable *headers,
 	struct ast_ari_response *response)
 {
 	struct ast_variable *variables = NULL;
+	struct ast_variable *report_event_variables = NULL;
 	struct ast_channel *chan;

 	/* Parse any query parameters out of the body parameter */
@@ -1494,7 +1582,8 @@ void ast_ari_channels_originate(struct ast_variable *headers,
 		ast_ari_channels_originate_parse_body(args->variables, args);
 		json_variables = ast_json_object_get(args->variables, "variables");
 		if (json_variables
-			&& json_to_ast_variables(response, json_variables, &variables)) {
+			&& json_to_ast_variables(response, json_variables, &variables,
+				&report_event_variables)) {
 			return;
 		}
 	}
@@ -1510,12 +1599,14 @@ void ast_ari_channels_originate(struct ast_variable *headers,
 		args->caller_id,
 		args->timeout,
 		variables,
+		report_event_variables,
 		args->channel_id,
 		args->other_channel_id,
 		args->originator,
 		args->formats,
 		response);
 	ast_channel_cleanup(chan);
+	ast_variables_destroy(report_event_variables);
 	ast_variables_destroy(variables);
 }

@@ -1701,7 +1792,8 @@ void ast_ari_channels_set_channel_var(struct ast_variable *headers,
 		return;
 	}

-	if (stasis_app_control_set_channel_var(control, args->variable, args->value)) {
+	if (stasis_app_control_set_channel_var_reportable(control, args->variable, args->value,
+		args->report_events)) {
 		ast_ari_response_error(
 			response, 400, "Bad Request",
 			"Failed to execute function");
@@ -1716,9 +1808,9 @@ void ast_ari_channels_set_channel_vars(struct ast_variable *headers,
 	struct ast_ari_response *response)
 {
 	struct ast_json *json_variables;
-	struct ast_json_iter *it_json_var;
-	struct ast_variable *var = NULL;
+	struct ast_variable *var;
 	RAII_VAR(struct ast_variable *, variables, NULL, ast_variables_destroy);
+	RAII_VAR(struct ast_variable *, report_event_variables, NULL, ast_variables_destroy);
 	RAII_VAR(struct ast_channel *, channel, NULL, ast_channel_cleanup);
 	RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);

@@ -1746,57 +1838,38 @@ void ast_ari_channels_set_channel_vars(struct ast_variable *headers,
 	}

 	json_variables = ast_json_object_get(args->variables, "variables");
-	for (it_json_var = ast_json_object_iter(json_variables); it_json_var;
-		it_json_var = ast_json_object_iter_next(json_variables, it_json_var)) {
-		const char *key = ast_json_object_iter_key(it_json_var);
-		char buf[strlen(key) + 1];
-		char *stripped_key;
-		struct ast_json *json_value;
-		const char *value;
-		struct ast_variable *new_var;
-
-		strcpy(buf, key);
-		stripped_key = ast_strip(buf);
-		if (ast_strlen_zero(stripped_key)) {
-			ast_ari_response_error(
-				response, 400, "Bad Request",
-				"Variable names are required");
-			return;
-		}
+	if (!json_variables || ast_json_typeof(json_variables) != AST_JSON_OBJECT) {
+		ast_ari_response_error(
+			response, 400, "Bad Request",
+			"The 'variables' field must be a JSON object");
+		return;
+	}

-		json_value = ast_json_object_iter_value(it_json_var);
-		if (ast_json_typeof(json_value) != AST_JSON_STRING) {
-			ast_ari_response_error(
-				response, 400, "Bad Request",
-				"Variable values must be strings");
-			return;
-		}
+	if (json_to_ast_variables(response, json_variables, &variables,
+		&report_event_variables)) {
+		return;
+	}

-		value = ast_json_string_get(json_value);
-		if (!value) {
-			ast_ari_response_error(
-				response, 500, "Internal Server Error",
-				"Could not get string value from JSON string");
-			return;
-		}
+	for (var = variables; var; var = var->next) {
+		int report_events = 0;
+		struct ast_variable *report_var;
+		char buf[strlen(var->name) + 1];
+		char *variable;
+		strcpy(buf, var->name);
+		/* Strip whitespace from the variable name */
+		variable = ast_strip(buf);

-		new_var = ast_variable_new(stripped_key, value, "");
-		if (!new_var) {
-			ast_ari_response_error(
-				response, 500, "Internal Server Error",
-				"Could not create internal variable");
-			return;
+		/* See if the variable is in the report event list */
+		for (report_var = report_event_variables; report_var;
+			report_var = report_var->next) {
+			if (!strcmp(report_var->name, var->name)) {
+				report_events = 1;
+				break;
+			}
 		}

-		/* Append to the tail */
-		var = ast_variable_list_append_hint(&variables, var, new_var);
-	}
-
-	/* We loop twice to preserve variable state if something goes wrong. If something
-	 * goes wrong in this loop, something went VERY wrong.
-	 */
-	for (var = variables; var; var = var->next) {
-		if (stasis_app_control_set_channel_var(control, var->name, var->value)) {
+		if (stasis_app_control_set_channel_var_reportable(control, variable,
+			var->value, report_events)) {
 			ast_ari_response_error(
 				response, 400, "Bad Request",
 				"Failed to execute function");
@@ -2018,6 +2091,7 @@ void ast_ari_channels_create(struct ast_variable *headers,
 	struct ast_ari_response *response)
 {
 	RAII_VAR(struct ast_variable *, variables, NULL, ast_variables_destroy);
+	RAII_VAR(struct ast_variable *, report_event_variables, NULL, ast_variables_destroy);
 	struct ast_assigned_ids assignedids;
 	struct ari_channel_thread_data *chan_data;
 	struct ast_channel_snapshot *snapshot;
@@ -2035,8 +2109,9 @@ void ast_ari_channels_create(struct ast_variable *headers,

 		ast_ari_channels_create_parse_body(args->variables, args);
 		json_variables = ast_json_object_get(args->variables, "variables");
-		if (json_variables
-			&& json_to_ast_variables(response, json_variables, &variables)) {
+		if (json_variables &&
+			json_to_ast_variables(response, json_variables, &variables, &report_event_variables)) {
+			ast_log(LOG_ERROR, "Failed to parse variables from request body for channel creation\n");
 			return;
 		}
 	}
@@ -2156,6 +2231,18 @@ void ast_ari_channels_create(struct ast_variable *headers,
 	if (variables) {
 		ast_set_variables(chan_data->chan, variables);
 	}
+	if (report_event_variables) {
+		struct ast_variable *var;
+
+		for (var = report_event_variables; var; var = var->next) {
+			if (ast_channel_set_ari_var_reportable(chan_data->chan, var->name, 1)) {
+				ast_ari_response_alloc_failed(response);
+				ast_channel_cleanup(originator);
+				chan_data_destroy(chan_data);
+				return;
+			}
+		}
+	}

 	ast_channel_cleanup(originator);

@@ -2326,6 +2413,7 @@ void ast_ari_channels_rtpstatistics(struct ast_variable *headers,

 static int external_media_rtp_udp(struct ast_ari_channels_external_media_args *args,
 	struct ast_variable *variables,
+	struct ast_variable *report_event_variables,
 	struct ast_ari_response *response)
 {
 	char *endpoint;
@@ -2349,6 +2437,7 @@ static int external_media_rtp_udp(struct ast_ari_channels_external_media_args *a
 		NULL,
 		0,
 		variables,
+		report_event_variables,
 		args->channel_id,
 		NULL,
 		NULL,
@@ -2373,6 +2462,7 @@ static int external_media_rtp_udp(struct ast_ari_channels_external_media_args *a

 static int external_media_audiosocket_tcp(struct ast_ari_channels_external_media_args *args,
 	struct ast_variable *variables,
+	struct ast_variable *report_event_variables,
 	struct ast_ari_response *response)
 {
 	char *endpoint;
@@ -2395,6 +2485,7 @@ static int external_media_audiosocket_tcp(struct ast_ari_channels_external_media
 		NULL,
 		0,
 		variables,
+		report_event_variables,
 		args->channel_id,
 		NULL,
 		NULL,
@@ -2419,6 +2510,7 @@ static int external_media_audiosocket_tcp(struct ast_ari_channels_external_media

 static int external_media_websocket(struct ast_ari_channels_external_media_args *args,
 	struct ast_variable *variables,
+	struct ast_variable *report_event_variables,
 	struct ast_ari_response *response)
 {
 	char *endpoint;
@@ -2453,6 +2545,7 @@ static int external_media_websocket(struct ast_ari_channels_external_media_args
 		NULL,
 		0,
 		variables,
+		report_event_variables,
 		args->channel_id,
 		NULL,
 		NULL,
@@ -2482,6 +2575,7 @@ void ast_ari_channels_external_media(struct ast_variable *headers,
 	struct ast_ari_channels_external_media_args *args, struct ast_ari_response *response)
 {
 	RAII_VAR(struct ast_variable *, variables, NULL, ast_variables_destroy);
+	RAII_VAR(struct ast_variable *, report_event_variables, NULL, ast_variables_destroy);
 	char *external_host;
 	char *host = NULL;
 	char *port = NULL;
@@ -2495,7 +2589,8 @@ void ast_ari_channels_external_media(struct ast_variable *headers,
 		ast_ari_channels_external_media_parse_body(args->variables, args);
 		json_variables = ast_json_object_get(args->variables, "variables");
 		if (json_variables
-			&& json_to_ast_variables(response, json_variables, &variables)) {
+			&& json_to_ast_variables(response, json_variables, &variables,
+				&report_event_variables)) {
 			return;
 		}
 	}
@@ -2587,7 +2682,7 @@ void ast_ari_channels_external_media(struct ast_variable *headers,
 	}

 	if (strcasecmp(args->encapsulation, "rtp") == 0 && strcasecmp(args->transport, "udp") == 0) {
-		if (external_media_rtp_udp(args, variables, response)) {
+		if (external_media_rtp_udp(args, variables, report_event_variables, response)) {
 			ast_ari_response_error(
 				response, 500, "Internal Server Error",
 				"An internal error prevented this request from being handled");
@@ -2595,13 +2690,13 @@ void ast_ari_channels_external_media(struct ast_variable *headers,
 	} else if (strcasecmp(args->encapsulation, "audiosocket") == 0 && strcasecmp(args->transport, "tcp") == 0) {
 		if (ast_strlen_zero(args->data)) {
 			ast_ari_response_error(response, 400, "Bad Request", "data can not be empty");
-		} else if (external_media_audiosocket_tcp(args, variables, response)) {
+		} else if (external_media_audiosocket_tcp(args, variables, report_event_variables, response)) {
 			ast_ari_response_error(
 				response, 500, "Internal Server Error",
 				"An internal error prevented this request from being handled");
 		}
 	} else if (strcasecmp(args->encapsulation, "none") == 0 && strcasecmp(args->transport, "websocket") == 0) {
-		if (external_media_websocket(args, variables, response)) {
+		if (external_media_websocket(args, variables, report_event_variables, response)) {
 			ast_ari_response_error(
 				response, 500, "Internal Server Error",
 				"An internal error prevented this request from being handled");
diff --git a/res/ari/resource_channels.h b/res/ari/resource_channels.h
index 5d4bf5833a..2be83a0e7e 100644
--- a/res/ari/resource_channels.h
+++ b/res/ari/resource_channels.h
@@ -70,7 +70,7 @@ struct ast_ari_channels_originate_args {
 	const char *caller_id;
 	/*! Timeout (in seconds) before giving up dialing, or -1 for no timeout. */
 	int timeout;
-	/*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice" } } */
+	/*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice", "Call_State": { "value": "WaitingForAgent", "report_events": true } } } */
 	struct ast_json *variables;
 	/*! The unique id to assign the channel on creation. */
 	const char *channel_id;
@@ -118,7 +118,7 @@ struct ast_ari_channels_create_args {
 	const char *originator;
 	/*! The format name capability list to use if originator is not specified. Ex. "ulaw,slin16".  Format names can be found with "core show codecs". */
 	const char *formats;
-	/*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice" } } */
+	/*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice", "Call_State": { "value": "WaitingForAgent", "report_events": true } } } */
 	struct ast_json *variables;
 };
 /*!
@@ -175,7 +175,7 @@ struct ast_ari_channels_originate_with_id_args {
 	const char *caller_id;
 	/*! Timeout (in seconds) before giving up dialing, or -1 for no timeout. */
 	int timeout;
-	/*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice" } } */
+	/*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice", "Call_State": { "value": "WaitingForAgent", "report_events": true } } } */
 	struct ast_json *variables;
 	/*! The unique id to assign the second channel when using local channels. */
 	const char *other_channel_id;
@@ -706,6 +706,8 @@ struct ast_ari_channels_set_channel_var_args {
 	const char *variable;
 	/*! The value to set the variable to */
 	const char *value;
+	/*! Whether this variable should be included in channel events. Defaults to false. */
+	int report_events;
 };
 /*!
  * \brief Body parsing function for /channels/{channelId}/variable.
@@ -760,7 +762,7 @@ void ast_ari_channels_get_channel_vars(struct ast_variable *headers, struct ast_
 struct ast_ari_channels_set_channel_vars_args {
 	/*! Channel's id */
 	const char *channel_id;
-	/*! The "variables" key in the body object holds variable key/value pairs to set on the channel. Ex. { "variables": { "CALLERID(name)": "Alice" } } */
+	/*! The "variables" key in the body object holds variable key/value pairs to set on the channel. Each variable value may be either a string or an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in channel events (defaults to false). Ex. { "variables": { "CALLERID(name)": "Alice", "Call_State": { "value": "WaitingForAgent", "report_events": true } } } */
 	struct ast_json *variables;
 };
 /*!
@@ -901,7 +903,7 @@ struct ast_ari_channels_external_media_args {
 	const char *channel_id;
 	/*! Stasis Application to place channel into */
 	const char *app;
-	/*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice" } } */
+	/*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing "value" (string) and optional "report_events" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice", "Call_State": { "value": "WaitingForAgent", "report_events": true } } } */
 	struct ast_json *variables;
 	/*! Hostname/ip:port or websocket_client connection ID of external host.  May be empty for a websocket server connection. */
 	const char *external_host;
diff --git a/res/res_ari_bridges.c b/res/res_ari_bridges.c
index 465f6e947a..ac236957eb 100644
--- a/res/res_ari_bridges.c
+++ b/res/res_ari_bridges.c
@@ -158,10 +158,7 @@ static void ast_ari_bridges_create_cb(
 		} else
 		{}
 	}
-	if (ast_ari_bridges_create_parse_body(body, &args)) {
-		ast_ari_response_alloc_failed(response);
-		goto fin;
-	}
+	args.variables = body;
 	ast_ari_bridges_create(headers, &args, response);
 #if defined(AST_DEVMODE)
 	code = response->response_code;
@@ -248,10 +245,7 @@ static void ast_ari_bridges_create_with_id_cb(
 		} else
 		{}
 	}
-	if (ast_ari_bridges_create_with_id_parse_body(body, &args)) {
-		ast_ari_response_alloc_failed(response);
-		goto fin;
-	}
+	args.variables = body;
 	ast_ari_bridges_create_with_id(headers, &args, response);
 #if defined(AST_DEVMODE)
 	code = response->response_code;
@@ -402,6 +396,413 @@ static void ast_ari_bridges_destroy_cb(
 	}
 #endif /* AST_DEVMODE */

+fin: __attribute__((unused))
+	return;
+}
+int ast_ari_bridges_get_bridge_var_parse_body(
+	struct ast_json *body,
+	struct ast_ari_bridges_get_bridge_var_args *args)
+{
+	struct ast_json *field;
+	/* Parse query parameters out of it */
+	field = ast_json_object_get(body, "variable");
+	if (field) {
+		args->variable = ast_json_string_get(field);
+	}
+	return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}/variable.
+ * \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_bridges_get_bridge_var_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_bridges_get_bridge_var_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, "variable") == 0) {
+			args.variable = (i->value);
+		} else
+		{}
+	}
+	for (i = path_vars; i; i = i->next) {
+		if (strcmp(i->name, "bridgeId") == 0) {
+			args.bridge_id = (i->value);
+		} else
+		{}
+	}
+	if (ast_ari_bridges_get_bridge_var_parse_body(body, &args)) {
+		ast_ari_response_alloc_failed(response);
+		goto fin;
+	}
+	ast_ari_bridges_get_bridge_var(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: /* Missing variable parameter. */
+	case 404: /* Bridge or variable not found */
+	case 409: /* Bridge not in a Stasis application */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ast_ari_validate_variable(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/variable\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/variable\n");
+		ast_ari_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+	return;
+}
+int ast_ari_bridges_set_bridge_var_parse_body(
+	struct ast_json *body,
+	struct ast_ari_bridges_set_bridge_var_args *args)
+{
+	struct ast_json *field;
+	/* Parse query parameters out of it */
+	field = ast_json_object_get(body, "variable");
+	if (field) {
+		args->variable = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "value");
+	if (field) {
+		args->value = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "report_events");
+	if (field) {
+		args->report_events = ast_json_is_true(field);
+	}
+	return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}/variable.
+ * \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_bridges_set_bridge_var_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_bridges_set_bridge_var_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, "variable") == 0) {
+			args.variable = (i->value);
+		} else
+		if (strcmp(i->name, "value") == 0) {
+			args.value = (i->value);
+		} else
+		if (strcmp(i->name, "report_events") == 0) {
+			args.report_events = ast_true(i->value);
+		} else
+		{}
+	}
+	for (i = path_vars; i; i = i->next) {
+		if (strcmp(i->name, "bridgeId") == 0) {
+			args.bridge_id = (i->value);
+		} else
+		{}
+	}
+	if (ast_ari_bridges_set_bridge_var_parse_body(body, &args)) {
+		ast_ari_response_alloc_failed(response);
+		goto fin;
+	}
+	ast_ari_bridges_set_bridge_var(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: /* Missing variable parameter. */
+	case 404: /* Bridge not found */
+	case 409: /* Bridge not in a Stasis application */
+		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 /bridges/{bridgeId}/variable\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/variable\n");
+		ast_ari_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+	return;
+}
+int ast_ari_bridges_get_bridge_vars_parse_body(
+	struct ast_json *body,
+	struct ast_ari_bridges_get_bridge_vars_args *args)
+{
+	struct ast_json *field;
+	/* Parse query parameters out of it */
+	field = ast_json_object_get(body, "variables");
+	if (field) {
+		/* If they were silly enough to both pass in a query param and a
+		 * JSON body, free up the query value.
+		 */
+		ast_free(args->variables);
+		if (ast_json_typeof(field) == AST_JSON_ARRAY) {
+			/* Multiple param passed as array */
+			size_t i;
+			args->variables_count = ast_json_array_size(field);
+			args->variables = ast_malloc(sizeof(*args->variables) * args->variables_count);
+
+			if (!args->variables) {
+				return -1;
+			}
+
+			for (i = 0; i < args->variables_count; ++i) {
+				args->variables[i] = ast_json_string_get(ast_json_array_get(field, i));
+			}
+		} else {
+			/* Multiple param passed as single value */
+			args->variables_count = 1;
+			args->variables = ast_malloc(sizeof(*args->variables) * args->variables_count);
+			if (!args->variables) {
+				return -1;
+			}
+			args->variables[0] = ast_json_string_get(field);
+		}
+	}
+	return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}/variables.
+ * \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_bridges_get_bridge_vars_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_bridges_get_bridge_vars_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, "variables") == 0) {
+			/* Parse comma separated list */
+			char *vals[MAX_VALS];
+			size_t j;
+
+			args.variables_parse = ast_strdup(i->value);
+			if (!args.variables_parse) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			if (strlen(args.variables_parse) == 0) {
+				/* ast_app_separate_args can't handle "" */
+				args.variables_count = 1;
+				vals[0] = args.variables_parse;
+			} else {
+				args.variables_count = ast_app_separate_args(
+					args.variables_parse, ',', vals,
+					ARRAY_LEN(vals));
+			}
+
+			if (args.variables_count == 0) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			if (args.variables_count >= MAX_VALS) {
+				ast_ari_response_error(response, 400,
+					"Bad Request",
+					"Too many values for variables");
+				goto fin;
+			}
+
+			args.variables = ast_malloc(sizeof(*args.variables) * args.variables_count);
+			if (!args.variables) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			for (j = 0; j < args.variables_count; ++j) {
+				args.variables[j] = (vals[j]);
+			}
+		} else
+		{}
+	}
+	for (i = path_vars; i; i = i->next) {
+		if (strcmp(i->name, "bridgeId") == 0) {
+			args.bridge_id = (i->value);
+		} else
+		{}
+	}
+	if (ast_ari_bridges_get_bridge_vars_parse_body(body, &args)) {
+		ast_ari_response_alloc_failed(response);
+		goto fin;
+	}
+	ast_ari_bridges_get_bridge_vars(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: /* Missing variables parameter. */
+	case 404: /* Bridge or variable not found */
+	case 409: /* Bridge not in a Stasis application */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ast_ari_validate_variables(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/variables\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/variables\n");
+		ast_ari_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+	ast_free(args.variables_parse);
+	ast_free(args.variables);
+	return;
+}
+int ast_ari_bridges_set_bridge_vars_parse_body(
+	struct ast_json *body,
+	struct ast_ari_bridges_set_bridge_vars_args *args)
+{
+	/* Parse query parameters out of it */
+	return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}/variables.
+ * \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_bridges_set_bridge_vars_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_bridges_set_bridge_vars_args args = {};
+	struct ast_variable *i;
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
+	for (i = path_vars; i; i = i->next) {
+		if (strcmp(i->name, "bridgeId") == 0) {
+			args.bridge_id = (i->value);
+		} else
+		{}
+	}
+	args.variables = body;
+	ast_ari_bridges_set_bridge_vars(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: /* Missing variables parameter. */
+	case 404: /* Bridge not found */
+	case 409: /* Bridge not in a Stasis application */
+		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 /bridges/{bridgeId}/variables\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/variables\n");
+		ast_ari_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+
 fin: __attribute__((unused))
 	return;
 }
@@ -1515,6 +1916,26 @@ fin: __attribute__((unused))
 	return;
 }

+/*! \brief REST handler for /api-docs/bridges.json */
+static struct stasis_rest_handlers bridges_bridgeId_variable = {
+	.path_segment = "variable",
+	.callbacks = {
+		[AST_HTTP_GET] = ast_ari_bridges_get_bridge_var_cb,
+		[AST_HTTP_POST] = ast_ari_bridges_set_bridge_var_cb,
+	},
+	.num_children = 0,
+	.children = {  }
+};
+/*! \brief REST handler for /api-docs/bridges.json */
+static struct stasis_rest_handlers bridges_bridgeId_variables = {
+	.path_segment = "variables",
+	.callbacks = {
+		[AST_HTTP_GET] = ast_ari_bridges_get_bridge_vars_cb,
+		[AST_HTTP_POST] = ast_ari_bridges_set_bridge_vars_cb,
+	},
+	.num_children = 0,
+	.children = {  }
+};
 /*! \brief REST handler for /api-docs/bridges.json */
 static struct stasis_rest_handlers bridges_bridgeId_addChannel = {
 	.path_segment = "addChannel",
@@ -1599,8 +2020,8 @@ static struct stasis_rest_handlers bridges_bridgeId = {
 		[AST_HTTP_GET] = ast_ari_bridges_get_cb,
 		[AST_HTTP_DELETE] = ast_ari_bridges_destroy_cb,
 	},
-	.num_children = 6,
-	.children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_videoSource,&bridges_bridgeId_moh,&bridges_bridgeId_play,&bridges_bridgeId_record, }
+	.num_children = 8,
+	.children = { &bridges_bridgeId_variable,&bridges_bridgeId_variables,&bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_videoSource,&bridges_bridgeId_moh,&bridges_bridgeId_play,&bridges_bridgeId_record, }
 };
 /*! \brief REST handler for /api-docs/bridges.json */
 static struct stasis_rest_handlers bridges = {
diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c
index 3d109e101c..d07631df46 100644
--- a/res/res_ari_channels.c
+++ b/res/res_ari_channels.c
@@ -2484,6 +2484,10 @@ int ast_ari_channels_set_channel_var_parse_body(
 	if (field) {
 		args->value = ast_json_string_get(field);
 	}
+	field = ast_json_object_get(body, "report_events");
+	if (field) {
+		args->report_events = ast_json_is_true(field);
+	}
 	return 0;
 }

@@ -2515,6 +2519,9 @@ static void ast_ari_channels_set_channel_var_cb(
 		if (strcmp(i->name, "value") == 0) {
 			args.value = (i->value);
 		} else
+		if (strcmp(i->name, "report_events") == 0) {
+			args.report_events = ast_true(i->value);
+		} else
 		{}
 	}
 	for (i = path_vars; i; i = i->next) {
diff --git a/res/res_stasis.c b/res/res_stasis.c
index 6912f3a9c5..634a2e4649 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -920,6 +920,26 @@ void stasis_app_bridge_destroy(const char *bridge_id)
 	ast_bridge_destroy(bridge, 0);
 }

+int stasis_app_bridge_set_var_reportable(const char *bridge_id, const char *variable,
+	const char *value, int report_events)
+{
+	RAII_VAR(struct ast_bridge *, bridge, stasis_app_bridge_find_by_id(bridge_id), ao2_cleanup);
+
+	if (!bridge) {
+		return -1;
+	}
+
+	ast_bridge_lock(bridge);
+	if (ast_bridge_set_variable(bridge, variable, value, report_events)) {
+		ast_bridge_unlock(bridge);
+		return -1;
+	}
+	ast_bridge_publish_state(bridge);
+	ast_bridge_unlock(bridge);
+
+	return 0;
+}
+
 struct replace_channel_store {
 	struct ast_channel_snapshot *snapshot;
 	char *app;
diff --git a/res/stasis/control.c b/res/stasis/control.c
index 52058ec0c7..ae99f9496e 100644
--- a/res/stasis/control.c
+++ b/res/stasis/control.c
@@ -728,6 +728,8 @@ struct chanvar {
 	char *name;
 	/*! Value of variable to set. If unsetting, this will be NULL */
 	char *value;
+	/*! Whether this variable should be included in channel events */
+	unsigned int report_events;
 };

 static void free_chanvar(void *data)
@@ -744,12 +746,21 @@ static int app_control_set_channel_var(struct stasis_app_control *control,
 {
 	struct chanvar *var = data;

+	if (ast_channel_set_ari_var_reportable(control->channel, var->name, var->report_events)) {
+		return -1;
+	}
+
 	pbx_builtin_setvar_helper(control->channel, var->name, var->value);

 	return 0;
 }

 int stasis_app_control_set_channel_var(struct stasis_app_control *control, const char *variable, const char *value)
+{
+	return stasis_app_control_set_channel_var_reportable(control, variable, value, 0);
+}
+
+int stasis_app_control_set_channel_var_reportable(struct stasis_app_control *control, const char *variable, const char *value, int report_events)
 {
 	struct chanvar *var;

@@ -773,9 +784,9 @@ int stasis_app_control_set_channel_var(struct stasis_app_control *control, const
 		}
 	}

-	stasis_app_send_command_async(control, app_control_set_channel_var, var, free_chanvar);
+	var->report_events = report_events ? 1 : 0;

-	return 0;
+	return stasis_app_send_command(control, app_control_set_channel_var, var, free_chanvar);
 }

 static int app_control_hold(struct stasis_app_control *control,
diff --git a/rest-api/api-docs/bridges.json b/rest-api/api-docs/bridges.json
index ae5ee56fec..c0947cb088 100644
--- a/rest-api/api-docs/bridges.json
+++ b/rest-api/api-docs/bridges.json
@@ -60,6 +60,14 @@
 							"required": false,
 							"allowMultiple": false,
 							"dataType": "string"
+						},
+						{
+							"name": "variables",
+							"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the bridge on creation. Each variable is an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in bridge events (defaults to false).   Ex. { \"name\": \"SupportBridge\", \"variables\": {  \"Bridge_State\": { \"value\": \"WaitingForAgent\", \"report_events\": true } } }",
+							"paramType": "body",
+							"required": false,
+							"dataType": "containers",
+							"allowMultiple": false
 						}
 					],
 					"errorResponses": [
@@ -108,6 +116,14 @@
 							"required": false,
 							"allowMultiple": false,
 							"dataType": "string"
+						},
+						{
+							"name": "variables",
+							"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the bridge on creation. Each variable is an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in bridge events (defaults to false).   Ex. { \"name\": \"SupportBridge\", \"variables\": {  \"Bridge_State\": { \"value\": \"WaitingForAgent\", \"report_events\": true } } }",
+							"paramType": "body",
+							"required": false,
+							"dataType": "containers",
+							"allowMultiple": false
 						}
 					],
 					"errorResponses": [
@@ -170,6 +186,207 @@
 				}
 			]
 		},
+		{
+			"path": "/bridges/{bridgeId}/variable",
+			"description": "Variables on a bridge",
+			"operations": [
+				{
+					"httpMethod": "GET",
+					"since": [
+						"20.20.0",
+						"22.10.0",
+						"23.4.0"
+					],
+					"summary": "Get the value of a bridge variable or function.",
+					"nickname": "getBridgeVar",
+					"responseClass": "Variable",
+					"parameters": [
+						{
+							"name": "bridgeId",
+							"description": "Bridge's id",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "variable",
+							"description": "The bridge variable or function to get",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 400,
+							"reason": "Missing variable parameter."
+						},
+						{
+							"code": 404,
+							"reason": "Bridge or variable not found"
+						},
+						{
+							"code": 409,
+							"reason": "Bridge not in a Stasis application"
+						}
+					]
+				},
+				{
+					"httpMethod": "POST",
+					"since": [
+						"20.20.0",
+						"22.10.0",
+						"23.4.0"
+					],
+					"summary": "Set the value of a bridge variable or function.",
+					"nickname": "setBridgeVar",
+					"responseClass": "void",
+					"parameters": [
+						{
+							"name": "bridgeId",
+							"description": "Bridge's id",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "variable",
+							"description": "The bridge variable or function to set",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "value",
+							"description": "The value to set the variable to",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "report_events",
+							"description": "Whether this variable should be included in bridge events. Defaults to false.",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "boolean",
+							"defaultValue": false
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 400,
+							"reason": "Missing variable parameter."
+						},
+						{
+							"code": 404,
+							"reason": "Bridge not found"
+						},
+						{
+							"code": 409,
+							"reason": "Bridge not in a Stasis application"
+						}
+					]
+				}
+			]
+		},
+		{
+			"path": "/bridges/{bridgeId}/variables",
+			"description": "Multiple variables on a bridge",
+			"operations": [
+				{
+					"httpMethod": "GET",
+					"since": [
+						"20.20.0",
+						"22.10.0",
+						"23.4.0"
+					],
+					"summary": "Get the value of multiple bridge variables or functions.",
+					"nickname": "getBridgeVars",
+					"responseClass": "Variables",
+					"parameters": [
+						{
+							"name": "bridgeId",
+							"description": "Bridge's id",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "variables",
+							"description": "The bridge variables or functions to get",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": true,
+							"dataType": "string"
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 400,
+							"reason": "Missing variables parameter."
+						},
+						{
+							"code": 404,
+							"reason": "Bridge or variable not found"
+						},
+						{
+							"code": 409,
+							"reason": "Bridge not in a Stasis application"
+						}
+					]
+				},
+				{
+					"httpMethod": "POST",
+					"since": [
+						"20.20.0",
+						"22.10.0",
+						"23.4.0"
+					],
+					"summary": "Set the values of multiple bridge variables or functions.",
+					"nickname": "setBridgeVars",
+					"responseClass": "void",
+					"parameters": [
+						{
+							"name": "bridgeId",
+							"description": "Bridge's id",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "variables",
+							"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the bridge. Each variable value may be either a string or an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in bridge events (defaults to false). Ex. { \"variables\": { \"Bridge_State\": \"WaitingForAgent\", \"Support_Level\": { \"value\": \"Premium\", \"report_events\": true } } }",
+							"paramType": "body",
+							"required": true,
+							"dataType": "containers",
+							"allowMultiple": false
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 400,
+							"reason": "Missing variables parameter."
+						},
+						{
+							"code": 404,
+							"reason": "Bridge not found"
+						},
+						{
+							"code": 409,
+							"reason": "Bridge not in a Stasis application"
+						}
+					]
+				}
+			]
+		},
 		{
 			"path": "/bridges/{bridgeId}/addChannel",
 			"description": "Add a channel to a bridge",
@@ -857,6 +1074,11 @@
 					"required": true,
 					"type": "Date",
 					"description": "Timestamp when bridge was created"
+				},
+				"bridgevars": {
+					"required": false,
+					"type": "object",
+					"description": "Bridge variables"
 				}
 			}
 		}
diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json
index 0d7c245c78..f1b711d36a 100644
--- a/rest-api/api-docs/channels.json
+++ b/rest-api/api-docs/channels.json
@@ -114,7 +114,7 @@
 						},
 						{
 							"name": "variables",
-							"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\" } }",
+							"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\", \"Call_State\": { \"value\": \"WaitingForAgent\", \"report_events\": true } } }",
 							"paramType": "body",
 							"required": false,
 							"dataType": "containers",
@@ -237,7 +237,7 @@
 						},
 						{
 							"name": "variables",
-							"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\" } }",
+							"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\", \"Call_State\": { \"value\": \"WaitingForAgent\", \"report_events\": true } } }",
 							"paramType": "body",
 							"required": false,
 							"dataType": "containers",
@@ -375,7 +375,7 @@
 						},
 						{
 							"name": "variables",
-							"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\" } }",
+							"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\", \"Call_State\": { \"value\": \"WaitingForAgent\", \"report_events\": true } } }",
 							"paramType": "body",
 							"required": false,
 							"dataType": "containers",
@@ -1601,6 +1601,15 @@
 							"required": false,
 							"allowMultiple": false,
 							"dataType": "string"
+						},
+						{
+							"name": "report_events",
+							"description": "Whether this variable should be included in channel events. Defaults to false.",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "boolean",
+							"defaultValue": false
 						}
 					],
 					"errorResponses": [
@@ -1688,9 +1697,9 @@
 						},
 						{
 							"name": "variables",
-							"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel. Ex. { \"variables\": { \"CALLERID(name)\": \"Alice\" } }",
+							"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel. Each variable value may be either a string or an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in channel events (defaults to false). Ex. { \"variables\": { \"CALLERID(name)\": \"Alice\", \"Call_State\": { \"value\": \"WaitingForAgent\", \"report_events\": true } } }",
 							"paramType": "body",
-							"required": false,
+							"required": true,
 							"dataType": "containers",
 							"allowMultiple": false
 						}
@@ -2026,7 +2035,7 @@
 						},
 						{
 							"name": "variables",
-							"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\" } }",
+							"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Each variable value may be either a string or an object containing \"value\" (string) and optional \"report_events\" (boolean) to include updates for that variable in channel events (defaults to false). Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\", \"Call_State\": { \"value\": \"WaitingForAgent\", \"report_events\": true } } }",
 							"paramType": "body",
 							"required": false,
 							"dataType": "containers",