Skip to content

Webhook best practices

How to build a receiver that stays correct and healthy in production.

Your endpoint must return 2xx well within the 10-second delivery timeout. The reliable pattern:

  1. Validate the request minimally (parse JSON, check X-UCS-Event).
  2. Persist the event to a queue or table.
  3. Return 200 OK.
  4. 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.

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 deliveryId of everything you have processed and skip repeats (see below).
  • Answer with the right status. Return 2xx as soon as the event is safely stored. Return 5xx (or let the request time out) only when you genuinely want the delivery again later. Return 4xx for 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.

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.

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”.

  • 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.
  • 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.
  • Endpoint answers 200 in under a second (queue internally).
  • Idempotency on deliveryId.
  • Out-of-order handling based on occurredAt.
  • 4xx for unprocessable requests, 5xx only when a retry should follow.
  • Alerting on endpoint downtime and on X-UCS-Attempt values above 1 in 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 500 to see a retry arrive.