jsonwebhooksapibackend

Designing Webhook JSON Payloads (with Examples)

·9 min read·APIs

What a webhook is

A webhook is a reversed API call: instead of clients polling you for changes, *you* POST a JSON payload to a URL the consumer registered whenever an event happens. Stripe, GitHub, and Shopify all work this way. Good payload design makes integrations painless; bad design makes them fragile.

Anatomy of a good payload

json
{
  "id": "evt_1NXyZA",
  "type": "order.shipped",
  "created": 1716268800,
  "api_version": "2026-01-01",
  "data": {
    "object": {
      "id": "ord_7Tn",
      "status": "shipped",
      "tracking_number": "DLVY99887"
    }
  }
}

Five things make this robust:

  • `id` — a unique event ID for deduplication and idempotency.
  • `type` — a dot-namespaced event name (order.shipped) consumers switch on.
  • `created` — when the event occurred, so consumers can order or discard stale events.
  • `api_version` — which payload schema this follows, so you can evolve safely.
  • `data.object` — the resource that changed, nested so you can add sibling fields later.

Make delivery verifiable

Anyone who learns the URL can POST fake events. Sign every payload so the consumer can verify it came from you. The standard pattern is an HMAC signature over the raw body, sent in a header:

text
X-Signature: t=1716268800,v1=5257a8...

The consumer recomputes HMAC-SHA256(secret, timestamp + "." + rawBody) and compares. Include the timestamp in the signed content to prevent replay attacks, and reject events older than a few minutes.

Design for reliability

Networks fail. Consumers go down. Design for it:

  • At-least-once delivery. Assume events may arrive more than once; consumers must dedupe on id.
  • Retries with backoff. Retry failed deliveries on an increasing schedule, then give up and surface the failure.
  • Fast acknowledgement. Consumers should return 2xx immediately and process asynchronously — never do slow work before acking, or you will time out and trigger retries.

Versioning the payload

Never break existing consumers. Safe evolution:

  • Add fields freely — consumers ignore unknown keys.
  • Never remove or repurpose a field within a version.
  • Carry `api_version` so you can introduce a new shape while old consumers keep getting the old one.

A consumer's checklist

text
1. Verify the signature. Reject if invalid.
2. Reject if the timestamp is too old (replay protection).
3. Dedupe on event id. Skip if already processed.
4. Return 2xx immediately.
5. Process the event asynchronously.

Frequently asked questions

Include enough to act without an extra API call (the changed resource), but consumers should re-fetch for the authoritative state if precision matters, since events can arrive out of order.

By deduplicating on the unique id. Because delivery is at-least-once, idempotency is the consumer's responsibility.

To prevent replay attacks — an attacker capturing a valid payload cannot resend it later because the timestamp will be too old.

Try JSON Formatter

Format and validate webhook payloads as you design them.