← Back to documentation

Routing Filters

Add a filter rule to each destination, so that only a matching request goes to the target.

6 min read

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:

  1. PayloadRelay evaluates each filter in the list against the payload after the transforms, the headers, and the query parameters.
  2. All the filters must match. This is AND logic. If one filter fails, PayloadRelay skips the destination.
  3. 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 push event to Slack, and a pull_request event to a webhook. Create one Slack destination with headers.X-GitHub-Event EQUALS push, and one webhook destination with headers.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 IN with a comma-separated list, or REGEX with 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:

SyntaxExampleResolves to
bodybodyThe complete JSON body as text
body.<path>body.event_typeA field in the JSON body
body.<path>.<nested>body.repository.nameA nested field
body.<path>.<n>body.items.0.nameAn array element by index (zero-based)
headers.<name>headers.x-github-eventAn inbound header (case-insensitive lookup)
query.<name>query.sourceA 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#

OperatorDescription
EQUALSThe field value is equal to the comparison value (exact match)
NOT_EQUALSThe field value is not equal to the comparison value
CONTAINSThe field value contains the comparison value as a substring
NOT_CONTAINSThe field value does not contain the comparison value
STARTS_WITHThe field value starts with the comparison value
ENDS_WITHThe field value ends with the comparison value
INThe field value is one item in a comma-separated or newline-separated list
NOT_INThe field value is no item in the list
EXISTSThe field exists and is not null
NOT_EXISTSThe field does not exist, or it is null
GREATER_THANThe field value, as a number, is more than the comparison value
LESS_THANThe field value, as a number, is less than the comparison value
REGEXThe field value matches the regular expression (partial match with find())
NOT_REGEXThe 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.
  • comparisonValue has a maximum of 1024 characters.
  • fieldPath has a maximum of 512 characters.

Examples#

Only forward GitHub push events#

Code Example
{
  "fieldPath": "headers.x-github-event",
  "operator": "EQUALS",
  "comparisonValue": "push",
  "caseSensitive": false
}

Only forward high-severity alerts#

Code Example
{
  "fieldPath": "body.severity",
  "operator": "IN",
  "comparisonValue": "error,critical"
}

Forward when a numeric priority exceeds a threshold#

Code Example
{
  "fieldPath": "body.priority",
  "operator": "GREATER_THAN",
  "comparisonValue": "5"
}

Route emails from a specific domain#

Code Example
{
  "fieldPath": "body.email",
  "operator": "REGEX",
  "comparisonValue": ".+@example\\.com$"
}

Skip test events#

Code Example
{
  "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.