Think all webhook payload examples are interchangeable? Think again.
Providers like GitHub, Stripe, Shopify, and Typeform pack the same event information into very different JSON shapes — timestamps, nested data, and id fields all move around.
This post walks through real-world webhook payload examples and shows practical patterns you’ll trust: where to find event ids, how amounts and timestamps are formatted, which signature headers to verify, and quick checks to avoid duplicate processing so you save debugging time.
Real-World Webhook Payload Examples from Multiple Providers

Different platforms send unique JSON payloads that match their data model and event types. GitHub uses a push event structure that captures repository changes, commit metadata, and developer actions. Here’s what a GitHub push payload looks like:
{
"ref": "refs/heads/main",
"head_commit": {
"id": "abc123def456",
"message": "Fix parsing bug in webhook handler"
},
"repository": {
"id": 123456789,
"full_name": "octocat/hello-world"
},
"pusher": {
"name": "octocat",
"email": "octocat@github.com"
},
"commits": [
{
"id": "abc123def456",
"timestamp": "2024-01-15T10:30:00Z"
}
]
}
Stripe wraps everything inside a data.object property. Currency values are integer cents. When a customer pays an invoice, you’ll get the invoice id, amount, currency, and payment metadata:
{
"id": "evt_1N2P3Q4R5S6T7U8V",
"type": "invoice.payment_succeeded",
"created": 1615380000,
"data": {
"object": {
"id": "in_1N2P3Q4R5S6T7U8V",
"amount_due": 1999,
"amount_paid": 1999,
"currency": "usd",
"status": "paid"
}
}
}
Shopify orders/create webhooks deliver full order records with ISO timestamps, line item arrays showing price and quantity, customer details. Typeform response webhooks include a definition object that maps question references to labels, an answers array containing only the fields people actually filled out, and multiformat objects when video responses exist. Those multiformat blocks include videoid, audioid, and transcript fields. File uploads show up as file_url entries.
Common providers sending webhook payloads:
- GitHub push – repository metadata, head_commit details, pusher identity, commits array
- Stripe invoice – payment amounts in cents, currency codes, unix timestamps, nested payment objects
- Shopify order – createdat in ISO 8601, lineitems with price and quantity, customer shipping data
- Typeform response – definition for question mapping, answers array (answered only), multi_format for video/audio
- E-commerce events – Add To Cart, Complete Transaction, Product View with SKUs and totals
Understanding Webhook Payload Structure and Required Fields

Most webhook payloads share a common top level structure. The outer JSON object includes event metadata (id, event type, timestamp) and a nested data or object property containing the actual resource. Providers always set Content-Type to application/json in the request headers.
The answers array in form webhooks like Typeform only includes questions the respondent answered. Skipped or non-required fields don’t appear. Date fields arrive in YYYY-MM-DD format without time components. Ending screens appear in an ending object that carries an id and a reference string identifying which ending the workflow displayed. When you receive payloads with arrays of definition objects or field lists, expect each definition.fields item to include up to six properties describing label, type, constraints, and metadata.
| Field Name | Description | Example Value |
|---|---|---|
| id | Unique event identifier (UUID or string) | “evt_1N2P3Q4R5S6T7U8V” |
| event / type | Event category or action | “invoice.payment_succeeded” |
| timestamp / created | Event time in ISO 8601 or unix epoch | “2024-01-15T10:30:00Z” or 1615380000 |
| data / object | Nested resource object or array | {“id”: “in_123”, “amount”: 1999} |
Examples of Webhook Event Types and Their JSON Bodies

