Formatting webhook JSON and reading provider signature headers
Published: 2026-09-05
Pretty-print or minify webhook JSON for debugging, keep the raw body for HMAC checks, and use a local cheat sheet of Stripe, GitHub, Shopify, Slack, and other signature headers without uploading payloads.
Webhook debugging usually fails in two places at once: you cannot read the JSON event (minified one-liners, nested data.object trees), and you cannot verify it because you re-serialized the body or looked at the wrong signature header. Providers sign exact bytes and label authenticity with provider-specific HTTP headers—not with a field inside the JSON.
This guide covers when to pretty-print vs minify, which headers matter for common platforms, and a local workflow that keeps customer payloads and signing secrets in your browser. Pair formatting with HMAC webhook signatures when you need to check digests.
Pretty-print for humans, minify for signatures
| Goal | Use | Why |
|---|---|---|
| Read an event in logs or a ticket | Pretty-print (2- or 4-space indent) | Nested Stripe/GitHub/Shopify objects become scannable |
| Replay or HMAC-verify what the provider sent | Raw body (or minify only if that matches what was signed) | Extra spaces, key order, and trailing newlines change the digest |
| Store a compact fixture | Minify after you understand the shape | Smaller diffs; still not a substitute for the original wire body |
A human-readable {\n "id": "evt_…"\n} and a wire {"id":"evt_…"} are the same object and different messages. Middleware that parses JSON then JSON.stringifys again often reorders keys or drops insignificant whitespace—enough to break Stripe’s t=.… HMAC or GitHub’s sha256= digest. When verification fails, go back to the raw request body from your framework or reverse proxy, not the pretty-printed copy from a dashboard.
LocalTools’ Webhook payload formatter pretty-prints or minifies JSON in-tab and can load Stripe, GitHub, and Shopify samples so you can practice without pasting production events. Formatting runs locally under the same local-only model as other tools on the site.
Signature headers vs metadata headers
Think in two buckets:
- Authenticity — HMAC (or RSA) digests and the timestamps/tokens that go into the signed string.
- Routing and ops — event type, delivery id, shop domain, correlation ids. These help you log and idempotent-process events; they are usually not what you HMAC alone.
Header names are case-insensitive on the wire (Stripe-Signature and stripe-signature are the same header). Values are not: hex vs Base64, sha256= prefixes, and comma-separated t= / v1= fields must match the provider’s docs.
Common authenticity headers
| Provider | Header(s) | Typical shape |
|---|---|---|
| Stripe | Stripe-Signature |
t=<unix>,v1=<hex> — signed string is usually t + . + raw body |
| GitHub | X-Hub-Signature-256 (prefer) / legacy X-Hub-Signature |
sha256=<hex> or sha1=<hex> over the raw body |
| Shopify | X-Shopify-Hmac-SHA256 |
Base64 HMAC-SHA256 of the raw body |
| Slack | X-Slack-Signature + X-Slack-Request-Timestamp |
v0=<hex>; signed string includes v0:timestamp:body |
| Svix-compatible | svix-id, svix-timestamp, svix-signature |
Signatures over id.timestamp.body |
| Mailgun | Timestamp, token, and signature headers | HMAC over timestamp + token (not always “body alone”) |
| Twilio | X-Twilio-Signature |
Often URL + sorted form params, not JSON body HMAC |
| PayPal | PAYPAL-TRANSMISSION-* + cert URL / algo |
Transmission metadata + body; often cert-based verify |
Twilio and PayPal are easy to mis-debug if you assume “HMAC of JSON body like GitHub.” Always confirm what string the provider signed before comparing digests in the HMAC & Webhook Verifier.
Metadata headers worth logging
| Provider | Examples | Use |
|---|---|---|
| GitHub | X-GitHub-Event, X-GitHub-Delivery |
Route by event name; idempotency / log correlation |
| Shopify | X-Shopify-Topic, X-Shopify-Shop-Domain |
Topic handlers; multi-tenant shop identity |
| Generic | X-Request-Id, X-Webhook-Id, User-Agent, Content-Type |
Tracing, sender identity, expect application/json |
The formatter’s offline headers table lists these (and more) with short purpose notes and example fragments—filter by provider or search “signature,” “hmac,” or “delivery” when you are staring at access logs.
A practical local workflow
- Capture the raw body and the signature-related headers from one failed delivery (or a staging replay).
- Pretty-print the body in the Webhook payload formatter to understand
type/topic/ nested objects. Do not feed that pretty string back into HMAC unless it truly matches the wire bytes. - Look up the provider’s signature header name and example shape in the headers cheat sheet; copy the header name into your framework middleware or ticket.
- Verify with the HMAC & Webhook Verifier: paste the raw body (minify only if that restores the signed form), secret, algorithm/encoding, and header value. See HMAC webhook signatures for Stripe-style
t=/v1=pitfalls. - Keep PII and secrets in the tab. Prefer local tools over pasting live webhook JSON into random online “formatters.”
For timestamp windows and Unix seconds vs milliseconds in replay checks, see Unix timestamps: seconds vs milliseconds. For general JSON hygiene before you minify fixtures, see Minifying JSON safely and What is JSON?.
Common pitfalls
- Pretty-print then verify — changes bytes; HMAC fails even with the correct secret.
- Wrong header — using
X-Hub-Signature(SHA-1) while the app sendsX-Hub-Signature-256, or checking a delivery id instead of the signature header. - Body-only HMAC on Stripe/Slack/Svix — those schemes include timestamp (and sometimes id) in the signed string.
- Assuming every webhook is JSON HMAC — form-encoded or cert-based providers need a different verify path.
- Uploading production events — customer emails, order totals, and repo metadata do not belong on third-party paste sites; format and reference headers locally.
Try it
Use the Webhook payload formatter to:
- Paste a body from logs or load a Stripe / GitHub / Shopify sample.
- Pretty-print or minify, then copy output for tickets or fixtures.
- Search the bundled headers table for signature and metadata fields.
- Jump to the HMAC & Webhook Verifier when you need a local match/mismatch check.
Related: Why “local only” matters for developer tools, HMAC webhook signatures, and Converting curl to JavaScript fetch when you are replaying deliveries from captured HTTP.