diff --git a/CHANGES b/CHANGES
index f5deca99e8ad78920e0cad4fce0b5de5b079f1bd..a2abf563b38ae4be8e502596cc7eb41b9876a8b0 100644
--- a/CHANGES
+++ b/CHANGES
@@ -8,6 +8,18 @@
 ===
 ==============================================================================
 
+------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 16.2.0 to Asterisk 16.3.0 ----------
+------------------------------------------------------------------------------
+
+ARI
+------------------
+ * Application event filtering is now supported. An application can now specify
+   an "allowed" and/or "disallowed" list(s) of event types. Only those types
+   indicated in the "allowed" list are sent to the application. Conversely, any
+   types defined in the "disallowed" list are not sent to the application. Note
+   that if a type is specified in both lists "disallowed" takes precedence.
+
 ------------------------------------------------------------------------------
 --- Functionality changes from Asterisk 16.1.0 to Asterisk 16.2.0 ------------
 ------------------------------------------------------------------------------
diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h
index b0829ab7363266155e869e878f3f0eb6fbbad20e..c40e901e5f39dcc7694b9c138d179e5182c7b60a 100644
--- a/include/asterisk/stasis_app.h
+++ b/include/asterisk/stasis_app.h
@@ -147,6 +147,17 @@ struct stasis_app;
  */
 const char *stasis_app_name(const struct stasis_app *app);
 
+/*!
+ * \brief Return the JSON representation of a Stasis application.
+ * \since 16.3.0
+ *
+ * \param app The application.
+ *
+ * \return JSON representation of app with given name.
+ * \return \c NULL on error.
+ */
+struct ast_json *stasis_app_object_to_json(struct stasis_app *app);
+
 /*!
  * \brief Return the JSON representation of a Stasis application.
  *
@@ -958,6 +969,41 @@ struct ast_cli_args;
  */
 void stasis_app_to_cli(const struct stasis_app *app, struct ast_cli_args *a);
 
+/*!
+ * \brief Convert and add the app's event type filter(s) to the given json object.
+ *
+ * \param app The application
+ * \param json The json object to add the filter data to
+ *
+ * \return The given json object
+ */
+struct ast_json *stasis_app_event_filter_to_json(struct stasis_app *app, struct ast_json *json);
+
+/*!
+ * \brief Set the application's event type filter
+ *
+ * \param app The application
+ * \param filter The allowed and/or disallowed event filter
+ *
+ * \return 0 if successfully set
+ */
+int stasis_app_event_filter_set(struct stasis_app *app, struct ast_json *filter);
+
+/*!
+ * \brief Check if the given event should be filtered.
+ *
+ * Attempts first to find the event in the application's disallowed events list.
+ * If found then the event won't be sent to the remote. If not found in the
+ * disallowed list then a search is done to see if it can be found in the allowed
+ * list. If found the event message is sent, otherwise it is not sent.
+ *
+ * \param app_name The application name
+ * \param event The event to check
+ *
+ * \return True if allowed, false otherwise
+ */
+int stasis_app_event_allowed(const char *app_name, struct ast_json *event);
+
 /*! @} */
 
 #endif /* _ASTERISK_STASIS_APP_H */
diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c
index a06a1f5e59e894f4f49de4a37f728bb520027883..44d9d7727cc7daf4ff0d0c0c37a82adc4474999e 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -6534,6 +6534,8 @@ int ast_ari_validate_application(struct ast_json *json)
 	int has_channel_ids = 0;
 	int has_device_names = 0;
 	int has_endpoint_ids = 0;
+	int has_events_allowed = 0;
+	int has_events_disallowed = 0;
 	int has_name = 0;
 
 	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
@@ -6581,6 +6583,28 @@ int ast_ari_validate_application(struct ast_json *json)
 				res = 0;
 			}
 		} else
