Skip to main content
Taberna delivers webhooks through a durable outbox and a retrying dispatcher. That makes delivery at-least-once: the same event can arrive more than once, and a failing endpoint is retried for days rather than dropped. Your handler has to be built for both. Webhooks covers setup, the ten events, the payloads and signature verification. This page is what happens after the first request.

What counts as success

A delivery succeeds only if your endpoint returns a 2xx within the request timeout — 10 seconds by default. Anything else, non-2xx or network error, is retried. Respond fast and queue the real work. A handler that does fulfilment inline is a handler that times out under load and gets replayed.

The backoff schedule

Exponential, capped:
With the defaults — base 15 s, multiplier 2, cap 8 hours, up to 20 attempts — the wait doubles from 15 seconds until it reaches the 8-hour cap after about a dozen attempts, then stays there. That gives a total horizon of roughly three days: These are server-configurable, so treat the exact numbers as current defaults rather than a contract. The shape — fast at first, then hours apart, for days — is the part to design against.

Exhaustion

After the last attempt the delivery is marked exhausted and is not retried automatically. The store’s owners are emailed, at most once per webhook per 24 hours, so a broken endpoint produces one alert rather than a flood. Once your endpoint is healthy again, replay exhausted deliveries — from the dashboard (owner, admin or developer), or through the resend endpoint.
Resending an exhausted delivery buys exactly one more attempt. The attempt counter is not reset: the delivery is pushed back into the queue at its existing count, one request is made, and if that request does not return a 2xx the delivery is immediately exhausted again. It does not restart the multi-day retry schedule.So fix the endpoint first, verify it, and only then resend. If the resend fails you are back where you started and must resend again.

At-least-once delivery

The same event can arrive more than once — an automatic retry after your endpoint was slow but actually succeeded, or a manual resend. Your handler must be idempotent.
A manual resend does not mint a new delivery id. It re-queues the same delivery row, so the resent request arrives with the same X-Taberna-Delivery-Id as the original. A handler that dedups purely on delivery id will therefore silently drop every resend — exactly the deliveries a merchant is replaying because something went wrong.
Idempotency has to live in your business state. Before acting, ask whether the state change has already been applied — is this order already fulfilled? Look up data.id, or your own data.extraContext.purchaseId / data.metadata.purchaseId, and no-op if the work is done. That check is correct whether the duplicate came from a retry, a resend, or two workers racing. X-Taberna-Delivery-Id is still worth recording — for correlation with the dashboard, and as a cheap short-circuit for automatic retries piling onto an expensive handler — but it is not sufficient on its own. A handler that records the id and then crashes before applying the change has poisoned itself: the resend that would have fixed it gets skipped as a duplicate.

An idempotent handler

Verify, parse, then apply the change against your own state — never gate on the delivery id.
Best done in one transaction, or with a unique constraint on purchase.id in whatever table records the fulfilment, so two concurrent deliveries cannot both pass the fulfilledAt check.

List deliveries

GET /v1/webhook-deliveries — requires the webhooks:read scope. Newest first.
200 OK
lastResponseStatus is the HTTP status your endpoint last returned, or null if the attempt failed at the network level. Exactly one of orderId / invoiceId is set. A store with no webhook configured returns an empty page, not an error.

Resend a delivery

POST /v1/webhook-deliveries/{deliveryId}/resend — requires the webhooks:write scope. Returns 204 with no body.
webhooks:write covers replay only. It does not permit changing where a store’s events are sent — repointing the webhook URL stays a dashboard action, deliberately.

Current limitations

One webhook URL per store. Every event for the store goes to that single endpoint. If you need to fan out to several consumers, receive once and dispatch yourself. No per-event subscription filtering. You cannot subscribe to order.completed alone; you receive all ten event types and filter on event in your handler. Both are listed in What Taberna does not do.