Webhook Payload: Structure, Examples, and Best Practices
Learn what a webhook payload is, see real examples, and follow best practices to build reliable integrations and automate faster.
Introduction: what a webhook payload is and why it matters
A webhook payload is the data body sent to your server when a webhook event is triggered. It usually arrives as JSON in an HTTP POST request and contains the details your app needs to react to what just happened: a payment succeeded, a GitHub pull request opened, a CI job finished, or a customer updated their profile.
The terms are easy to mix up, but they mean different things. A webhook is the mechanism that sends data when an event occurs. A webhook event is the trigger itself, such as push, payment_intent.succeeded, or deployment.created. The webhook endpoint is the URL that receives the request. The payload body is the actual content sent in the request, while HTTP headers carry metadata such as Content-Type or signature information used for verification.
That distinction matters because webhook payloads power automation, CI/CD pipelines, integrations, and event-driven architecture. If you can read, verify, and parse the payload correctly, you can build reliable workflows instead of brittle ones. For a broader primer on how webhooks work, see the webhook tutorial.
This guide covers payload structure, GitHub webhook payload examples, verification, parsing, retries, and debugging. Payload shape varies by provider and event type, so the goal is to recognize patterns, not expect one universal schema.
Webhook payload basics and anatomy
A webhook payload is the request body that carries event data from the provider to your endpoint. The flow is simple: an event occurs, the provider builds the payload, signs the request, and sends it to your webhook endpoint.
Common fields include event_type, action, delivery_id, timestamp, object_id, sender, and api_version. GitHub, for example, sends X-GitHub-Event and X-GitHub-Delivery in the headers, while the webhook payload body contains the resource data itself. Keep body fields separate from HTTP headers like Content-Type and User-Agent during payload validation and schema validation.
Payloads often include nested objects and arrays, such as a repository object or a list of commits, and schemas vary by provider and event type. Delivery IDs and timestamps help with routing, logging, deduplication, and debugging when the same event is retried or arrives out of order.
What is included in a webhook payload?
What a webhook payload includes depends on the provider and the event, but most payloads contain some combination of:
- an event name or type
- a unique delivery ID
- a timestamp
- the primary object that changed
Some providers send full resource objects, while others send only IDs and summary fields. That tradeoff affects latency, payload size, privacy, and how many follow-up API calls your system must make.
How does a webhook payload work?
A webhook payload follows a simple lifecycle: the event happens, the provider builds the JSON body, signs the request with HMAC using SHA-256 and a shared secret, and sends it as an HTTP POST. Your endpoint receives the request, checks Content-Type, verifies the X-Hub-Signature-256 header, then parses the payload and decides whether to process it or queue it.
If your server times out or returns a non-2xx response, retry logic usually resends the same delivery, so handlers must be idempotent and use deduplication with delivery IDs plus request logging. Authentication proves the sender knows the secret; integrity verification proves the payload was not altered; authorization is a separate check for whether that sender may trigger the action. Parse JSON defensively, acknowledge quickly, and hand off heavier work asynchronously. See the webhook tutorial and the API documentation guide.
What is the difference between a webhook and a webhook payload?
A webhook is the delivery mechanism: a provider sends an HTTP request to your webhook endpoint when an event occurs. A webhook payload is the data inside that request. In other words, the webhook is the pipe, and the payload is the message.
That distinction matters when you document integrations, debug failures, or design API endpoints for event-driven architecture. The webhook defines how the request arrives; the payload defines what your application can do with it.
What is the difference between webhook headers and payload body?
Webhook headers and the payload body serve different purposes. Headers carry metadata such as Content-Type, X-GitHub-Event, X-GitHub-Delivery, and signature headers like X-Hub-Signature-256. The payload body contains the event data itself, usually in JSON.
Use headers to identify the event, verify authenticity, and route the request. Use the body to read the actual resource data, such as a pull request title, an issue state, or a repository name.
Payload structure by event type: GitHub webhook examples
GitHub webhooks do not share one universal schema; each event family has its own payload shape. A push event centers on before and after SHAs, ref, pusher, repository, and commits, which makes it useful for CI/CD triggers. A pull_request event adds action, number, title, state, merged, base, and head, so it tracks workflow status rather than raw code changes. An issue event focuses on labels, assignees, comments, state, and repository context for triage. A repository event carries metadata for created, renamed, archived, transferred, or deleted repositories. See the webhook tutorial for setup basics.
What does a GitHub webhook payload look like?
A GitHub push event payload usually looks like this:
{
"ref": "refs/heads/main",
"before": "abc123",
"after": "def456",
"pusher": { "name": "octocat" },
"repository": { "full_name": "octo/repo", "private": true },
"commits": [{ "id": "def456", "message": "Update README" }]
}
Read ref as the branch, commits as an array of changes, repository as nested metadata, and pusher as the actor. A pull request event centers on action, number, state, merged, and base/head branches:
{
"action": "opened",
"number": 42,
"pull_request": {
"state": "open",
"merged": false,
"base": { "ref": "main" },
"head": { "ref": "feature/login" }
}
}
An issue event highlights labels, assignees, state, and issue metadata:
{
"action": "opened",
"issue": {
"number": 17,
"state": "open",
"labels": [{ "name": "bug" }],
"assignees": []
}
}
For payload validation, treat objects as key/value maps, arrays as ordered lists, and IDs as stable references. Before publishing examples, use redaction to replace tokens, emails, and private URLs with placeholders like REDACTED; see README best practices.
How do push event payloads differ from pull request payloads?
Push event payloads describe code changes at the branch level. They usually include commit SHAs, commit messages, the ref that changed, and repository metadata. Pull request payloads describe a reviewable change request. They usually include the PR number, action, merge state, base branch, head branch, and review-related metadata.
In practice, push events are often used to trigger builds, tests, and deployment checks, while pull request events are used to update review status, run CI, and manage approvals.
How do you verify a webhook payload?
To verify a webhook payload, compare the signature sent by the provider with a signature you compute locally from the raw request body and your shared secret. For GitHub webhooks, that usually means reading X-Hub-Signature-256, computing an HMAC with SHA-256, and comparing the result in constant time.
Verification should happen before you trust any field in the body. Also check the timestamp if the provider includes one, and reject requests outside your allowed window to reduce replay attack risk. If the signature fails, return a non-2xx response and do not process the event.
How do you parse a webhook payload in JSON?
Parse the raw request body as JSON only after you verify the signature. In most frameworks, that means reading the request stream once, preserving the raw bytes for verification, and then decoding the same body into an object.
A safe parsing flow looks like this:
- Read the raw body.
- Verify the signature.
- Confirm
Content-Type: application/jsonor the provider’s expected media type. - Decode the JSON.
- Validate required fields.
- Process the event.
If parsing fails, log the delivery ID, event type, and response details so you can distinguish malformed JSON from transport issues.
What are common webhook payload fields?
Common webhook payload fields include:
idordelivery_idevent_typeor provider-specific event nameactiontimestampsenderoractorrepositoryref
Not every provider uses the same names, so map provider-specific fields into your own internal model when possible.
How do webhook retries work?
Webhook retries usually happen when the provider does not receive a timely 2xx response. The provider may resend the same event multiple times with the same delivery ID or a new attempt identifier, depending on the platform. Your handler should assume duplicates are normal and should use retry logic, deduplication, and idempotency to avoid double-processing.
A good pattern is to store the delivery ID, mark the event as processed only after the business action succeeds, and make downstream writes safe to repeat.
How do you handle duplicate webhook deliveries?
Duplicate deliveries happen when a provider retries after a timeout, a network failure, or a transient server error. Handle them by storing a deduplication key such as the delivery ID, event ID, or a hash of the payload plus event type.
If the same event arrives again, return success without repeating the side effect. This is where idempotency matters: your code should produce the same final result even if the webhook is delivered more than once.
What is idempotency in webhook processing?
Idempotency means processing the same webhook event multiple times does not create duplicate side effects. For example, if a payment webhook arrives twice, your system should record the payment once, not twice.
You can implement idempotency with unique constraints, processed-event tables, upserts, or state checks before writes. The exact approach depends on your database and workflow.
How do you secure webhook payloads?
Secure webhook payloads by combining transport security, authenticity checks, and data minimization:
- Use HTTPS for every webhook endpoint.
- Verify signatures with HMAC and SHA-256.
- Reject requests with invalid signatures.
- Add replay protection with timestamps or nonce checks.
- Redact secrets and personal data from logs.
- Validate payloads before business logic runs.
Security is stronger when you treat the payload as untrusted input until verification and validation both succeed.
What is HMAC signature verification?
HMAC signature verification is the process of computing a message authentication code from the raw request body and a shared secret, then comparing it to the signature sent by the provider. If the values match, the request likely came from a party that knows the secret and the body was not altered in transit.
For GitHub webhooks, the signature header is X-Hub-Signature-256. Other providers may use different header names or hash algorithms, but the principle is the same.
Should webhook payloads include full data or just IDs?
There is no single right answer. Full payloads reduce follow-up API calls and can improve developer experience, but they increase payload size and may expose more data than necessary. ID-only payloads are smaller and safer, but they require your system to fetch the latest state from an API endpoint.
A common compromise is to send enough summary data to act immediately, plus stable IDs for fetching the full record when needed. That approach works well when you need both speed and consistency.
How do you version webhook payloads without breaking integrations?
Version webhook payloads with explicit version fields, versioned endpoints, or additive changes that preserve backward compatibility. Avoid renaming or removing fields unless you can support both versions during a transition period.
Good versioning also improves developer experience because integrators can update gradually. Document changes clearly in your API documentation and README, and include examples for each supported version.
Testing, debugging, observability, and common mistakes
Local testing starts with exposing your dev server to the internet so a provider can reach it. Tools like ngrok make this easy: run your app on localhost, tunnel a public URL to it, and point the webhook endpoint at that temporary address. This lets you inspect the raw request body, headers, and signature flow before you deploy.
When deliveries fail, start with the provider dashboard and your own request logging. Look for the delivery ID, response code, retry history, request headers, and timestamps. Many platforms also let you replay events, which helps you compare a successful delivery with a failed one and confirm whether the issue is in your code, your network, or the provider’s retry logic.
For payload parsing problems, check three things first: Content-Type, JSON validity, and body completeness. A webhook may arrive with the wrong content type, truncated data, or malformed JSON if your framework reads the stream too early or your reverse proxy buffers incorrectly. If fields seem missing, compare the event type and API version. GitHub, Stripe, and similar providers often send different shapes for different event types, and version changes can move, rename, or omit fields.
Good observability makes debugging much faster. Log the request ID, delivery ID, event type, timestamp, response code, and processing duration. Add alerting for repeated failures and slow handlers that could trigger provider retries. Keep logs useful, but apply redaction to secrets, tokens, and personal data.
Common mistakes are predictable: skipping signature verification, processing the same event twice without idempotency, blocking on slow downstream work, ignoring retry behavior, trusting schemas never change, and logging sensitive data. Use webhook tutorial for the end-to-end flow, API documentation guide for schema and contract details, how to write API documentation for examples and field definitions, and README best practices to document your endpoint, retry expectations, and verification requirements clearly.
Quick checklist
- Verify the signature before parsing the body.
- Use the raw request body for HMAC verification.
- Treat webhook payloads as untrusted input.
- Make handlers idempotent.
- Store delivery IDs for deduplication.
- Validate JSON with JSON Schema when possible.
- Log enough context for observability, but redact sensitive data.
- Document payload versions and backward compatibility rules.
- Test locally with ngrok or a similar tunnel.
Related resources
- Webhook tutorial
- API documentation guide
- How to write API documentation
- README best practices
- Hookdeck for webhook delivery management and observability