# Webhooks for Delivery Status: Design Guide | ZafeConnect

> How to build a webhook endpoint for message status: acknowledge fast, handle duplicates and out-of-order events, verify signatures and reconcile gaps.

Canonical URL: https://www.zafeconnect.com/blog/webhooks-for-delivery-status
Language: en
Last modified: 2026-11-09

## Webhooks for delivery status: design for retries and duplicates

URL: https://www.zafeconnect.com/blog/webhooks-for-delivery-status

Published: 2026-11-09

A webhook pushes each status change to your system as it happens. It is simple to start and easy to get subtly wrong, because the same event can arrive twice or in the wrong order.

Key takeaways:
- Acknowledge with a 2xx response quickly and do the work afterwards, in a queue.
- Expect duplicates and out-of-order events; make processing idempotent.
- Verify the signature over the exact bytes received, and reject old timestamps.
- Reconcile: look up messages that never reach a final status.

### Why webhooks beat polling
Asking the API for the status of every message wastes calls and adds delay. With a webhook the provider calls you when a status changes, so your system can react in seconds: show a code as delivered, trigger a fallback or update an order.

### What can go wrong

- Duplicates: delivery is usually at least once, so the same event may arrive more than once
- Order: a later status can arrive before an earlier one
- Delay: a status can arrive minutes or hours after sending, or not at all
- Downtime: if your endpoint is down, events are retried for a limited time and then dropped

### Design rules for the endpoint

- Return a 2xx status as soon as the request is stored; do the processing in a queue
- Key every update on the message ID and the status, so a repeat changes nothing
- Never move a message backwards: once it is delivered or failed, ignore an earlier status
- Keep the raw payload for a while for debugging
- Make the endpoint cheap and fast; a slow endpoint looks like a failing one

### Verify what you receive
Anyone can call a public URL, so check that the request came from your provider. A common method is a signature: the sender computes a keyed hash over a timestamp and the raw body, and sends it in a header. You compute the same value with your secret and compare.

Compute it over the exact bytes received, not a re-serialised copy, compare in constant time, and reject timestamps that are too old so a captured request cannot be replayed.

### Reconcile the gaps
Run a job that looks for messages that have had no final status for longer than your normal delay, and ask the API for their status. It catches events that were lost and shows you whether a country or route is quiet.

### How ZafeConnect fits
ZafeConnect sends a POST to your webhook URL when a message status changes, with the headers X-ZafeConnect-Event, X-ZafeConnect-Timestamp and X-ZafeConnect-Signature. The signature is a hex HMAC-SHA256 over the timestamp, a dot and the raw body. Return any 2xx to acknowledge; ZafeConnect retries up to 5 times if your endpoint does not. The developer documentation has the details.

FAQ:
Q: Can I receive the same event twice?
A: Yes. Design for it by keying updates on the message ID and the status.

Q: How fast must my endpoint respond?
A: Quickly. Store the event, return a 2xx and process it afterwards. A slow or failing endpoint triggers retries.

Q: What if my endpoint was down?
A: Events are retried for a limited time. Use a reconciliation job to look up messages that never reached a final status.
