Webhook Testing: How to Verify Payloads, Signatures, and Retries
Verify webhook requests from headers and raw-body signatures through safe acknowledgement, duplicate delivery, and provider retries.
To test webhooks, expose a local receiver through a temporary public HTTPS tunnel, trigger a test event, and verify its headers, event type, body, signature, and processing behavior. A webhook is an HTTP request sent by a provider to your server when an event occurs; it is not a request your client initiates on demand. Delivery, signing, and retry rules vary by provider, so use that provider's documentation as the contract.
Build a local test loop
Run your receiver locally on a test port, then use a reputable tunneling tool that provides a temporary public HTTPS URL and forwards requests to that port. Register the URL only in a provider's test or sandbox configuration. Send a harmless test event, inspect the request at your receiver, and remove or rotate the temporary endpoint when finished. A tunnel makes the local server reachable from outside your machine; treat it as public exposure and avoid sensitive data.
API Test Lab is an API client and tester. The current product documentation describes sending requests to APIs, but does not establish that it receives inbound provider webhooks. Use your own receiver or provider's delivery tools for inbound delivery tests.
Validate headers, event type, and payload
Check the HTTP method and content type, required delivery or event ID, signature header, and provider-specific event header. Parse the body only after reading it in the way required by the signature process. Validate the event type against an allowlist, then validate required fields and types. Unknown event types should follow a deliberate policy, such as acknowledge and ignore, or return a documented error.
Use provider fixtures or sandbox events. Do not assume a sample event covers nullable fields, large values, Unicode, unknown fields, or a newer event version. Record a redacted payload fixture to make your handler tests repeatable.
Verify the signature over original bytes
Many providers sign the exact payload bytes. Parsing JSON and serializing it again can change whitespace, escaping, or key order and invalidate the signature. In a real framework, capture the raw request body before JSON parsing and pass those bytes to verification. Follow the chosen provider's algorithm, header format, encoding, and key rotation instructions.
The example below uses GitHub's documented `X-Hub-Signature-256` format as the provider-specific example. It computes HMAC-SHA256 over the raw bytes, compares equal-length buffers with `timingSafeEqual`, and rejects malformed signatures. Do not reuse this header or scheme for another provider without checking its specification.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyGitHubSignature(rawBody, signatureHeader, secret) {
if (!Buffer.isBuffer(rawBody) || typeof signatureHeader !== 'string') return false;
const match = /^sha256=([a-f0-9]{64})$/i.exec(signatureHeader);
if (!match) return false;
const received = Buffer.from(match[1], 'hex');
const expected = createHmac('sha256', secret).update(rawBody).digest();
return received.length === expected.length && timingSafeEqual(received, expected);
}The function assumes the framework already preserved the raw body in a `Buffer`. Keep the secret outside source control. Reject a missing or invalid signature before dispatching any business action. Constant-time comparison for the digest does not by itself make the entire handler timing-safe.
Add timestamp and replay checks
Some providers include a signed timestamp; others do not. If the provider specifies one, verify it as part of the signed message and enforce a reasonable age window based on its guidance and your clock-synchronization policy. A timestamp alone does not stop a replay inside that window. Persist provider delivery IDs or event IDs and deduplicate them, ideally with a uniqueness constraint, before applying side effects.
Do not invent a timestamp header or combine it with the sample scheme above. Signature bases differ by provider. If your provider does not sign timestamps, use its event identity and duplicate delivery policy to provide replay resistance.
Acknowledge durable acceptance, then work asynchronously
Webhook senders often have a response timeout. Validate the request, store a durable job or inbox record, and acknowledge only once you have accepted responsibility for it. A worker can then process the event asynchronously. If persistence fails, return a failure response that fits the provider's retry behavior. Avoid acknowledging an event that exists only in process memory if a restart would lose it.
Providers commonly retry some unsuccessful or timed-out deliveries, but the retry schedule, duration, and manual redelivery controls differ. Read the provider's current delivery documentation. Design handlers to tolerate duplicate and out-of-order events: use idempotent writes, compare event versions or timestamps when meaningful, and fetch current provider state when event ordering cannot be trusted.
Test the cases that break handlers
- Valid signature and valid event produce one durable job and one intended effect.
- One changed body byte, wrong secret, malformed signature, or missing signature is rejected.
- The exact same delivery sent twice creates no second effect.
- A timeout after durable acceptance followed by retry does not duplicate work.
- An event arriving after a newer event cannot roll state backward accidentally.
- Unknown event types, missing fields, invalid JSON, and unexpected content types follow a safe policy.
- A key rotation window accepts the provider's documented active keys and rejects retired keys as specified.
- Large or Unicode payloads verify correctly using their original bytes.
Keep a local test mode that can replay redacted fixtures. Never test retry storms against a production endpoint, and do not store real customer payloads in fixtures without an approved data-handling basis.
For related request checks, see how to test REST APIs, API authentication testing, and API regression testing.
References
- GitHub: Validating webhook deliveries
- GitHub: Webhook events and payloads
- Node.js Crypto documentation
Use API Test Lab to exercise any outbound API calls your webhook handler makes, while using your receiver and provider tools to verify inbound delivery.
More from the blog
Read 3 related articles from our latest posts.