Forwarding & replay

Delivery you can audit, replay you can trust.

Capture is only half the job, the webhook usually needs to reach your system too. WebhookVault relays every capture through a durable outbox, so a webhook we have acknowledged can never lose its way to you.

What that buys you

Once we have answered the sender, the delivery is owed and cannot be dropped. Delivery starts immediately rather than on the next tick of a queue. Failures back off and retry on their own; anything that runs out of attempts parks somewhere you can see it and restart it. Pull the plug mid-burst and nothing is lost, which is the only test of durability that counts.

  • Retries start at 30 seconds and double each time; eight attempts in all, then the delivery parks in dead-letter
  • Every attempt recorded: status, duration, error, retries and replays alike
  • Rate-smoothed per destination, a 500-request replay won't stampede your server
  • SSRF-guarded targets: private and internal ranges refused at connect time
  • Transformations on Pro and Team: a declarative rule shapes what the destination receives (keep, drop, rename, redact, set headers); the stored capture never changes
sender capture + job one transaction 200 = stored wake dispatcher you retry · 30s · 1m · 2m … → dead-letter (replay restarts)

Replay: the vault's second act

Anything the vault holds can be delivered again to the endpoint's configured destination once you have fixed the bug. Replay one request inline and watch the outcome, or queue hundreds in bulk through the same audited pipeline.

  • Replays appear in the same attempt ledger as automatic retries
  • Before it sends, a replay checks what each action already received
  • Truncated-body requests refuse to replay rather than lie to your server
  • Bulk replay from the explorer or one API call
Replay via API →
A selected request's Deliveries tab: the forward action, its status code, duration and attempt number.

the attempt ledger, nothing to reconstruct, it's all written down

Safe replays

Once is enough

A webhook can reach your receiver more than once: a retry after a slow answer, a replay after a fix. Exactly-once delivery is not possible over HTTP, so the vault does the next best thing. A replay checks what each action already received before it sends, every forward is stamped so your receiver can recognise a repeat, and each action can say what a successful answer looks like.

A replay checks before it sends

Before a replay goes out, WebhookVault looks at what each chosen action already received for that request: delivered, or still being delivered. In the dashboard a short checklist names those earlier deliveries and asks before sending again; when nothing would be duplicated, it is one click. Bulk replay skips what was already delivered unless you choose to send it again.

  • Duplicate protection on each action, on by default; switch it off only for a receiver that de-duplicates on its own
  • Include in replays on each action: on for forwards, off by default for new notify and sub-request actions, so replaying an order does not post the same message to a channel twice
  • API: guard=true refuses a duplicate with 409 already_delivered; without it a replay sends as before and reports warnings
  • MCP server: refuses already_delivered, lists the earlier deliveries, and an AI agent must have your yes before sending again
  • Every replay writes one audit row: who replayed, which actions it reached, what the check found
Safe replays in the docs →
replay · guard=true409 · nothing sent
# POST …/replay?guard=true
{
  "code": "already_delivered",
  "actions": [{
    "name": "Back office",
    "state": "delivered",
    "statusCode": 200,
    "jobId": 81234
  }]
}
# allowDuplicates=true sends anyway

Your receiver can tell a repeat

Every forward carries four headers, set by the vault alone: a request id that stays the same across retries and replays of one captured request, a delivery id that is new on each replay, the attempt number within a delivery, and a flag on every delivery a replay queued. Claim the key under a unique constraint in the same transaction as the work, answer 2xx when it was already claimed, and a repeat is harmless.

  • De-duplicate on the provider's own event id when the payload has one; the request id is the same whichever way the event reached you
  • Use the delivery id when only automatic retries should count as repeats and a replay should be processed again
  • Forwards only; the signature covers the timestamp and body, not these headers. Use them to de-duplicate and trace, never to decide a request is genuine
De-duplicating in a receiver →
what your receiver sees · a replay, attempt 2
# same across retries and replays
X-WebhookVault-Request-Id: req_5120

# one per delivery, new on each replay
X-WebhookVault-Delivery-Id: 81234

# counts up on each retry
X-WebhookVault-Attempt: 2

# only on a delivery a replay queued
X-WebhookVault-Replay: true

Say what success looks like

By default any 2xx counts as delivered. Some receivers answer 200 with an error in the body, or use one specific status for success. An action can say what success looks like: the accepted status codes, such as 200, 201 or 2xx, 409, and an optional condition on the response body or headers, for example body.status equals ok. A response that misses it is a failed delivery: retried on the normal schedule, dead-lettered when retries run out, and alerted like any other failure.

  • Three plain conditions: body contains text, JSON key exists, JSON key equals value; an advanced builder over the body and headers for the rest
  • A listed non-2xx code counts as delivered, so a receiver's "already processed" 409 stops being retried
  • A check that cannot run fails rather than passes; a timeout has nothing to check and reads skipped
  • Check against the last response previews the check before you save it
  • Every attempt reports its check: passed, failed, skipped or not configured
Response checks in the docs →
action · how does this receiver say success?
# the check
status codes   2xx, 409
condition      body.status equals "ok"

# attempt 1 · 200 OK
check failed: body.status is "queued"
retry in 30s, like any failed delivery

Relay with receipts.

Forwarding is on every plan, point an endpoint at your URL and watch the ledger fill.