Filter webhooks payload

Learn how to write custom payload-filters for your triggers.

For custom webhooks, Dust cannot generate a filter automatically for triggers based on your natural language description. To filter the incoming calls, you'll need to write a filter using a custom syntax.

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.

Webhook Filter Expression Language

Overview

The Webhook Filter Expression is a declarative language for filtering webhook payloads based on their JSON content. Filter expressions evaluate to either true (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: true or false (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 the name property from each object in the tags array

Example payload:

{
  "action": "opened",
  "issue": {
    "number": 42,
    "state": "open",
    "labels": ["bug", "critical"]
  },
  "tags": [
    {"name": "bug", "id": 1},
    {"name": "feature", "id": 2}
  ]
}

Field references:

  • "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:

(and
  (eq "action" "opened")
  (eq "issue.state" "open"))

Returns 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:

(or
  (eq "action" "opened")
  (eq "action" "edited"))

Returns true if action is either "opened" OR "edited".


not

Returns the negation of its single sub-expression.

Syntax: (not expr)

Example:

(not (eq "issue.state" "closed"))

Returns 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:

(eq "action" "opened")           ; String comparison
(eq "issue.number" 42)           ; Numeric comparison
(eq "issue.locked" true)         ; Boolean comparison

String Operators

starts-with

Checks if a string field starts with a given prefix.

Syntax: (starts-with "field.path" "prefix")

Example:

(starts-with "pull_request.head.ref" "feature/")

Returns 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:

(has "issue.labels" "bug")

Returns true if the labels array contains "bug".

For arrays of objects, use wildcard paths:

(has "tags.*.name" "important")

Returns 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:

(has-all "issue.labels" ("bug" "critical"))

Returns true only if the labels array contains both "bug" AND "critical".

For arrays of objects:

(has-all "tags.*.name" ("bug" "feature"))

Returns 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:

(has-any "issue.labels" ("bug" "enhancement"))

Returns true if the labels array contains either "bug" OR "enhancement" (or both).

For arrays of objects:

(has-any "tags.*.id" (1 2 3))

Returns 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:

(gt "issue.comments" 10)

Returns 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:

(gte "pull_request.changed_files" 5)

Returns 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:

(lt "issue.comments" 3)

Returns 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:

(lte "pull_request.additions" 100)

Returns 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:

(exists "issue.milestone")

Returns true if the issue has a milestone assigned.


Common Patterns

Not Equal

To express "not equal", combine not and eq:

(not (eq "issue.state" "closed"))

Has None

To check that an array contains none of the specified values:

(not (has-any "issue.labels" ("wontfix" "duplicate")))

Multiple Conditions

Combine conditions with and:

(and
  (eq "action" "opened")
  (eq "sender.login" "alice")
  (has "issue.labels" "urgent"))

Any of Multiple Conditions

Use or for alternatives:

(or
  (eq "sender.login" "alice")
  (eq "sender.login" "bob")
  (eq "sender.login" "charlie"))

Complete Examples

Example 1: Simple Filter

Requirement: Only process when action is "opened"

(eq "action" "opened")

Example 2: Multiple Conditions

Requirement: Process opened tickets from a specific user

(and
  (eq "action" "opened")
  (eq "sender.login" "alice"))

Example 3: Label-Based Routing

Requirement: Process issues that have either "bug" or "enhancement" label

(has-any "issue.labels" ("bug" "enhancement"))

Example 4: Complex Criteria

Requirement: Process opened PRs with more than 20 changed files

(and
  (eq "action" "opened")
  (gt "pull_request.changed_files" 20))

Example 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
(or
  (and
    (eq "action" "opened")
    (or
      (eq "sender.login" "alice")
      (eq "sender.login" "bob"))
    (has-any "issue.labels" ("critical" "urgent")))
  (and
    (eq "action" "labeled")
    (has-all "issue.labels" ("needs-review" "backend"))
    (gt "issue.comments" 5)))

Important Notes

Type Safety

  • All operators perform type-safe comparisons
  • Comparing incompatible types returns false
  • Example: (gt "issue.title" 5) returns false because title is a string, not a number

Non-Existent Fields

  • Non-existent or null fields return false for all operations
  • Exception: (not (exists "field")) returns true for non-existent fields

Empty Lists

  • Empty lists in has-all and has-any return false
  • Example: (has-any "labels" ()) always returns false

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

  1. Keep it simple: Use the simplest expression that meets your requirements
  2. Be specific: Reference the most specific fields available to avoid false positives
  3. Test thoroughly: Verify your filter against sample payloads before deployment
  4. Use wildcards: Leverage .* notation for arrays of objects instead of complex nested logic
  5. Document complex filters: Add comments (outside the expression) explaining the logic for complex filters
  6. Prefer has-any over multiple or statements: (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:

  1. Start with your JSON payload structure
  2. Identify the fields you need to check
  3. Write a simple expression and verify it works
  4. Incrementally add conditions
  5. Test edge cases (missing fields, empty arrays, etc.)

Example workflow:

Payload: {"action": "opened", "issue": {"labels": ["bug"]}}

Step 1: (eq "action" "opened")  ✓
Step 2: (has "issue.labels" "bug")  ✓
Step 3: (and (eq "action" "opened") (has "issue.labels" "bug"))  ✓