Skip to content

What happens when a webhook arrives

When an external system POSTs to a Webhook trigger’s URL, Routario does two things in order: it writes the delivery down, then runs the flow. Those used to be the same step — the flow ran inline, and the sender waited for it to finish before getting a response. Now the response comes back as soon as the delivery is safely recorded, and the flow runs just after.

Recording the delivery before running anything means the two can no longer fail together:

  • A restart or a busy moment delays the run — it never loses the webhook. Once Routario has answered, the delivery is on disk. If the instance is mid-restart or the flow simply hasn’t gotten to it yet, the webhook still runs; it just runs a little later.
  • The sender’s own timeout stops mattering. Some senders give you only a few seconds to respond. Because the response comes back the moment the delivery is recorded — not when the flow finishes — a flow that takes a minute is no longer racing the sender’s patience.
  • A retry with the same idempotency key never runs the flow twice. Most senders retry automatically if they don’t get a fast, clean response. Because Routario can tell “I’ve seen this exact delivery before” from “this is new,” a retry is safely turned away rather than triggering a second run.

Sending a webhook gets you one of a few responses:

  • Accepted. The ordinary case — the delivery is recorded and Routario has answered before the flow runs:

    { "accepted": true, "delivery_id": "...", "status": "pending" }
  • Duplicate. Routario has already seen this delivery — nothing runs again:

    { "accepted": true, "duplicate": true, "delivery_id": "...", "status": "pending" }
  • The run’s result, if the sender asked to wait (see below) — a run_id and a status like COMPLETED.

A Webhook trigger can be paused — by hand, or automatically after a storm (below). Pausing doesn’t change what a sender sees: the delivery is still accepted and written down exactly as usual. It just doesn’t run yet:

{ "accepted": true, "delivery_id": "...", "status": "pending", "held": "trigger paused" }

Held deliveries wait in line. Resume the trigger and they run, oldest first — nothing is lost by pausing. That’s the whole point of writing a delivery down before running it: a pause delays the flow, it doesn’t drop the webhook.

A webhook trigger has a setting for what to do when more than one delivery about the same thing is waiting at once — Run every one, or Run only the latest per key.

Run every one is the default: every delivery runs, in the order it arrived, no matter how many pile up. That’s right when each delivery is its own event — an order placed, a payment received — and none of them make an earlier one pointless.

Run only the latest per key is for the opposite case: something that gets updated several times before the flow catches up, where only the current state matters. Turn it on and set a coalesce key — a path into the payload that names the thing being reported, e.g. ticket.id. When several waiting deliveries share the same value there, only the newest one runs; the rest are skipped and recorded as coalesced, not lost — they’re just not going to run.

For example: a support ticket gets updated five times in the minute before your flow gets to it. With the coalesce key set to ticket.id, the flow runs once, with the fifth (most recent) payload — the four before it are marked coalesced instead of queuing four redundant runs.

Both fields, with more examples, are documented on the Webhook trigger reference page.

A trigger can also be given a storm cap — the most deliveries it will let pile up waiting before something is clearly wrong. A sender stuck in a retry loop, or a bulk replay from the other side, could otherwise queue up thousands of runs nobody asked for.

Past the cap, the trigger pauses itself and notifies the flow’s owner — a message naming how many deliveries are waiting and why. It keeps accepting deliveries the whole time; it only stops running them, exactly like any other pause above. From there, resume the trigger once you’ve confirmed the burst is safe to work through, and the held deliveries run in order.

The cap is set on the trigger as max_waiting — see the Webhook trigger reference page.

The Job’s Observe page shows the queue depth right next to every webhook-triggered flow that’s part of a Job: how many deliveries are waiting, how long the oldest one has been waiting, and how many have died after exhausting their retries (what the retries section below calls being parked). A trigger that’s paused or mid-storm is flagged there too, so you can tell something needs attention without opening the flow itself.

Opening the trigger on the flow’s canvas shows a Deliveries block with the same numbers — waiting, oldest, dead — plus the most recent dead deliveries with the reason they stopped, and two actions:

  • Discard waiting throws away everything that is queued, after confirming the count. Use it when a storm turns out to be junk — nothing in that queue will run.
  • Re-queue dead puts the parked deliveries back in line with a fresh set of retries, for when the cause (a connector down, a bad credential) has been fixed.

The armed/paused control above that block is the one place to resume, whether the trigger was paused by hand or by a storm; resuming also wakes anything that was held.

Once a delivery is recorded, Routario tries to run its flow. If that attempt fails, it doesn’t give up right away — it retries a handful of times, waiting a bit longer between each attempt. A delivery whose flow keeps failing after every retry is parked rather than retried forever, and that outcome is recorded as an event — visible on the Logs page under the Ingest lens (see See what happened — the Logs page).

This is a different layer from a flow run failing and being retried, which Keeping your automations healthy covers — this is about getting the flow to start running at all.

Most integrations don’t need to know how a flow turned out — they just need Routario to have it. For the ones that do, sending X-Routario-Wait: 1 (or adding ?wait=1 to the URL) tells Routario to still record the delivery first, but then run the flow before answering, and hand back the run’s result in the same response. This is the older, fully synchronous behaviour, kept for exactly this case — reach for it only when the caller genuinely can’t proceed without the outcome, since it means waiting for the whole flow to finish.

Most webhook senders — Zendesk, Resend, Slack, Meta/WhatsApp, Microsoft Graph, and similar platforms — already retry deliveries on their own when they don’t get a fast response, so a webhook trigger’s normal behaviour suits them without any extra setup. If you’re sending from a system you control, send an Idempotency-Key with each delivery (and reuse the same key on any retry) so your own retries are safe too.