Guides
Receive and process webhooks
Verify, deduplicate, and fulfil a checkout.paid event.
Who this is for
Developers implementing a production webhook receiver, beyond the basic signature check covered in verify webhook signatures.
Before you begin
- A working signature-verification function.
- A database table (or equivalent) to record processed delivery IDs and order-fulfilment state.
Steps
- Capture the raw request body.
- Validate the timestamp window.
- Compute
v1=HMAC_SHA256(secret, timestamp + "." + rawBody). - Compare signatures with a timing-safe function.
- Reject a delivery whose ID has already been processed.
- Parse the JSON body and confirm
event === "checkout.paid". - Look up your order using the stored checkout reference.
- Commit a unique fulfilment transition, then return a
2xxresponse.
// Pseudocode for the database boundary.
await db.transaction(async (tx) => {
const inserted = await tx.insertProcessedWebhookIfAbsent(deliveryId);
if (!inserted) return;
await tx.markOrderPaidOnce({ orderId, checkoutRef, txHash });
});Expected result
Return a 2xx status only after the event is durably recorded. If your endpoint returns a failure, Outpay retries according to its own retry schedule.
Common problems
| Problem | Likely cause | Next step |
|---|---|---|
| Endpoint gets auto-disabled | Three consecutive full retry cycles all failed. | Fix the underlying failure, then re-save the endpoint in Developers to re-enable it and rotate the secret. |
| Order fulfilled twice | The delivery-ID deduplication check runs after the fulfilment side effect, not before it. | Insert the processed-delivery record and check for fulfilment inside the same transaction, as shown above. |
Related documentation
- Verify webhook signatures — the signature check this guide builds on.
- Events — the exact payload shape referenced above.
- Idempotency — the equivalent pattern for checkout creation.