A webhook payload is the data in the body of a webhook HTTP request: a description of the event that just happened, usually as JSON. It typically includes an event ID, an event type, a timestamp and the resource the event is about. The provider sends it to your webhook endpoint, along with headers that identify and sign it.
POST /webhooks/billing HTTP/1.1
Content-Type: application/json
webhook-id: msg_2xKq8f3Ld9
webhook-timestamp: 1793180400
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{
"id": "evt_01JBX8Q3",
"type": "invoice.paid",
"created_at": "2026-10-28T09:00:00Z",
"data": {
"invoice_id": "inv_8842",
"customer_id": "cus_193",
"amount": 4900,
"currency": "usd"
}
}
What's in a typical payload
| Field | Purpose |
|---|---|
Event ID (id) | Unique per event. Use it to deduplicate retries. See idempotency. |
Event type (type, event, action) | Lets one endpoint route invoice.paid and invoice.failed to different handlers. |
| Timestamp | When the event happened. Useful for ordering, since deliveries can arrive out of order. |
Resource (data, object) | The thing that changed, either in full or as a reference. |
| API version | Some providers (Stripe, for example) pin the payload shape to an API version. |
Some providers put the event type or delivery ID in headers instead. GitHub sends X-GitHub-Event and X-GitHub-Delivery, and the Standard Webhooks spec uses webhook-id.
Payload formats
- JSON (
application/json): Stripe, GitHub, Shopify and most modern APIs. - Form-encoded (
application/x-www-form-urlencoded): Twilio, Slack slash commands, and some older systems. Slack's interactive payloads are a JSON string inside a form field calledpayload. - XML: some payment, telecom and enterprise systems.
- Multipart: inbound email services that send attachments.
Read the Content-Type header and parse accordingly.
Fat vs thin payloads
A fat payload carries the full resource, so you can act on it directly. A thin payload carries only the type and an ID, and you call the provider's API for the current state. Thin payloads are smaller, never stale, and limit the damage if a payload leaks, at the cost of an extra API request per event. Even with fat payloads, fetching the latest state before acting is a safe pattern when events can arrive out of order.
Handling payloads safely
- Keep the raw bytes. Signatures are computed over the exact body. If a framework parses and re-serializes JSON first, whitespace or key order changes and verification fails. In Express, use
express.raw()on the webhook route. - Verify, then parse. Check the signature with your webhook secret before trusting any field.
- Validate the shape. Return
400for a body you can't parse and422for a valid body you can't accept. See 400 vs 422. Most providers retry both, so log them. - Ignore unknown fields and event types. Providers add fields without warning. Acknowledge events you don't handle with
200so they aren't retried. - Store before processing. Write the raw payload to a queue or table, return
200, and process asynchronously.
Size limits
Providers cap what they send (GitHub caps payloads at 25 MB), and receivers cap what they accept: nginx defaults client_max_body_size to 1 MB, and many serverless platforms limit request bodies to a few megabytes. Anything over the limit fails with 413 Payload Too Large. Webhook Relay accepts 4 MB per webhook by default and up to 40 MB with large webhooks enabled.
When the payload is the wrong shape
If a destination expects a different structure, for example forwarding a Stripe event into a Slack message, you can reshape it in code, or transform the webhook in flight with a Webhook Relay function before it's delivered.
