Webhook Filter Expression Language
This syntax is described below. To use it and write your filters, you’ll need to know the payload that the webhook source will send.Overview
The Webhook Filter Expression is a declarative language for filtering webhook payloads based on their JSON content. Filter expressions evaluate to eithertrue (the webhook should be processed) or false (the webhook should be ignored).
The language uses Lisp-style S-expression syntax, where expressions are written as parenthesized prefix notation: (operator arguments...).
Syntax Basics
S-Expression Format
All expressions follow the pattern:(operator argument1 argument2 ...)
- Parentheses: Every expression is wrapped in parentheses
- Prefix notation: The operator comes first, followed by its arguments
- Whitespace: Arguments are separated by whitespace (spaces, tabs, newlines are all valid)
Data Types
- Strings: Must be enclosed in double quotes:
"value" - Numbers: Written without quotes:
42,3.14 - Booleans:
trueorfalse(no quotes) - Lists: Space-separated values in parentheses:
("option1" "option2" "option3")
Field Access
Fields are referenced using dot notation to traverse the JSON payload structure:- Top-level field:
"action" - Nested field:
"issue.state" - Deeply nested:
"pull_request.head.ref" - Array wildcards:
"tags.*.name"extracts thenameproperty from each object in thetagsarray
"action""opened""issue.number"42"issue.labels"["bug", "critical"]"tags.*.name"["bug", "feature"]
Operators
Logical Operators
and
Returns true if all sub-expressions evaluate to true.
Syntax: (and expr1 expr2 ... exprN)
Example:
true only if action is “opened” AND issue state is “open”.
or
Returns true if any sub-expression evaluates to true.
Syntax: (or expr1 expr2 ... exprN)
Example:
true if action is either “opened” OR “edited”.
not
Returns the negation of its single sub-expression.
Syntax: (not expr)
Example:
true if issue state is NOT “closed”.
Equality Operator
eq
Checks for exact equality between a field and a value.
Syntax: (eq "field.path" value)
Examples:
String Operators
starts-with
Checks if a string field starts with a given prefix.
Syntax: (starts-with "field.path" "prefix")
Example:
true if the branch name starts with “feature/”.
Note: Only works on string fields.
Array Operators
has
Checks if an array contains a specific value.
Syntax: (has "field.path" value)
Examples:
true if the labels array contains “bug”.
For arrays of objects, use wildcard paths:
true if any tag object has name equal to “important”.
has-all
Checks if an array contains all specified values.
Syntax: (has-all "field.path" (value1 value2 ... valueN))
Examples:
true only if the labels array contains both “bug” AND “critical”.
For arrays of objects:
true if tags include objects with both names.
has-any
Checks if an array contains at least one of the specified values.
Syntax: (has-any "field.path" (value1 value2 ... valueN))
Examples:
true if the labels array contains either “bug” OR “enhancement” (or both).
For arrays of objects:
true if any tag has id 1, 2, or 3.
Numeric Operators
gt (Greater Than)
Checks if a numeric field is greater than a value.
Syntax: (gt "field.path" number)
Example:
true if the issue has more than 10 comments.
gte (Greater Than or Equal)
Checks if a numeric field is greater than or equal to a value.
Syntax: (gte "field.path" number)
Example:
true if the PR changed 5 or more files.
lt (Less Than)
Checks if a numeric field is less than a value.
Syntax: (lt "field.path" number)
Example:
true if the issue has fewer than 3 comments.
lte (Less Than or Equal)
Checks if a numeric field is less than or equal to a value.
Syntax: (lte "field.path" number)
Example:
true if the PR added 100 lines or fewer.
Existence Operator
exists
Checks if a field exists and is not null or undefined.
Syntax: (exists "field.path")
Example:
true if the issue has a milestone assigned.
Common Patterns
Not Equal
To express “not equal”, combinenot and eq:
Has None
To check that an array contains none of the specified values:Multiple Conditions
Combine conditions withand:
Any of Multiple Conditions
Useor for alternatives:
Complete Examples
Example 1: Simple Filter
Requirement: Only process when action is “opened”Example 2: Multiple Conditions
Requirement: Process opened tickets from a specific userExample 3: Label-Based Routing
Requirement: Process issues that have either “bug” or “enhancement” labelExample 4: Complex Criteria
Requirement: Process opened PRs with more than 20 changed filesExample 5: Advanced Multi-Condition
Requirement: Process if:- Action is “opened” by alice or bob with a critical/urgent label, OR
- Action is “labeled” with both “needs-review” and “backend” labels and more than 5 comments
Important Notes
Type Safety
- All operators perform type-safe comparisons
- Comparing incompatible types returns
false - Example:
(gt "issue.title" 5)returnsfalsebecause title is a string, not a number
Non-Existent Fields
- Non-existent or null fields return
falsefor all operations - Exception:
(not (exists "field"))returnstruefor non-existent fields
Empty Lists
- Empty lists in
has-allandhas-anyreturnfalse - Example:
(has-any "labels" ())always returnsfalse
Case Sensitivity
- Field paths are case-sensitive
"issue.State"and"issue.state"are different fields
Formatting
- Whitespace and newlines are ignored
- Use indentation for readability in complex expressions
Best Practices
- Keep it simple: Use the simplest expression that meets your requirements
- Be specific: Reference the most specific fields available to avoid false positives
- Test thoroughly: Verify your filter against sample payloads before deployment
- Use wildcards: Leverage
.*notation for arrays of objects instead of complex nested logic - Document complex filters: Add comments (outside the expression) explaining the logic for complex filters
- Prefer
has-anyover multipleorstatements:(has-any "labels" ("a" "b" "c"))is cleaner than(or (has "labels" "a") (has "labels" "b") (has "labels" "c"))
Testing Your Filters
When writing filters:- Start with your JSON payload structure
- Identify the fields you need to check
- Write a simple expression and verify it works
- Incrementally add conditions
- Test edge cases (missing fields, empty arrays, etc.)