# Filters and transformations

Deliver only the events a gateway cares about, and reshape headers and JSON bodies before they are forwarded.

Source: https://docs.webhooker.eu/deliver/filters-and-transformations/

Both are per gateway and both live in the same editor: **Destinations** tab →
**Edit routing** on the row.

## Filter rules

A rule addresses one value in the request, compares it, and the group decides
how the rules combine.

```json
{
  "operator": "and",
  "rules": [
    { "path": "body.type", "op": "eq", "value": "invoice.paid" },
    { "path": "body.data.object.amount", "op": "gt", "value": 5000 }
  ]
}
```

### Paths

A path starts with one of two roots and then walks the JSON:

| Root | Reads |
| --- | --- |
| `body.` | The payload, when it is JSON. |
| `headers.` | The inbound headers. Names are lowercase. |

`body.data.object.id`, `headers.x-github-event`. A path with any other root
never matches.

### Operators

| Operator | True when |
| --- | --- |
| `eq` | The value equals the rule's value. |
| `ne` | It differs, **or the path is absent**. |
| `contains` | A string contains the substring, or an array contains the value. |
| `exists` | The path resolves to anything, including `null`. |
| `gt` / `lt` | Both sides are numbers and compare that way. |
| `in` | The value is one of the array in the rule. |

### Groups

The top level has an `operator` of `and` or `or`. A rule entry can itself be a
group, giving one level of nesting — enough for "A and (B or C)":

```json
{
  "operator": "and",
  "rules": [
    { "path": "headers.x-github-event", "op": "eq", "value": "push" },
    {
      "operator": "or",
      "rules": [
        { "path": "body.ref", "op": "eq", "value": "refs/heads/main" },
        { "path": "body.ref", "op": "eq", "value": "refs/heads/release" }
      ]
    }
  ]
}
```

Nesting deeper is rejected when you save.

### What a filtered event looks like

A non-matching event is stored, and its delivery for that gateway is recorded as
`filtered`. It is visible in the event log, was never sent, and is not retried.
Other gateways on the same source are unaffected.

An empty filter matches everything. A payload that is not valid JSON makes every
`body.` rule fail to match, so filter on headers when you expect non-JSON
bodies.

![Filter rules on a connection](https://docs.webhooker.eu/screenshots/connection-editor-light.webp "A filter with two rules joined by AND: only large successful payments reach this destination.")

## Transformations

Applied after the filter and before the authentication headers.

### Headers

Transformations operate on the headers Webhooker is about to send — the content
type copied from the event, plus the destination's fixed headers — not on the
sender's own headers, which are not forwarded (see
[HTTP headers](/reference/http-headers/)).

- **Set headers** — add a header, or overwrite it if it already exists.
- **Rename headers** — change the name of a header already in the outbound set,
  keeping its value.
- **Remove headers** — drop a header from the outbound set, for example the
  content type or a fixed header that one endpoint must not receive.

Header names are matched case-insensitively. Set values support placeholders:

| Placeholder | Becomes |
| --- | --- |
| `{{event.id}}` | The event id. |
| `{{source.name}}` | The source's name. |
| `{{timestamp}}` | The delivery's Unix timestamp. |

### Body

**Merge body fields** takes a JSON object and merges it into the top level of a
JSON payload. Existing keys with the same name are overwritten, everything else
is kept, and values support the same placeholders.

```json
{ "received_via": "webhooker", "trace_id": "{{event.id}}" }
```

A body that is not valid JSON is forwarded byte for byte; header rules still
apply.

### Order of operations

1. Base headers: content type, then the destination's fixed headers.
2. Transformation: renames, removals, then sets.
3. Body merge.
4. Authentication headers.

Because authentication comes last, a transformation cannot overwrite or remove
the signature headers — so a broken transformation can never quietly turn a
signed delivery into an unsigned one.