+		if (strcmp("events_allowed", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_events_allowed = 1;
+			prop_is_valid = ast_ari_validate_list(
+				ast_json_object_iter_value(iter),
+				ast_ari_validate_object);
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Application field events_allowed failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("events_disallowed", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_events_disallowed = 1;
+			prop_is_valid = ast_ari_validate_list(
+				ast_json_object_iter_value(iter),
+				ast_ari_validate_object);
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Application field events_disallowed failed validation\n");
+				res = 0;
+			}
+		} else
 		if (strcmp("name", ast_json_object_iter_key(iter)) == 0) {
 			int prop_is_valid;
 			has_name = 1;
@@ -6619,6 +6643,16 @@ int ast_ari_validate_application(struct ast_json *json)
 		res = 0;
 	}
 
+	if (!has_events_allowed) {
+		ast_log(LOG_ERROR, "ARI Application missing required field events_allowed\n");
+		res = 0;
+	}
+
+	if (!has_events_disallowed) {
+		ast_log(LOG_ERROR, "ARI Application missing required field events_disallowed\n");
+		res = 0;
+	}
+
 	if (!has_name) {
 		ast_log(LOG_ERROR, "ARI Application missing required field name\n");
 		res = 0;
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index ab0b2f60b08b6d0234d9e781d357bcf96e58f448..1ee74f4865b83eaaf54b1a74b333dfb40f2040f5 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -1830,6 +1830,8 @@ ari_validator ast_ari_validate_application_fn(void);
  * - channel_ids: List[string] (required)
  * - device_names: List[string] (required)
  * - endpoint_ids: List[string] (required)
+ * - events_allowed: List[object] (required)
+ * - events_disallowed: List[object] (required)
  * - name: string (required)
  */
 
diff --git a/res/ari/resource_applications.c b/res/ari/resource_applications.c
index fc93b28e228c98db416837e17cfdc7fdb2493b46..4a97401cf31249c7c66626e6f281b0030141395c 100644
--- a/res/ari/resource_applications.c
+++ b/res/ari/resource_applications.c
@@ -168,3 +168,23 @@ void ast_ari_applications_unsubscribe(struct ast_variable *headers,
 			"Error processing request");
 	}
 }
