features
August 10, 2026

A webhook doesn't know which events you actually want. Stripe sends every payment event, not just the failed ones you want to page someone about. A form tool sends every submission, including the test entries your own team generates while checking that the form still works. If you want only a slice of that traffic to reach a destination, something has to make that decision, and by default that something is code you write after the fact, inside whatever system received the noise.
ProxyHook filters make that decision before the event gets that far. A filter matches on a condition, and anything matching is excluded, it doesn't continue through the Automation. The eleven available filter types cover more ground than "drop test events," and where you attach a filter changes what it protects.
A filter can live in two places, and they answer different questions.
On a Source, a filter applies before the event is available to any Automation built on that Source. If you have one Webhook Source feeding three separate Automations, a Source-level filter narrows what all three ever see. This is the right place for exclusions that are true regardless of destination, like dropping requests that don't come from your form tool's expected IP range, or that hit the endpoint with a request method you never expect to receive.
As a step inside an Automation, a filter applies only to that one path from source to destinations. The same Source can feed an Automation to Postgres with no filter at all (you want the full history archived) and a separate Automation to Slack with a Payload Contents filter (you only want to be paged for a subset). The Automation builder puts filters in the same place as transformations, as a step you drop between the source and your destinations, so you can combine a filter with a transformation on the same path.
Getting this placement wrong shows up as either "why isn't Postgres getting everything" (a filter that should have been Automation-scoped got attached to the Source instead) or "why is Slack still noisy" (a filter that should have been on the Source, to protect every downstream Automation, only got added to one of them).
The filter list groups into three practical categories, and knowing which category a problem falls into tells you which filter to reach for.
Geographic: City, State, Country, Postal/Zip Code, IP. These match on where the request physically came from or claims to come from. A Country filter set to exclude everything outside the markets you operate in keeps foreign scanning traffic and bot noise out of a destination that only cares about real customer activity. An IP filter is the direct version of the same idea: exclude your own office or CI network's IP range so load tests and manual QA hits on a Source endpoint never show up in a production Automation's destination.
Request metadata: Host, User Agent, Referer, Cookies, Request Method, Request URL. These match on how the request arrived rather than what's inside it. A Request Method filter that only allows POST rejects anything else outright before it reaches a destination expecting a specific shape. A User Agent filter is the practical way to drop uptime monitors and synthetic health checks that hit a webhook endpoint on a schedule, they have a recognizable, consistent user agent string, and once you've identified it once you can exclude it permanently. A Referer filter is useful when a Webhook Source is meant to only ever receive requests originating from your own app's domain, anything else is either misconfigured or unwanted.
Payload Contents. This is the one that reaches inside the event body instead of just its envelope. It matches on key values in the payload itself, which means the filter condition is defined by whatever the sending service actually puts in its JSON, not by ProxyHook. A payload filter set to exclude events where a status or environment key equals "test" keeps sandbox traffic out of a production destination without touching the sending service's configuration. This is the highest-leverage filter type because most of what you actually want to exclude (a specific event subtype, a specific status, a specific customer segment) lives in the body, not in the request metadata around it.
Concretely, building a filtered path looks like this:
A Payload Contents rule, expressed as the condition you'd set in the builder, looks roughly like this:
filter:
type: payload_contents
key: environment
operator: equals
value: "test"
action: exclude
Anything matching that condition stops there. It never reaches the destination, and it never counts against that destination's delivery volume.
Once a filter is live, the Automation's Logs view captures every event that flowed through, and a filtered-out event still shows up there, marked as excluded rather than delivered. That matters when you're first setting a filter up: a Country filter that's too broad, or a Payload Contents match on the wrong key name, will silently drop events you meant to keep, and the only way to catch that before it becomes a support ticket is to check Logs against what you expected to see excluded versus what actually got excluded.
The safest way to introduce a new filter on a live Automation is to add it, then check Logs against a real traffic window before trusting it unattended. A Country filter that's supposed to keep only US traffic but has a typo in the country code, or a Payload Contents filter checking a key that got renamed upstream, both fail the same way: silently, by excluding either everything or nothing, and you won't notice until a destination's volume looks wrong. Two attachment points and eleven filter types give you a lot of ways to be precise about what continues through an Automation. Precision is only useful once you've confirmed the filter is catching what you think it's catching.