Delivery and retries
Once a webhook is configured, Straumur delivers every matching event to your endpoint as an
HTTP POST with a JSON body. This page describes how that delivery behaves: what we send, what
we expect back, and what happens when your endpoint does not answer.
The request we send
Each delivery is a POST to the URL registered on the webhook, with the event as a JSON body.
POST /your/webhook HTTP/1.1
Content-Type: application/json; charset=utf-8
Authorization: 5c1f…the webhook API key
X-Webhook-CorrelationId: 8f3c1b2e-4a91-4d5e-9b17-2c6f0a3d8e42
{ "checkoutReference": "…", "payfacReference": "…", "hmacSignature": "…", … }
| Header | Description |
|---|---|
| Authorization | The webhook API key issued when the webhook was registered, sent as the raw value — there is no Bearer prefix. Compare it against the key you stored. |
| X-Webhook-CorrelationId | A unique identifier for this delivery attempt. A retry of the same event carries a new value. Log it — it is what we use to trace a specific delivery for you. |
| Content-Type | Always application/json; charset=utf-8. |
The Authorization header authenticates the caller — it tells you the request really came from
Straumur. It is not the secret used to verify the hmacSignature field in the body; that is
the separate HMAC key, also issued when the webhook was registered. Validate both.
Responding
Your endpoint must answer with HTTP 200 within 15 seconds.
200 is the only response we treat as success. Any other status code — including 201, 202
and 204 — is recorded as a failed attempt and starts the retry sequence, as are timeouts,
connection errors and TLS failures.
The 15-second limit covers your entire response, not just the connection. If your handler does real work before replying — writing to a database, calling another service, sending an email — that work counts against the budget. Acknowledge the event first and process it asynchronously.
Retry schedule
If an attempt does not return 200, we retry on a fixed, increasing schedule. There are five
attempts in total.
| Attempt | Sent | Elapsed since the event |
|---|---|---|
| 1 | Immediately | — |
| 2 | + 1 minute | 1 minute |
| 3 | + 10 minutes | 11 minutes |
| 4 | + 1 hour | ~1 hour |
| 5 | + 6 hours | ~7 hours |
After the fifth attempt fails, the delivery is marked as failed and is not retried again. The whole sequence spans a little over seven hours, which is the window you have to restore a broken endpoint before events are lost.
Every attempt is recorded, including the response status code and body we received. If you
believe an event was never delivered, contact us with the X-Webhook-CorrelationId — or the
payfacReference from the event — and we can tell you what your endpoint returned.
What to build for
Expect the same event more than once
A retry delivers the same event again, and your endpoint may have processed the earlier attempt successfully before timing out. Make your handler idempotent: key on the identifiers in the event rather than assuming each delivery is new.
Duplicates also arise legitimately. If two of your webhooks both cover the same contract and both subscribe to the event type, each one receives its own delivery. Partners see this too — a payment on a merchant contract can reach both the merchant's own webhook and the partner webhook whose agreement that contract is linked to.
Do not depend on ordering
Deliveries are independent jobs, and a retried event can arrive after a later one. Treat each event on its own merits rather than assuming it follows the previous one.
Answer first, work later
The pattern that survives load is: validate the Authorization header, verify the
hmacSignature, put the event on your own queue, return 200. Everything else happens after
the response.
Checklist
- Endpoint reachable over
https, on a complete absolute URL - Returns
200— and nothing else — well inside 15 seconds - Validates the
Authorizationheader against the stored webhook API key - Recomputes and compares
hmacSignatureusing the HMAC key - Handles duplicate deliveries idempotently
- Logs
X-Webhook-CorrelationIdwith every request