Webhook examples: real payloads and headers from 5 providers
Here are 5 real webhook examples copied from official docs: a GitHub issues delivery, a Stripe event, a Shopify product payload, a Slack reaction_added event and a Twilio SMS form post, with their headers. Replay any of them to a free 48-hour URL with one curl command.
Below you'll find 5 real webhook examples copied from the official docs for GitHub, Stripe, Shopify, Slack and Twilio, each with the headers that come with it, trimmed where the original is long but never invented. They all share the same shape: a POST to a URL you registered, headers that name the event and carry a signature, and a body in JSON (or form fields for Twilio). At the end of the page, there are curl commands that send these examples to a free URL so you can see them the way your server would.
What every webhook request contains
Every webhook request has 4 parts. First, the webhook URL, for example https://example.com/webhooks/stripe on your own server, or a test URL like https://<bin>.webhooks.page/stripe. The method is POST in all 5 examples here, and the request comes with headers that name the event and carry an ID and a signature, plus the body itself.
| Provider | Body | Event named in | Signature header | Unique ID |
|---|---|---|---|---|
| GitHub | JSON | X-GitHub-Event header | X-Hub-Signature-256 | X-GitHub-Delivery |
| Stripe | JSON | type field | Stripe-Signature | Event id |
| Shopify | JSON | X-Shopify-Topic header | X-Shopify-Hmac-Sha256 | X-Shopify-Webhook-Id |
| Slack | JSON | event.type field | X-Slack-Signature | event_id |
| Twilio | Form fields | No event field; it is an incoming message | X-Twilio-Signature | MessageSid |
GitHub: an issue was opened
Here is GitHub's own example delivery, starting with the headers and then the JSON body, which GitHub trims with ... in its docs. X-GitHub-Event is issues and the action field in the body is opened, while X-GitHub-Delivery is the unique GUID for this delivery. X-Hub-Signature-256 is an HMAC SHA-256 hex digest of the body made with your webhook secret (we shortened the value here). GitHub expects a 2xx within 10 seconds.
HTTP headers (GitHub's example)
POST /payload HTTP/1.1
X-GitHub-Delivery: 72d3162e-cc78-11e3-81ab-4c9367dc0958
X-Hub-Signature-256: sha256=d57c68ca6f92289e6987922ff2693893...
User-Agent: GitHub-Hookshot/044aadd
Content-Type: application/json
X-GitHub-Event: issues
X-GitHub-Hook-ID: 292430182
X-GitHub-Hook-Installation-Target-ID: 79929171
X-GitHub-Hook-Installation-Target-Type: repository
JSON body (trimmed by GitHub)
{
"action": "opened",
"issue": {
"number": 1347,
...
},
"repository": {
"id": 1296269,
"full_name": "octocat/Hello-World",
"owner": {
"login": "octocat",
"id": 1,
...
},
...
},
"sender": {
"login": "octocat",
"id": 1,
...
}
}
Stripe: an Event object
Every Stripe webhook body is an Event object. Below is Stripe's example from its API reference, trimmed to the main fields, along with the Stripe-Signature header format from Stripe's signature docs, they're two separate examples, so that signature doesn't belong to this body.
typenames the event (setup_intent.createdhere), anddata.objectholds the object itself.id(evt_1NG8Du2eZvKYlo2CUI79vXWy) is what you store to skip duplicates, because Stripe can send the same event more than once.livemodesays whether it's live or test, andcreatedis a Unix timestamp.
The Stripe-Signature header carries t (a timestamp) and v1, which is an HMAC-SHA256 of t, a dot, and the raw body made with your whsec_ signing secret, with a default tolerance of 5 minutes. Like Stripe's docs, we split the header over lines for clarity; a real one is a single line.
Stripe-Signature header (format example, wrapped)
Stripe-Signature: t=1492774577,
v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd,
v0=6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39
Event body (Stripe's example, trimmed)
{
"id": "evt_1NG8Du2eZvKYlo2CUI79vXWy",
"object": "event",
"api_version": "2019-02-19",
"created": 1686089970,
"data": {
"object": {
"id": "seti_1NG8Du2eZvKYlo2C9XMqbR0x",
"object": "setup_intent",
"status": "requires_confirmation",
"usage": "off_session"
}
},
"livemode": false,
"pending_webhooks": 0,
"request": {
"id": null,
"idempotency_key": null
},
"type": "setup_intent.created"
}
Shopify: a product was updated
Below are Shopify's header example and its example product payload, both from Shopify's docs but from different pages, so the HMAC doesn't match the body. X-Shopify-Topic names the topic (products/update), and X-Shopify-Hmac-Sha256 is a base64 HMAC-SHA256 of the raw body made with the app's client secret. You get X-Shopify-Webhook-Id, which is unique per delivery and is what Shopify says to deduplicate on, while X-Shopify-Event-Id is shared by all deliveries from the same merchant action, and X-Shopify-API-Version gives the payload's API version. By default the body is the full REST resource for the topic (a product here), and Shopify expects a response within 5 seconds.
HTTP headers (Shopify's example)
POST /webhooks/products HTTP/1.1
Content-Type: application/json
X-Shopify-Topic: products/update
X-Shopify-Hmac-Sha256: xT6yH5jZfQm6Q3lC8nA0r7sV2bP9dK1eL4wG0hM5nYI=
X-Shopify-Shop-Domain: snowdevil.myshopify.com
X-Shopify-API-Version: 2026-10
X-Shopify-Webhook-Id: 545ebdd4-b7be-4d3d-b14e-54aa78e0e0f9
X-Shopify-Triggered-At: 2025-07-10T16:15:47.000000Z
X-Shopify-Event-Id: 59d6d79c-595f-4e7e-bc6f-72f5c3d0c66f
Product payload (Shopify's example, trimmed)
{
"id": 9554194432293,
"title": "T-Shirt",
"status": "active",
"vendor": "My Store",
"product_type": "Shirts",
"updated_at": "2025-04-22T14:30:00-05:00",
"variants": [
{
"id": 123456789,
"title": "Default Title",
"price": "29.99",
"sku": "TSHIRT-001",
"taxable": true
}
],
"tags": "cotton, comfortable"
}
Slack: someone added a reaction
Slack's reaction_added example is below, wrapped in the event_callback envelope that every Events API request uses (trimmed here). The outer type is event_callback while event.type is the actual event, event_id is unique to this event across all workspaces, and team_id and api_app_id say which workspace and app it's for.
Slack's example doesn't show headers, so rather than invent values, here are the ones it sends: X-Slack-Signature (v0= plus an HMAC-SHA256 made with your signing secret) and X-Slack-Request-Timestamp, plus x-slack-retry-num and x-slack-retry-reason on retries. Slack expects a 2xx within 3 seconds.
{
"team_id": "T123ABC456",
"api_app_id": "A123ABC456",
"event": {
"type": "reaction_added",
"user": "U123ABC456",
"item": {
"type": "message",
"channel": "C123ABC456",
"ts": "1464196127.000002"
},
"reaction": "slightly_smiling_face",
"item_user": "U222222222",
"event_ts": "1465244570.336841"
},
"type": "event_callback",
"event_id": "Ev123ABC456",
"event_time": 1234567890
}
Twilio: an incoming SMS is a form post
Twilio is the odd one out because it sends application/x-www-form-urlencoded fields instead of JSON, and your app answers with TwiML. The main parameters and example values from Twilio's docs are in the table below. MessageSid is a 34-character unique ID, and Body holds up to 1600 characters. The signature header is X-Twilio-Signature: Twilio appends the POST fields, sorted alphabetically by name, to the full URL, signs that with HMAC-SHA1 using your Auth Token, and base64-encodes it.
| Parameter | Example | Meaning |
|---|---|---|
MessageSid | SMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | 34-character unique ID of the message |
AccountSid | ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | 34-character SID of your account |
From | +14017122661 | The number that sent the text |
To | +15558675310 | The recipient, your Twilio number |
Body | Ahoy! Let's build something amazing. | The text, up to 1600 characters |
NumMedia | 0 | Number of media items attached |
NumSegments | 1 | Number of message segments |
Send any of these to a free URL
You can create a free bin at view.webhooks.page and replay these examples with curl using the commands below, GitHub's with its event header, and a Twilio-style form post built with --data-urlencode, which curl sends as application/x-www-form-urlencoded. Open your view URL and you'll see each request arrive live the way your server would: method, path, query string, headers and body. Replayed examples won't pass signature checks in your own code, since a real signature needs the provider's secret, which is what signatures are for.
A free bin lasts 48 hours and takes 100 requests a day with bodies up to 100KB, showing the latest 10 in the viewer. You can claim it with a free Google sign-in to keep the same URL with 30 days of rolling history.
# 1. Create a free bin (or open view.webhooks.page)
curl -X POST https://webhooks.sh/v1/bins
# 2. Replay GitHub's example (trimmed)
curl -X POST https://<bin>.webhooks.page/github \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: issues" \
-d '{"action":"opened","issue":{"number":1347}}'
# 3. Replay a Twilio-style SMS form post
curl -X POST https://<bin>.webhooks.page/sms \
--data-urlencode "From=+14017122661" \
--data-urlencode "To=+15558675310" \
--data-urlencode "Body=Ahoy! Let's build something amazing."
FAQ
What does a webhook payload look like?
A webhook payload is usually a JSON object describing one event, such as Stripe's Event object with its id and type, sent with headers that carry a signature and often the event name. Twilio is an exception and sends application/x-www-form-urlencoded form fields instead of JSON.
What is an example of a webhook URL?
It's any HTTPS URL you control, like https://example.com/webhooks/stripe, or a test URL like https://<bin>.webhooks.page/stripe.
Which headers come with a webhook?
You'll get Content-Type, a signature header such as X-Hub-Signature-256, Stripe-Signature, X-Shopify-Hmac-Sha256, X-Slack-Signature or X-Twilio-Signature, and often an event name header like X-GitHub-Event or X-Shopify-Topic plus a delivery ID like X-GitHub-Delivery or X-Shopify-Webhook-Id.
How do I send a test webhook payload?
POST it with curl to a free bin on webhooks.sh with the right Content-Type and headers, then read the captured request in the viewer.
Can I get a real Stripe or GitHub webhook for testing?
Yes, point their webhook settings at a free bin URL and trigger an event. GitHub lets you redeliver deliveries from the past 3 days, and Stripe lets you resend an event from the Dashboard up to 15 days after it was created.
Last updated
Try it in 10 seconds.
Create a free bin at view.webhooks.page — no signup, no install. Point your sender at the URL and watch requests land live. Bins expire after 48 hours.