Stripe delivers invoice payment success messages with the event type invoice.payment_succeeded. The payload wraps the invoice object inside data.object and reports the amount in integer cents. Currency appears as a three-letter ISO code. The created timestamp uses unix epoch seconds:
{
"id": "evt_1N2P3Q4R5S6T7U8V",
"type": "invoice.payment_succeeded",
"created": 1615380000,
"data": {
"object": {
"id": "in_1N2P3Q4R5S6T7U8V",
"amount_due": 1999,
"amount_paid": 1999,
"currency": "usd",
"status": "paid"
}
}
}
Shopify sends orders/create webhooks whenever a new order is placed. The created_at field uses ISO 8601 formatting with a Z timezone marker. Line items are delivered in an array, and each item includes price as a decimal string and quantity as an integer:
{
"id": 820982911946154508,
"email": "customer@example.com",
"created_at": "2021-03-10T14:48:00Z",
"total_price": "19.99",
"currency": "USD",
"line_items": [
{
"id": 466157049,
"title": "Widget",
"price": "19.99",
"quantity": 1
}
]
}
E-commerce and site tracking webhooks follow similar patterns. Add To Cart events include productid, SKU, price (often as a decimal like 19.99), and quantity. Complete Transaction payloads carry orderid, totalamount (either decimal or integer cents), currency code, and a timestamp. Product View events deliver productid and SKU for analytics tracking. Update Cart and Remove From Cart events include item_id and updated quantity or zero for removals.
Event category patterns you’ll see:
- Repository events – push, pull_request, release, with commit and branch metadata
- Payment and billing – invoice paid, subscription created, payment failed, with amounts and currency
- Order lifecycle – order created, order updated, order canceled, line item arrays, shipping data
- User interactions – page view, product view, add to cart, registration, favorite, search, waitlist, with timestamps and user identifiers
Webhook Headers, Signatures, and Security Fields in Payloads

Webhook providers send cryptographic signatures in custom HTTP headers so you can verify the payload hasn’t been tampered with. GitHub uses X-Hub-Signature-256 with an HMAC SHA256 digest. Stripe delivers three components in a single Stripe-Signature header: a unix timestamp prefixed by t= and one or more versioned signatures like v1=. Other platforms send X-Signature with a sha256= prefix followed by the hex digest. These signatures are computed by hashing the raw request body with a shared secret.
Timestamps in webhook payloads usually appear as either ISO 8601 strings (YYYY-MM-DDTHH:MM:SSZ) or unix epoch integers. Validation logic should accept events within a five-minute window on either side of the current server time to account for clock skew. Payloads may include a retrycount field when a previous delivery attempt failed. Check this value to decide whether to skip processing duplicates or log errors. Some providers also add a deliveryid or attempt_id that changes with every retry.
For long-running processes like video transcriptions, the initial webhook may arrive before transcription finishes. Typeform payloads include videoid, audioid, and transcript fields inside multi_format objects, but the transcript content may be missing if processing hasn’t completed. When you see empty or null transcript fields, re-fetch the response later using the Responses API.
Common signature header formats:
- X-Hub-Signature-256: sha256=abc123def456
- X-Signature: sha256=abc123def456
- Stripe-Signature: t=1615380000,v1=abc123def456
- Custom-Signature: hmac-sha256=abc123def456
Handling Webhook Payload Delivery, Retries, and Idempotency

Webhook platforms expect your endpoint to respond with a 2xx status code within a short timeout window (commonly 5 to 30 seconds). If your server returns a 4xx status, the provider interprets it as a permanent failure and stops retrying. A 5xx response or a timeout triggers automatic retries with exponential backoff. The retry_count field increments with each attempt, so you can detect repeated deliveries.
Retries can deliver the same event multiple times. Every webhook consumer should implement idempotent processing. Use the event id field as a deduplication key. Store processed event IDs in a database or cache with a time-to-live that exceeds the provider’s retry window (often 24 to 72 hours). Before processing a payload, check whether the id already exists in your store. If it does, return 200 immediately without running business logic again.
Patterns for reliable webhook handling:
- Return 200 fast – acknowledge receipt within seconds, then queue heavy processing asynchronously
- Store event IDs – write the id to a database or Redis set before processing; reject duplicates with a 200 response
- Validate signatures – compute HMAC on the raw body and compare to the signature header before parsing JSON
- Handle missing data gracefully – answers arrays only include answered fields; fetch missing transcripts or media files later via the provider’s API
- Log raw payloads – capture the entire request body and headers for debugging and replay testing
Parsing and Validating Webhook Payloads in Code

