Filters
Filters allow you to permit and route events conditionally based on the contents of their Headers, Body, Query, or Path.
The two most common uses of filters are:
- Allowing only events with useful information to pass through to a destination.
- Routing events to different destinations based on their contents, as in a fan out approach.
Filters utilize JSON, and support matching on any value (string, number, boolean, null), on nested objects, and on arrays. There is also support for special operators. For a full rundown of supported schema, see filter syntax.
Filters are applied to an event after a transformation by default, if one is defined. However, you can configure the order of transformations and filters in your connection rules. This allows you to run transformations before or after filters, depending on your use case.
How to Order Transformations & Filters ->
Learn how to control the order of transformations and filters in your connections.
Filter limitations
Filters were designed for JSON payloads. To filter non-JSON payloads, you can use a transformation to convert the payload to JSON. The transformation can be configured to run before the filter.
Basic, non-structured filters, such as partial or exact matches on strings, will function with XML payloads.
Filters syntax
Filters utilize JSON, and support matching on any value (string, number, boolean, null), on nested objects, and on arrays.
The following events would be permitted through the filter, based on their request bodies and respective filters.
Simple matches
Single value match
Simple primitive match
Nested object match
Also valid:
Array contains match
Array contains (multiple) match
Array must contain all the specified values.
Non-object match
To match on a string that is not part of an object structure (for example, to set a filter on a path), simply consider the string to be at the root of your filter:
Complex matches using operators
Operators allow for more complex matching strategies, beyond simple equivalency tests.
| Operator | Supported Type | Description |
|---|---|---|
$gte | number, string | Greater than or equal to |
$gt | number, string | Greater than |
$lt | number, string | Less than |
$lte | number, string | Less than or equal to |
$eq | array, number, object, string | Equal (or deep equal) |
$neq | array, number, object, string | Not Equal (or deep not equal) |
$in | array, string | Contains |
$nin | array, string | Does not contain |
$startsWith | string | Starts with text |
$endsWith | string | Ends with text |
$or | array | Array of conditions to match |
$and | array | Array of conditions to match |
$ref | <field> | Reference a field |
$exist | boolean | Undefined or not undefined |
$not | Valid syntax | Negation |
Value comparison operators
The following demonstrates a matching filter using the $lte (less than or equal to) operator.
$or and $and operators
$or and $and evaluate matches across an array of values. The array of conditions can contain any other valid schema.
The following demonstrates a matching filter using the $or operator.
The following demonstrates a matching filter using the $or operator. The data has hello: "world", which matches the first option in the $or array, so the event is Not Filtered — it matches and is forwarded.
To see Filtered — the event filtered out, not forwarded — change the data to something that matches neither option (e.g. {"hello":"other"}).
Reference operator
$ref references other values in your JSON input when evaluating a match. The reference input must be a string representing the value path.
// Event request body
{
"type": "example",
"nested_object": {
"hello": "world",
"array": [1, 2, 3]
}
}
// Example reference values
"$ref" : "type" // evaluates to "example"
"$ref" : "nested_object.hello" // evaluates to "world"
"$ref" : "nested_object.array[1]" // evaluates to 1
"$ref" : "nested_object.array[$index]" // evaluates to 1, 2, or 3 (depending on the current index)
The following demonstrates a matching filter using the $ref operator.
You may also reference the current array index, instead of a specific index, using $index. Multiple indexes can be referenced when dealing with nested arrays.
Test if any of the elements in the array has the same value for updated_at and created_at. This will match because the 2nd element has the same date for both updated_at and created_at.
$ref may also be used in conjunction with other operators.
$exist operator
$exist requires a field to be undefined when false and array, number, object, string, boolean or null when true.
Negation operator
$not negation of the schema.
Examples using operators
The following examples show real-world filter patterns. For filter-out scenarios, we include both a Filtered (event filtered out, not forwarded) and Not Filtered (event matches and is forwarded) example to clarify the behavior.
Example 1 - Filter if property = value (nested)
Condition Don't allow "subtype" = "message_changed" OR "subtype" = "message_deleted"
This event is Filtered — it does not match and is filtered out (not forwarded) — because subtype is "message_changed":
In contrast, this event is Not Filtered — it matches and is forwarded — because subtype is "message_sent", which is not in the filtered-out list:
Example 2 - Two properties meet criteria with 'or' condition
Condition Only allow "subtype" = "message_changed" OR "subtype" = "message_deleted" AND "type"="message"
This event is Not Filtered — it matches and is forwarded — because type is "message" and subtype is "message_changed":
This event is Filtered — it does not match and is filtered out (not forwarded) — because subtype is "message_sent", which is not in the match list:
Example 3 - using $not and $or operators
Condition
- Allow "event" = "message"
- AND don't allow "team_id" = "team1" OR "team_id" = "team2"
- AND if "subtype" exists, don't allow "subtype" = "message_changed" OR "subtype" = "message_deleted"
This event is Filtered — it does not match and is filtered out (not forwarded) — because subtype is "message_changed":
This event is Not Filtered — it matches and is forwarded — because subtype is "message_sent" and team_id is "team", which satisfy the conditions:
Example 4 - using $not and $endsWith
Condition Don't allow where "subtype" ends with "message_changed"
This event is Filtered — it does not match and is filtered out (not forwarded) — because "some_message_changed" ends with "message_changed":
This event is Not Filtered — it matches and is forwarded — because "message_sent" does not end with "message_changed":
Create or add a filter
Filters are applied to a connection, just like any other rule. You can add a filter when creating a new connection or to an existing connection.
To version control your filters, consider creating or updating your connection using the API.
- Open Connections. Click the connection line.
- In Connection Rules, click , then Editor.
- Select the tab for
Body,Headers,Query, orPathand enter the schema in the right pane using filter syntax. The left pane shows a sample payload for reference. - Click to verify, then .
- Click again on the connection form to apply.
For a new connection, include the filter in your initial upsert:
hookdeck connection upsert my-connection \
--source-name my-source --source-type WEBHOOK \
--destination-name my-destination --destination-type HTTP \
--destination-url https://api.example.com/webhooks \
--rules-file rules.json
To add a filter to an existing connection, run upsert with your updated rules.json:
hookdeck connection upsert my-connection \
--rules-file rules.json
Where rules.json contains:
[
{
"type": "filter",
"body": {
"type": "order.completed"
}
}
]
See the CLI Connection Commands reference for all available options.
New connection
{
"name": "shopify-my-api",
"source": {
"name": "shopify"
},
"destination": {
"name": "my-api",
"config": {
"url": "https://mock.hookdeck.com/example"
}
}
} {
"id": "web_nbbweTiOtzsm",
"team_id": "tm_lbhzBKgFOUnB",
"updated_at": "2026-01-14T13:36:41.634Z",
"created_at": "2026-01-14T13:36:41.595Z",
"paused_at": null,
"name": "shopify-my-api",
"rules": [],
"description": null,
"destination": {
"id": "des_TU9ioCk5EHUU",
"team_id": "tm_lbhzBKgFOUnB",
"updated_at": "2026-01-14T13:36:41.615Z",
"created_at": "2026-01-14T13:35:55.263Z",
"name": "my-api",
"description": null,
"type": "HTTP",
"config": {
"url": "https://mock.hookdeck.com/example",
"rate_limit": null,
"rate_limit_period": "second",
"http_method": null,
"path_forwarding_disabled": false,
"auth": {},
"auth_type": "HOOKDECK_SIGNATURE"
},
"disabled_at": null
},
"source": {
"id": "src_qa5626p6y5o79b",
"team_id": "tm_lbhzBKgFOUnB",
"updated_at": "2026-01-14T13:36:41.615Z",
"created_at": "2026-01-14T13:35:55.226Z",
"name": "shopify",
"description": null,
"type": "WEBHOOK",
"config": {
"allowed_http_methods": [
"POST",
"PUT",
"PATCH",
"DELETE"
],
"custom_response": null
},
"url": "http://localhost:8787/qa5626p6y5o79b",
"disabled_at": null,
"authenticated": false
},
"disabled_at": null,
"full_name": "shopify -> shopify-my-api"
} Include the filter in the rules array when creating the connection:
curl -X PUT "https://api.hookdeck.com/2025-07-01/connections" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "my-connection",
"source": { "name": "my-source" },
"destination": { "name": "my-destination" },
"rules": [
{
"type": "filter",
"body": { "type": "order.completed" }
}
]
}'
Existing connection
{
"rules": [
{
"type": "retry",
"count": 5,
"interval": 3600000,
"strategy": "linear"
},
{
"body": {
"type": "customer.subscription.updated"
},
"type": "filter"
}
]
} {
"id": "web_FMKlTwAoGFRu",
"team_id": "tm_lbhzBKgFOUnB",
"updated_at": "2026-01-14T13:36:41.659Z",
"created_at": "2026-01-14T13:36:06.347Z",
"paused_at": null,
"name": "shopify-orders",
"rules": [
{
"type": "retry",
"count": 5,
"interval": 3600000,
"strategy": "linear",
"response_status_codes": null
},
{
"body": {
"type": "customer.subscription.updated"
},
"type": "filter"
}
],
"description": null,
"destination": {
"id": "des_TU9ioCk5EHUU",
"team_id": "tm_lbhzBKgFOUnB",
"updated_at": "2026-01-14T13:36:41.615Z",
"created_at": "2026-01-14T13:35:55.263Z",
"name": "my-api",
"description": null,
"type": "HTTP",
"config": {
"url": "https://mock.hookdeck.com/example",
"rate_limit": null,
"rate_limit_period": "second",
"http_method": null,
"path_forwarding_disabled": false,
"auth": {},
"auth_type": "HOOKDECK_SIGNATURE"
},
"disabled_at": null
},
"source": {
"id": "src_qa5626p6y5o79b",
"team_id": "tm_lbhzBKgFOUnB",
"updated_at": "2026-01-14T13:36:41.615Z",
"created_at": "2026-01-14T13:35:55.226Z",
"name": "shopify",
"description": null,
"type": "WEBHOOK",
"config": {
"allowed_http_methods": [
"POST",
"PUT",
"PATCH",
"DELETE"
],
"custom_response": null
},
"url": "http://localhost:8787/qa5626p6y5o79b",
"disabled_at": null,
"authenticated": false
},
"disabled_at": null,
"full_name": "shopify -> shopify-orders"
} Send a PUT request with the connection ID in the path and the updated rules array:
curl -X PUT "https://api.hookdeck.com/2025-07-01/connections/conn_xxx" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rules": [
{
"type": "filter",
"body": { "type": "order.completed" }
}
]
}'
From this point on, any events not matching the filter are disregarded by Hookdeck and recorded as ignored events. They will not appear in your list of events or generate delivery attempts. When a request produces only ignored events (e.g., because all connections filtered it out), it is a discarded request.
Edit a filter
Editing a filter changes the schema that determines an event's delivery eligibility.
- Open Connections. Click the connection line.
- Next to the filter rule, click Editor.
- Select the tab for
Body,Headers,Query, orPathand update the schema in the right pane using filter syntax. The left pane shows a sample payload for reference. - Click to verify, then .
- Click again on the connection form to apply.
hookdeck connection upsert my-connection \
--rules-file rules.json
Update the filter schema in your rules.json file. See the CLI Connection Commands reference for all available options.
{
"rules": [
{
"type": "retry",
"count": 5,
"interval": 3600000,
"strategy": "linear"
},
{
"body": {
"type": "customer.subscription.updated"
},
"type": "filter"
}
]
} {
"id": "web_FMKlTwAoGFRu",
"team_id": "tm_lbhzBKgFOUnB",
"updated_at": "2026-01-14T13:36:41.659Z",
"created_at": "2026-01-14T13:36:06.347Z",
"paused_at": null,
"name": "shopify-orders",
"rules": [
{
"type": "retry",
"count": 5,
"interval": 3600000,
"strategy": "linear",
"response_status_codes": null
},
{
"body": {
"type": "customer.subscription.updated"
},
"type": "filter"
}
],
"description": null,
"destination": {
"id": "des_TU9ioCk5EHUU",
"team_id": "tm_lbhzBKgFOUnB",
"updated_at": "2026-01-14T13:36:41.615Z",
"created_at": "2026-01-14T13:35:55.263Z",
"name": "my-api",
"description": null,
"type": "HTTP",
"config": {
"url": "https://mock.hookdeck.com/example",
"rate_limit": null,
"rate_limit_period": "second",
"http_method": null,
"path_forwarding_disabled": false,
"auth": {},
"auth_type": "HOOKDECK_SIGNATURE"
},
"disabled_at": null
},
"source": {
"id": "src_qa5626p6y5o79b",
"team_id": "tm_lbhzBKgFOUnB",
"updated_at": "2026-01-14T13:36:41.615Z",
"created_at": "2026-01-14T13:35:55.226Z",
"name": "shopify",
"description": null,
"type": "WEBHOOK",
"config": {
"allowed_http_methods": [
"POST",
"PUT",
"PATCH",
"DELETE"
],
"custom_response": null
},
"url": "http://localhost:8787/qa5626p6y5o79b",
"disabled_at": null,
"authenticated": false
},
"disabled_at": null,
"full_name": "shopify -> shopify-orders"
} Update the rules array in the request body to include the modified filter. See the Connections API reference.
Filters are updated immediately, and any events received going forward are tested against the new filter contents.
Delete a filter
Deleting a filter allows all events, regardless of content, to pass through a connection unimpeded.
- Open Connections. Click the connection line.
- Next to the filter rule, click the trash icon to remove it.
- Click on the connection form to apply.
Update your connection rules to remove the filter from the rules array:
hookdeck connection upsert my-connection \
--rules-file rules.json
Ensure rules.json no longer includes the filter rule. See the CLI Connection Commands reference.
{
"rules": [
{
"type": "retry",
"count": 5,
"interval": 3600000,
"strategy": "linear"
},
{
"body": {
"type": "customer.subscription.updated"
},
"type": "filter"
}
]
} {
"id": "web_FMKlTwAoGFRu",
"team_id": "tm_lbhzBKgFOUnB",
"updated_at": "2026-01-14T13:36:41.659Z",
"created_at": "2026-01-14T13:36:06.347Z",
"paused_at": null,
"name": "shopify-orders",
"rules": [
{
"type": "retry",
"count": 5,
"interval": 3600000,
"strategy": "linear",
"response_status_codes": null
},
{
"body": {
"type": "customer.subscription.updated"
},
"type": "filter"
}
],
"description": null,
"destination": {
"id": "des_TU9ioCk5EHUU",
"team_id": "tm_lbhzBKgFOUnB",
"updated_at": "2026-01-14T13:36:41.615Z",
"created_at": "2026-01-14T13:35:55.263Z",
"name": "my-api",
"description": null,
"type": "HTTP",
"config": {
"url": "https://mock.hookdeck.com/example",
"rate_limit": null,
"rate_limit_period": "second",
"http_method": null,
"path_forwarding_disabled": false,
"auth": {},
"auth_type": "HOOKDECK_SIGNATURE"
},
"disabled_at": null
},
"source": {
"id": "src_qa5626p6y5o79b",
"team_id": "tm_lbhzBKgFOUnB",
"updated_at": "2026-01-14T13:36:41.615Z",
"created_at": "2026-01-14T13:35:55.226Z",
"name": "shopify",
"description": null,
"type": "WEBHOOK",
"config": {
"allowed_http_methods": [
"POST",
"PUT",
"PATCH",
"DELETE"
],
"custom_response": null
},
"url": "http://localhost:8787/qa5626p6y5o79b",
"disabled_at": null,
"authenticated": false
},
"disabled_at": null,
"full_name": "shopify -> shopify-orders"
} Send a PUT request with the rules array omitting the filter rule. See the Connections API reference.
Try out a filter
Use the interactive filter tester below to experiment with Body, Headers, Path, and Query. Enter test data and a filter schema to see whether events would be allowed through or filtered out.