Skip to content

Delivery and retries

When an event matches your subscription, BankCore writes one delivery row and a poller picks it up. The poller is the only thing that sends webhooks, so its cadence is the floor on latency: expect the first attempt within about ten seconds of the event, longer when the platform is paying out a large batch.

Phase Limit
TCP connect 5 seconds
Waiting for your response 10 seconds

A slow endpoint is a failed endpoint. Verify the signature, write the event somewhere durable, answer 2xx, and do the work afterwards.

You answer We record What happens next
Any 2xx DELIVERED Nothing more.
Any 4xx FAILED on that attempt Never retried. The same bytes would get the same answer. Does not count toward auto-disable.
Any 5xx RETRYING Back on the schedule below.
No answer at all (timeout, connection refused, DNS failure) RETRYING Back on the schedule below.

The 4xx rule is deliberate: every event fans out to every subscriber, and a subscriber rejecting a type it does not want should not burn retries. Its consequence is that a signature or freshness check that answers 401 drops that event permanently. Answer 5xx when the failure might be on your side.

Each delivery is attempted up to 10 times. After the first failure the next attempt is 5 minutes later, then 30 minutes, 2 hours, 6 hours, 12 hours, and then 24 hours between each remaining attempt. The whole ladder spans a little under five days. Each attempt is signed afresh; X-Webhook-Id does not change.

When the tenth attempt fails the delivery is marked FAILED and the subscription’s consecutive-failure count goes up by one. A single success resets that count to zero.

Status Meaning
PENDING Queued, never attempted. Served before the retry backlog.
RETRYING Attempted, failed with a 5xx or no answer, waiting for its next slot.
DELIVERED You answered 2xx. Terminal.
FAILED You answered 4xx, or ten attempts were used up. Terminal unless retried.
CANCELLED Never sent on purpose: the subscription was disabled, or the event type was removed from it, after the event had been queued. Not retryable from the portal.
DELIVERING Reserved. The sender claims rows with a database lock rather than this status, so you will not normally see it.

Delivery rows in a terminal state are kept for 90 days and then purged.

Ordering is not guaranteed. Retries interleave with fresh events, and one subscription’s work is spread across a small number of parallel lanes, so two events for the same account can arrive out of order or at the same moment. Order by the payload’s own fields (reference, timestamp), not by arrival.

Duplicates do happen: a 2xx that was sent but lost on the wire is a retry on our side, and a replay or resend is a second delivery of the same event by design. Consume idempotently:

  1. Treat X-Webhook-Id as the key for retries of the same delivery. It is identical on every attempt.

  2. Treat the body’s eventId (and for transactions, reference) as the key across replays. A replayed or resent event gets a new X-Webhook-Id of the form replay-<uuid> or resend-<uuid>, but the body is byte-for-byte the original, so eventId and reference are unchanged.

  3. Claim the key in your store before you act, inside a unique constraint, and answer 2xx on a duplicate without doing the work again.

Three paths, from narrowest to widest.

Retry one delivery yourself. A delivery in FAILED can be re-queued from the portal. It resets the attempt counter and goes out on the next poll with the same X-Webhook-Id. Only Administrator and Operations roles can do it, and each subscription is limited to 20 retries per hour; a bigger backlog is a replay, below.

POST/api/portal/webhooks/deliveries/{deliveryId}/retry

This is a portal-session call (the button on a delivery’s page), not a partner-API call, and takes no body. It answers 202 Accepted; the message has been queued, not yet sent. Refusals use the portal’s error shape:

errorCode When
PORTAL_RETRY_NOT_PERMITTED Your portal role can read deliveries but not re-send.
PORTAL_WEBHOOK_NOT_RETRYABLE The delivery is not FAILED. Pending and retrying rows are already on their way; delivered ones have arrived; cancelled ones need the bank.
PORTAL_WEBHOOK_RETRY_LIMIT More than 20 retries on this subscription in the last hour.
PORTAL_WEBHOOK_DELIVERY_NOT_FOUND Not a delivery on one of your subscriptions.

Replay a window. The bank can replay every event delivered to your subscription between two instants, optionally narrowed to certain event types. Each replayed event is a fresh delivery (replay-<uuid>) carrying the original payload, flagged as a replay in the portal. Replay works only on an enabled subscription and is how you recover events that were dropped with a 4xx, or lost while your secret was being rotated.

Resend one event. The bank can also resend a single delivery regardless of its state. At the time of writing that tool targets the bank’s own channel subscriptions, not partner subscriptions; for a partner, ask for a replay of the window instead.

Webhook endpoints lists your subscriptions with their status and disabled reason, if any. Opening one shows Deliveries for a date window, filterable by status and event type, with the status, HTTP answer and attempt count per row. A delivery’s page shows What happened, The message that was sent, What your server said, and for a failed one a Re-send button.