+
+void ast_ari_applications_filter(struct ast_variable *headers,
+	struct ast_ari_applications_filter_args *args,
+	struct ast_ari_response *response)
+{
+	struct stasis_app *app = stasis_app_get_by_name(args->application_name);
+
+	if (!app) {
+		ast_ari_response_error(response, 404, "Not Found", "Application not found");
+		return;
+	}
+
+	if (stasis_app_event_filter_set(app, args->filter)) {
+		ast_ari_response_error(response, 400, "Bad Request", "Invalid format definition");
+	} else {
+		ast_ari_response_ok(response, stasis_app_object_to_json(app));
+	}
+
+	ao2_ref(app, -1);
+}
diff --git a/res/ari/resource_applications.h b/res/ari/resource_applications.h
index be62e9d5fd275511e5fbfab8756ffdf11e5aabda..23a9b9bd0d79e5ce3d86a7b30509dab90172783c 100644
--- a/res/ari/resource_applications.h
+++ b/res/ari/resource_applications.h
@@ -127,5 +127,33 @@ int ast_ari_applications_unsubscribe_parse_body(
  * \param[out] response HTTP response
  */
 void ast_ari_applications_unsubscribe(struct ast_variable *headers, struct ast_ari_applications_unsubscribe_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_applications_filter() */
+struct ast_ari_applications_filter_args {
+	/*! Application's name */
+	const char *application_name;
+	/*! Specify which event types to allow/disallow */
+	struct ast_json *filter;
+};
+/*!
+ * \brief Body parsing function for /applications/{applicationName}/eventFilter.
+ * \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_applications_filter_parse_body(
+	struct ast_json *body,
+	struct ast_ari_applications_filter_args *args);
+
+/*!
+ * \brief Filter application events types.
+ *
+ * Allowed and/or disallowed event type filtering can be done. The body (parameter) should specify a JSON key/value object that describes the type of event filtering needed. One, or both of the following keys can be designated:<br /><br />"allowed" - Specifies an allowed list of event types<br />"disallowed" - Specifies a disallowed list of event types<br /><br />Further, each of those key's value should be a JSON array that holds zero, or more JSON key/value objects. Each of these objects must contain the following key with an associated value:<br /><br />"type" - The type name of the event to filter<br /><br />The value must be the string name (case sensitive) of the event type that needs filtering. For example:<br /><br />{ "allowed": [ { "type": "StasisStart" }, { "type": "StasisEnd" } ] }<br /><br />As this specifies only an allowed list, then only those two event type messages are sent to the application. No other event messages are sent.<br /><br />The following rules apply:<br /><br />* If the body is empty, both the allowed and disallowed filters are set empty.<br />* If both list types are given then both are set to their respective values (note, specifying an empty array for a given type sets that type to empty).<br />* If only one list type is given then only that type is set. The other type is not updated.<br />* An empty "allowed" list means all events are allowed.<br />* An empty "disallowed" list means no events are disallowed.<br />* Disallowed events take precedence over allowed events if the event type is specified in both lists.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_applications_filter(struct ast_variable *headers, struct ast_ari_applications_filter_args *args, struct ast_ari_response *response);
 
 #endif /* _ASTERISK_RESOURCE_APPLICATIONS_H */
diff --git a/res/ari/resource_events.c b/res/ari/resource_events.c
index 5983f7b531be94f9dc7ac752ba18349a182e7b78..d935da15f530ef611a97138f89d4db89d89feb46 100644
--- a/res/ari/resource_events.c
+++ b/res/ari/resource_events.c
@@ -114,7 +114,7 @@ static void stasis_app_message_handler(
 				"Queued '%s' message for Stasis app '%s'; websocket is not ready\n",
 				msg_type,
 				msg_application);
-	} else {
+	} else if (stasis_app_event_allowed(app_name, message)) {
 		if (stasis_app_get_debug_by_name(app_name)) {
 			char *str = ast_json_dump_string_format(message, ast_ari_json_format());
 
diff --git a/res/res_ari_applications.c b/res/res_ari_applications.c
index fd8a448cef66e91cad39a2f23ddf05c76db02e0a..4d1443ec3642fb30ca261e676d580d0a2e57d2c3 100644
--- a/res/res_ari_applications.c
+++ b/res/res_ari_applications.c
@@ -459,6 +459,74 @@ fin: __attribute__((unused))
 	ast_free(args.event_source);
 	return;
 }
+int ast_ari_applications_filter_parse_body(
+	struct ast_json *body,
+	struct ast_ari_applications_filter_args *args)
+{
+	/* Parse query parameters out of it */
+	return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /applications/{applicationName}/eventFilter.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_applications_filter_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_applications_filter_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, "applicationName") == 0) {
+			args.application_name = (i->value);
+		} else
+		{}
+	}
+	args.filter = body;
+	ast_ari_applications_filter(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: /* Bad request. */
+	case 404: /* Application does not exist. */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ast_ari_validate_application(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /applications/{applicationName}/eventFilter\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /applications/{applicationName}/eventFilter\n");
+		ast_ari_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+	return;
+}
 
 /*! \brief REST handler for /api-docs/applications.json */
 static struct stasis_rest_handlers applications_applicationName_subscription = {
@@ -471,14 +539,23 @@ static struct stasis_rest_handlers applications_applicationName_subscription = {
 	.children = {  }
 };
 /*! \brief REST handler for /api-docs/applications.json */
+static struct stasis_rest_handlers applications_applicationName_eventFilter = {
+	.path_segment = "eventFilter",
+	.callbacks = {
+		[AST_HTTP_PUT] = ast_ari_applications_filter_cb,
+	},
+	.num_children = 0,
+	.children = {  }
+};
+/*! \brief REST handler for /api-docs/applications.json */
 static struct stasis_rest_handlers applications_applicationName = {
 	.path_segment = "applicationName",
 	.is_wildcard = 1,
 	.callbacks = {
 		[AST_HTTP_GET] = ast_ari_applications_get_cb,
 	},
-	.num_children = 1,
-	.children = { &applications_applicationName_subscription, }
+	.num_children = 2,
+	.children = { &applications_applicationName_subscription,&applications_applicationName_eventFilter, }
 };
 /*! \brief REST handler for /api-docs/applications.json */
 static struct stasis_rest_handlers applications = {
diff --git a/res/res_stasis.c b/res/res_stasis.c
index 704d779c48485b835750b8b89efe06b151135a15..47623df9091053c4f31f8c9f7eb5c4cb65ab628d 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -1689,13 +1689,14 @@ static struct ast_json *app_event_sources_to_json(
 	return json;
 }
 
-static struct ast_json *stasis_app_object_to_json(struct stasis_app *app)
+struct ast_json *stasis_app_object_to_json(struct stasis_app *app)
 {
 	if (!app) {
 		return NULL;
 	}
 
-	return app_event_sources_to_json(app, app_to_json(app));
+	return stasis_app_event_filter_to_json(
+		app, app_event_sources_to_json(app, app_to_json(app)));
 }
 
 struct ast_json *stasis_app_to_json(const char *app_name)
diff --git a/res/stasis/app.c b/res/stasis/app.c
index 465b8c5d554309a9a3eec44e6a738da78dec913f..fc0b2767a3b257af099ca0a23209419faaeccbfc 100644
--- a/res/stasis/app.c
+++ b/res/stasis/app.c
@@ -65,6 +65,10 @@ struct stasis_app {
 	enum stasis_app_subscription_model subscription_model;
 	/*! Whether or not someone wants to see debug messages about this app */
 	int debug;
+	/*! An array of allowed events types for this application */
+	struct ast_json *events_allowed;
+	/*! An array of disallowed events types for this application */
+	struct ast_json *events_disallowed;
 	/*! Name of the Stasis application */
 	char name[];
 };
@@ -316,6 +320,12 @@ static void app_dtor(void *obj)
 	app->forwards = NULL;
 	ao2_cleanup(app->data);
 	app->data = NULL;
+
+	ast_json_unref(app->events_allowed);
+	app->events_allowed = NULL;
+	ast_json_unref(app->events_disallowed);
+	app->events_disallowed = NULL;
+
 }
 
 static void call_forwarded_handler(struct stasis_app *app, struct stasis_message *message)
@@ -1643,3 +1653,119 @@ void stasis_app_unregister_event_sources(void)
 	stasis_app_unregister_event_source(&bridge_event_source);
 	stasis_app_unregister_event_source(&channel_event_source);
 }
+
+struct ast_json *stasis_app_event_filter_to_json(struct stasis_app *app, struct ast_json *json)
+{
+	if (!app || !json) {
+		return json;
+	}
+
+	ast_json_object_set(json, "events_allowed", app->events_allowed ?
+		ast_json_ref(app->events_allowed) : ast_json_array_create());
+	ast_json_object_set(json, "events_disallowed", app->events_disallowed ?
+		ast_json_ref(app->events_disallowed) : ast_json_array_create());
+
+	return json;
+}
+
+static int app_event_filter_set(struct stasis_app *app,	struct ast_json **member,
+	struct ast_json *filter, const char *filter_type)
+{
+	if (filter && ast_json_typeof(filter) == AST_JSON_OBJECT) {
+		if (!ast_json_object_size(filter)) {
+			/* If no filters are specified then reset this filter type */
+			filter = NULL;
+		} else {
+			/* Otherwise try to get the filter array for this type */
+			filter = ast_json_object_get(filter, filter_type);
+			if (!filter) {
+				/* A filter type exists, but not this one, so don't update */
+				return 0;
+			}
+		}
+	}
+
+	/* At this point the filter object should be an array */
+	if (filter && ast_json_typeof(filter) != AST_JSON_ARRAY) {
+		ast_log(LOG_ERROR, "Invalid json type event filter - app: %s, filter: %s\n",
+				app->name, filter_type);
+		return -1;
+	}
+
+	if (filter) {
+		/* Confirm that at least the type names are specified */
+		struct ast_json *obj;
+		int i;
+
+		for (i = 0; i < ast_json_array_size(filter) &&
+				 (obj = ast_json_array_get(filter, i)); ++i) {
+
+			if (ast_strlen_zero(ast_json_object_string_get(obj, "type"))) {
+				ast_log(LOG_ERROR, "Filter event must have a type - app: %s, "
+						"filter: %s\n",	app->name, filter_type);
+				return -1;
+			}
+		}
+	}
+
+	ao2_lock(app);
+	ast_json_unref(*member);
+	*member = filter ? ast_json_ref(filter) : NULL;
+	ao2_unlock(app);
+
+	return 0;
+}
+
+static int app_events_allowed_set(struct stasis_app *app, struct ast_json *filter)
+{
+	return app_event_filter_set(app, &app->events_allowed, filter, "allowed");
+}
+
+static int app_events_disallowed_set(struct stasis_app *app, struct ast_json *filter)
+{
+	return app_event_filter_set(app, &app->events_disallowed, filter, "disallowed");
+}
+
+int stasis_app_event_filter_set(struct stasis_app *app, struct ast_json *filter)
+{
+	return app_events_disallowed_set(app, filter) || app_events_allowed_set(app, filter);
+}
+
+static int app_event_filter_matched(struct ast_json *array, struct ast_json *event, int empty)
+{
+	struct ast_json *obj;
+	int i;
+
+	if (!array || !ast_json_array_size(array)) {
+		return empty;
+	}
+
+	for (i = 0; i < ast_json_array_size(array) &&
+			(obj = ast_json_array_get(array, i)); ++i) {
+
+		if (ast_strings_equal(ast_json_object_string_get(obj, "type"),
+				ast_json_object_string_get(event, "type"))) {
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+int stasis_app_event_allowed(const char *app_name, struct ast_json *event)
+{
+	struct stasis_app *app = stasis_app_get_by_name(app_name);
+	int res;
+
+	if (!app) {
+		return 0;
+	}
+
+	ao2_lock(app);
+	res = !app_event_filter_matched(app->events_disallowed, event, 0) &&
+		app_event_filter_matched(app->events_allowed, event, 1);
+	ao2_unlock(app);
+	ao2_ref(app, -1);
+
+	return res;
+}
diff --git a/rest-api/api-docs/applications.json b/rest-api/api-docs/applications.json
index cdd69c4178da55c68be6342b79c6b83b8d30fb16..09c5cd5c91ee5083c5228bb00a70de7ddfbde16d 100644
--- a/rest-api/api-docs/applications.json
+++ b/rest-api/api-docs/applications.json
@@ -134,6 +134,47 @@
 					]
 				}
 			]
+		},
+		{
+			"path": "/applications/{applicationName}/eventFilter",
+			"description": "Stasis application",
+			"operations": [
+				{
+					"httpMethod": "PUT",
+					"summary": "Filter application events types.",
+					"notes": "Allowed and/or disallowed event type filtering can be done. The body (parameter) should specify a JSON key/value object that describes the type of event filtering needed. One, or both of the following keys can be designated:<br /><br />\"allowed\" - Specifies an allowed list of event types<br />\"disallowed\" - Specifies a disallowed list of event types<br /><br />Further, each of those key's value should be a JSON array that holds zero, or more JSON key/value objects. Each of these objects must contain the following key with an associated value:<br /><br />\"type\" - The type name of the event to filter<br /><br />The value must be the string name (case sensitive) of the event type that needs filtering. For example:<br /><br />{ \"allowed\": [ { \"type\": \"StasisStart\" }, { \"type\": \"StasisEnd\" } ] }<br /><br />As this specifies only an allowed list, then only those two event type messages are sent to the application. No other event messages are sent.<br /><br />The following rules apply:<br /><br />* If the body is empty, both the allowed and disallowed filters are set empty.<br />* If both list types are given then both are set to their respective values (note, specifying an empty array for a given type sets that type to empty).<br />* If only one list type is given then only that type is set. The other type is not updated.<br />* An empty \"allowed\" list means all events are allowed.<br />* An empty \"disallowed\" list means no events are disallowed.<br />* Disallowed events take precedence over allowed events if the event type is specified in both lists.",
+					"nickname": "filter",
+					"responseClass": "Application",
+					"parameters": [
+						{
+							"name": "applicationName",
+							"description": "Application's name",
+							"paramType": "path",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "filter",
+							"description": "Specify which event types to allow/disallow",
+							"paramType": "body",
+							"required": false,
+							"dataType": "object",
+							"allowMultiple": false
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 400,
+							"reason": "Bad request."
+						},
+						{
+							"code": 404,
+							"reason": "Application does not exist."
+						}
+					]
+				}
+			]
 		}
 	],
 	"models": {
@@ -165,6 +206,16 @@
 					"type": "List[string]",
 					"description": "Names of the devices subscribed to.",
 					"required": true
+				},
+				"events_allowed": {
+					"type": "List[object]",
+					"description": "Event types sent to the application.",
+					"required": true
+				},
+				"events_disallowed": {
+					"type": "List[object]",
+					"description": "Event types not sent to the application.",
+					"required": true
 				}
 			}
 		}