Production webhook receivers must capture the raw request body before any middleware parses it. Signature verification requires the exact bytes the provider sent. After verifying the signature, parse the JSON and validate the schema against expected field types. If critical fields are missing or malformed, return a 4xx status to prevent retries.
The answers array in form-response payloads only contains answered questions. Iterate over the array and match each answer’s question reference to the definition.fields list to retrieve the human-readable question text. Payment webhooks include a payment object with exactly four elements describing amount, currency, status, and timestamp. File uploads appear as fileurl strings. Video responses deliver multiformat objects. If videoid and audioid are present, the respondent recorded a video answer, and you’ll need to call the Responses API to download the media files.
NodeJS/Express Parsing
Disable Express’s automatic JSON body parser for the webhook route. Use express.raw to capture the buffer, then verify the signature against that raw buffer before calling JSON.parse. Store the parsed payload in req.body for downstream middleware:
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-hub-signature-256'];
// Verify signature using req.body buffer
const event = JSON.parse(req.body.toString());
if (!event.id || !event.type || !event.data) {
return res.status(400).send('Missing required fields');
}
// Process event.data
res.status(200).send('OK');
});
Minimal schema expectations: every payload should have id (string), event or type (string), and timestamp (ISO 8601 or unix integer). Nested objects like data.object or answers[] require separate validation.
Python/Flask Parsing
Flask’s request.getdata() returns the raw bytes before any JSON parsing. Compute your HMAC signature on those bytes, compare it to the incoming header, then call request.getjson() to parse:
from flask import Flask, request
import json
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Signature')
raw_body = request.get_data()
# Verify HMAC signature here
payload = request.get_json()
if not payload.get('id') or not payload.get('event'):
return 'Missing required fields', 400
# Process payload['data']
return 'OK', 200
Check for required top level keys (id, event, timestamp, data) and validate nested types. If the payload includes a payment object, confirm it has four elements. If multiformat appears, check whether videoid and transcript are populated or null.
Testing and Debugging Webhook Payloads with Developer Tools

Run ngrok or localtunnel to expose your local dev server to the internet, then paste the generated HTTPS URL into the provider’s webhook configuration panel. Send a test event from the provider’s dashboard or use a REST client like Postman to POST a sample payload directly to your endpoint. Log the raw request body and all headers to a file so you can replay the exact payload during debugging.
Check for missing data in the answers array by cross-referencing the definition.fields array. If a required question isn’t present in answers, the respondent either skipped it or form logic hid it. For media fields, inspect the multiformat object. If videoid exists but transcript is null, the transcription isn’t ready yet. Re-fetch the response via the provider’s API after a delay.
Debugging workflow:
- Capture raw payloads – log request.body and headers to inspect exact bytes and signatures
- Replay test events – use webhook.site or requestbin to inspect payloads, then POST the same JSON to your local server
- Validate required fields – write assertions for id, event, timestamp, and data; return 400 if any are missing
- Map field references – iterate definition.fields to find question labels, then match answers by question id or ref
Final Words
In the action, we dropped raw JSON from GitHub and Stripe, plus concise Shopify and Typeform snippets so you can see how providers differ and copy real payloads fast.
Then we walked through payload anatomy, signature checks, retry and idempotency patterns, parsing examples for Node/Express and Flask, and testing tips with ngrok and webhook.site for quick debugging.
Keep a saved webhook payload example to replay locally, verify signatures, and dedupe by event id — do that and you’ll avoid late‑night surprises.
FAQ
Q: What is the payload of a webhook?
A: The payload of a webhook is the data bundle sent by a provider to your endpoint, usually JSON containing event id, type, timestamp, data/object fields, plus optional signature and retry metadata.
Q: What is an example of a payload?
A: An example of a payload is a GitHub push JSON with headcommit.id, repository.fullname, pusher.name, and commits[], or a Stripe invoice JSON showing amount, currency, created (unix), and payment sub‑objects.
Q: How to create a webhook payload URL?
A: To create a webhook payload URL, expose an HTTPS endpoint (public URL), configure the provider to POST there, accept application/json, verify signature headers, and return a 2xx response for successful deliveries.
Q: What are some webhook examples?
A: Some webhook examples are GitHub push events, Stripe invoice.payment_succeeded, Shopify orders/create, Typeform submission responses, and e‑commerce events like Add To Cart or Complete Transaction.
