Webhook best practices
How to build a receiver that stays correct and healthy in production.
Acknowledge fast, process later
Section titled “Acknowledge fast, process later”Your endpoint must return 2xx well within the 10-second delivery timeout. The
reliable pattern:
- Validate the request minimally (parse JSON, check
X-UCS-Event). - Persist the event to a queue or table.
- Return
200 OK. - Process asynchronously from the queue.
flowchart LR
UCS["Unexus"] -- "POST event" --> EP["Your endpoint"]
EP -- "1: persist" --> Q[("Queue / table")]
EP -- "2: 200 OK (fast)" --> UCS
Q -- "3: process async" --> W["Worker<br/>(CRM lookups, DB writes, APIs)"]
Doing CRM lookups, database writes, or downstream API calls inline is the most common cause of webhook timeouts — and every timeout now costs you a duplicate, because Unexus retries it.
Design for at-least-once delivery
Section titled “Design for at-least-once delivery”Unexus retries failed deliveries on a fixed schedule (see the delivery semantics). Consequences for your design:
- Idempotency is mandatory. A timed-out attempt may have reached you; the
retry will reach you again. Store the
deliveryIdof everything you have processed and skip repeats (see below). - Answer with the right status. Return
2xxas soon as the event is safely stored. Return5xx(or let the request time out) only when you genuinely want the delivery again later. Return4xxfor requests you will never be able to process — those are parked, not retried, so they do not clog your endpoint with repeats. - An outage is bridged, not skipped. While your endpoint is down, deliveries wait in the retry schedule (about 12 h 36 min by default) and arrive when it is back. Beyond that window they are parked on the Unexus side; if completeness matters (e.g. reporting), still reconcile current state via the REST API after a long outage.
- Treat webhooks as a real-time signal, not as the system of record.
Be idempotent
Section titled “Be idempotent”Use the deliveryId (also in the X-UCS-Delivery header) as an idempotency key
and skip events you have already processed. It is identical on every retry of
the same delivery. X-UCS-Attempt tells you whether a request is a retry, which
is useful for diagnostics but not for deduplication — dedupe on the id.
Don’t rely on ordering
Section titled “Don’t rely on ordering”Events can arrive out of order, and retries make it more likely: a retried
agent.status may arrive after a newer status for the same agent was already
delivered. Compare occurredAt against the timestamp of the state you already
hold, and ignore events older than your current state — a common technique is
“last-write-wins by occurredAt per uri”.
Tolerate schema evolution
Section titled “Tolerate schema evolution”- Ignore unknown JSON fields.
- Don’t fail on new event types appearing on a shared endpoint; log and skip
unknown values of
X-UCS-Event.
Secure your endpoint
Section titled “Secure your endpoint”- Use HTTPS with a valid certificate — subscription URLs should always be
https://in production. - Keep the URL secret and unguessable (e.g. include a random path segment:
/hooks/unexus/f3b1c9…). Anyone who knows the URL can send you fake events. - Validate the source where possible: restrict by source IP range of your Unexus installation (ask your Unexus contact).
- Never trust webhook data for authorization decisions — treat payloads as notifications and re-query authoritative state through the authenticated REST API when performing sensitive actions.
- Deliveries currently carry no signature header. If your security policy requires verifying payload authenticity, raise this with your Unexus contact.
Operational checklist
Section titled “Operational checklist”- Endpoint answers
200in under a second (queue internally). - Idempotency on
deliveryId. - Out-of-order handling based on
occurredAt. -
4xxfor unprocessable requests,5xxonly when a retry should follow. - Alerting on endpoint downtime and on
X-UCS-Attemptvalues above1in your logs. - Reconciliation job for data you cannot afford to miss after an outage longer than the retry window.
- Separate subscriptions (and URLs) per environment.
- Test end-to-end with a capture tool (e.g. webhook.site) before go-live, including a forced
500to see a retry arrive.