Routing Filters
Add a filter rule to each destination, so that only a matching request goes to the target.
A routing filter selects which incoming request goes to each destination. It uses the request body, the headers, or the query parameters. For example, you can send a GitHub push event to Slack, and skip the other event types.
A destination with no filters receives every request. An existing configuration keeps this behavior.
How routing filters operate#
Each destination is a link between an endpoint and a relay target. It can have a list of routing filters. When a request arrives:
- PayloadRelay evaluates each filter in the list against the payload after the transforms, the headers, and the query parameters.
- All the filters must match. This is AND logic. If one filter fails, PayloadRelay skips the destination.
- Activity shows a skipped destination as
SKIPPED_BY_ROUTING, and you can see what happened.
For OR logic across different targets, attach the matching filter to each target destination. PayloadRelay evaluates each destination separately. One inbound request can thus go to more than one target.
Example: send a GitHub
pushevent to Slack, and apull_requestevent to a webhook. Create one Slack destination withheaders.X-GitHub-Event EQUALS push, and one webhook destination withheaders.X-GitHub-Event EQUALS pull_request. PayloadRelay evaluates the two destinations separately for each request.
Note: each endpoint can have one destination for each relay target. To send the same payload to one target under separate conditions, combine the conditions in one filter set. For example, use
INwith a comma-separated list, orREGEXwith an alternation pattern such as^(push|opened)$.
Field path syntax#
A field path identifies a value in a request. A routing filter uses these field paths:
| Syntax | Example | Resolves to |
|---|---|---|
body | body | The complete JSON body as text |
body.<path> | body.event_type | A field in the JSON body |
body.<path>.<nested> | body.repository.name | A nested field |
body.<path>.<n> | body.items.0.name | An array element by index (zero-based) |
headers.<name> | headers.x-github-event | An inbound header (case-insensitive lookup) |
query.<name> | query.source | A query parameter |
Use the dot notation for an array (body.items.0.name). PayloadRelay does not accept the bracket notation (body.items[0].name).
If the field does not exist, the result depends on the operator. EXISTS returns false. EQUALS and the comparison operators return false. NOT_EXISTS, NOT_EQUALS, NOT_CONTAINS, NOT_IN, and NOT_REGEX return true. If a negated comparison must have the field, add an EXISTS filter.
PayloadRelay uses a JSON null as a missing field. It changes a body value to text before the comparison. It compares an object value and an array value as compact JSON strings. For a non-JSON request that PayloadRelay cannot parse as JSON, a body.* path operates as a missing field. PayloadRelay joins repeated query parameters with commas before the query.* filters evaluate them.
Supported Operators#
| Operator | Description |
|---|---|
EQUALS | The field value is equal to the comparison value (exact match) |
NOT_EQUALS | The field value is not equal to the comparison value |
CONTAINS | The field value contains the comparison value as a substring |
NOT_CONTAINS | The field value does not contain the comparison value |
STARTS_WITH | The field value starts with the comparison value |
ENDS_WITH | The field value ends with the comparison value |
IN | The field value is one item in a comma-separated or newline-separated list |
NOT_IN | The field value is no item in the list |
EXISTS | The field exists and is not null |
NOT_EXISTS | The field does not exist, or it is null |
GREATER_THAN | The field value, as a number, is more than the comparison value |
LESS_THAN | The field value, as a number, is less than the comparison value |
REGEX | The field value matches the regular expression (partial match with find()) |
NOT_REGEX | The field value does not match the regular expression |
Case sensitivity#
A string operator (EQUALS, NOT_EQUALS, CONTAINS, NOT_CONTAINS, STARTS_WITH, ENDS_WITH, IN, NOT_IN) has a case-sensitive control. It is enabled by default. To make the comparison case-insensitive, disable the control.
Numeric operators#
GREATER_THAN and LESS_THAN parse the field value as a number. If the value is not a number, such as "critical", the filter does not match.
Regular expressions#
REGEX and NOT_REGEX use the Java-compatible regular expression syntax with partial matching. The pattern can be at any position in the value. PayloadRelay examines a pattern when you save it. An invalid regex returns a 400 error.
Limits#
- A maximum of 20 filters for each destination.
comparisonValuehas a maximum of 1024 characters.fieldPathhas a maximum of 512 characters.
Examples#
Only forward GitHub push events#
{
"fieldPath": "headers.x-github-event",
"operator": "EQUALS",
"comparisonValue": "push",
"caseSensitive": false
}Only forward high-severity alerts#
{
"fieldPath": "body.severity",
"operator": "IN",
"comparisonValue": "error,critical"
}Forward when a numeric priority exceeds a threshold#
{
"fieldPath": "body.priority",
"operator": "GREATER_THAN",
"comparisonValue": "5"
}Route emails from a specific domain#
{
"fieldPath": "body.email",
"operator": "REGEX",
"comparisonValue": ".+@example\\.com$"
}Skip test events#
{
"fieldPath": "body.environment",
"operator": "NOT_EQUALS",
"comparisonValue": "test"
}Observability#
When a routing filter excludes a destination, Activity shows SKIPPED_BY_ROUTING. Expand the request, and read the delivery status of each destination. The reason identifies the configured filter that did not match. Examples are "filter 'body.event_type EQUALS push' did not match" and "filter 'body.severity EXISTS' did not match: field does not exist".
Privacy note: a reason string never contains the resolved payload value. An activity log does not keep a request body. To see the incoming value, read the request fields on the Activity row. You can also send the request again to a receiver with verbose logging.
Test button behavior#
The relay-target test button on the Relay targets page sends a synthetic payload directly to the target. It does not use the routing filters. A test payload is rarely the same as a production condition. The response contains "routingFiltersBypassed": "true".
To examine the filters, send a real request to the endpoint relay URL, and read the Activity log.
Troubleshooting#
My destination is always skipped. Read the Activity log for the SKIPPED_BY_ROUTING reason. The reason gives the field path, the operator, and the comparison value of the failed filter. The usual causes are a wrong field path, a wrong operator, a case mismatch, and a missing nested JSON path. The reason does not contain the incoming value. To compare the value, send the request again to a debug receiver.
My regex does not match. REGEX uses the Java-compatible syntax with partial matching. The anchors (^ and $) are optional. Escape a special character such as \. and \d.
My numeric comparison fails. GREATER_THAN and LESS_THAN need a numeric field. A JSON string such as "5" changes to 5. A value such as "high" fails the comparison. For a string severity level, use EQUALS or IN.
I want OR logic on one target. Each endpoint can have one destination for each target. Combine the conditions in one filter set. Use IN with a comma-separated list, or REGEX with an alternation such as ^(push|opened)$. For OR logic across different targets, attach the filter to each target destination. PayloadRelay evaluates each destination separately.