Event Types
How a request is classified and how handlers are matched against it.
One trigger URL usually carries more than one kind of event. Event types are how a single
URL routes order.created to one workflow and order.refunded to another.
There are two halves, and they are configured in different places:
- The event type path is on the trigger. It is an expression that turns a request into a string.
- The event type is on each handler. It is the literal string that handler responds to.
Classifying the request
The trigger's event type path is a LiquidJS template with ${ } delimiters, rendered
against the request:
${body.event}
${body.data.object.status}
${parameters.query.kind}
${parameters.path.0}Given the path ${body.event} and this request:
{ "event": "order.created", "order": { "id": "A-1001" } }the request is classified as order.created.
The variables available are the same ones handlers use, and are described in
Handlers: body is the parsed request
body, parameters holds the trigger code, query string and any trailing path segments.
Matching handlers
The comparison is exact string equality. No prefixes, no wildcards, no case folding.
order.created does not match order.*, Order.Created or order.created .
Beyond that there is one rule that decides everything, and it is not symmetric:
Said plainly:
- The request has no event type — because the trigger has no event type path, or the path rendered empty. The event type is ignored entirely and every handler on the trigger runs, including handlers that specify an event type.
- The request has an event type. Only handlers whose event type matches it exactly run. A handler with no event type never runs.
The second half is the one that catches people. A handler with an empty event type is not a catch-all. On a trigger that classifies, it is the opposite: it can never match, because matching requires equality with a non-empty string.
So the two configurations that work are:
| Trigger | Handlers | Result |
|---|---|---|
| No event type path | Event type empty | Every request runs every handler. Right for a single-purpose URL. |
| Event type path set | Each handler names its event type | Each request runs the handlers for its type. |
And the two that quietly do nothing:
| Trigger | Handlers | What happens |
|---|---|---|
| Event type path set | Event type left empty | The handler never runs. Requests record no_handlers and answer 404. |
| No event type path | Event type set | Every request runs the handler regardless of its type, because nothing is classifying. |
When the path renders empty
A LiquidJS template that references a missing field renders an empty string rather than failing. An empty result counts as "no event type", which means the fallback is "run everything", not "run nothing".
A typo in the path, or a provider that changes its payload shape, therefore does not break loudly. It makes every handler on the trigger run for every request.
${body.evnt} → "" → every handler runs
${body.event} → "order.created" → only order.created handlers runIf a handler starts firing on events it should not, check the event type path on the monitor page against a recorded request before suspecting the handler.
Choosing a path for common providers
The field that carries the event name varies. Capture one real request with the inspect endpoint and read it off the recorded body rather than guessing.
${body.event} # a flat "event": "order.created"
${body.type} # Stripe-style
${body.eventType}
${body.data.object.status} # nested under an envelope
${parameters.query.kind} # ?kind=order.created
${parameters.path.0} # /api/trigger/CODE/order.createdPath segments are keyed by position, so /api/trigger/CODE/orders/42 gives
parameters.path.0 of orders and parameters.path.1 of 42.
Verifying before you rely on it
The handler dialog shows the classified event type for a recorded request, computed with the trigger's current path. Select a captured request while editing the handler and check the value matches what you typed in Event Type, character for character. That takes a second and removes the whole class of "my webhook does nothing" problems.