Standard Webhooks Signature Generator
Generate signed webhook-id, webhook-timestamp and webhook-signature headers for any JSON payload. The signature is the Standard Webhooks HMAC-SHA256 used by Svix, OpenAI, Anthropic and the Webhook Relay Outbound Gateway. You also get a ready-to-run curl command and Node.js and Go signing code. Free, no signup, and the HMAC is computed in your browser.
whsec_ followed by base64. The base64 part is decoded to bytes and used as the HMAC key.
Signed byte-for-byte as typed, whitespace and newlines included. Send exactly this string.
Signed headers
webhook-id: msg_2Ld8Fq1zVbT7nXc4KpR9sWmE
webhook-timestamp: 1791626714
webhook-signature: v1,+6HzXJOO5aA9F/t1gC1vOyPRYYU9jz+Mo/4xlQTV7fs=Signed content ({id}.{timestamp}.{body})
msg_2Ld8Fq1zVbT7nXc4KpR9sWmE.1791626714.{
"type": "invoice.paid",
"timestamp": "2026-10-11T09:25:14Z",
"data": {
"invoice_id": "inv_123",
"customer_id": "cus_42",
"amount": 4900,
"currency": "usd",
"status": "paid"
}
}#), which browsers never send to a server.No receiver yet? Create a free Webhook Bin URL and paste it here to see exactly what arrives, headers included.
curl -X POST 'https://example.com/webhooks' \
-H 'Content-Type: application/json' \
-H 'webhook-id: msg_2Ld8Fq1zVbT7nXc4KpR9sWmE' \
-H 'webhook-timestamp: 1791626714' \
-H 'webhook-signature: v1,+6HzXJOO5aA9F/t1gC1vOyPRYYU9jz+Mo/4xlQTV7fs=' \
--data-binary '{
"type": "invoice.paid",
"timestamp": "2026-10-11T09:25:14Z",
"data": {
"invoice_id": "inv_123",
"customer_id": "cus_42",
"amount": 4900,
"currency": "usd",
"status": "paid"
}
}'The timestamp is fixed in this command, so run it within a few minutes. Receivers usually reject timestamps more than five minutes old. Click Now to re-sign.
What is Standard Webhooks?
Standard Webhooks is an open specification for sending webhooks the same way everywhere. Svix started it, and a steering group with members from Zapier, Twilio, ngrok, Supabase and others maintains it. It defines three headers, one signing scheme and how to rotate secrets. A receiver that verifies one Standard Webhooks provider can verify them all, and reference libraries exist for most languages. OpenAI, Anthropic and the Webhook Relay Outbound Gateway sign this way.
How Standard Webhooks signing works, step by step
- Give each message a unique ID, such as
msg_2Ld8Fq1zVbT7nXc4KpR9sWmE. Keep it the same across retries so receivers can deduplicate. - Take the current Unix time in seconds, such as
1791626714. Each delivery attempt gets a fresh timestamp and signature. - Serialize the payload to a string once. This exact string is both signed and sent as the body.
- Build the signed content:
{webhook-id}.{webhook-timestamp}.{body}, joined with dots. - Strip
whsec_from the secret and base64-decode the rest. Those bytes are the HMAC key. - Compute HMAC-SHA256 over the signed content, base64-encode the digest and prefix
v1,. - Send the request with
webhook-id,webhook-timestampandwebhook-signatureheaders.
Worked example
These values are precomputed, so you can check your own implementation against them.
- Secret
whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw- webhook-id
msg_2Ld8Fq1zVbT7nXc4KpR9sWmE- webhook-timestamp
1791626714
{
"type": "invoice.paid",
"timestamp": "2026-10-11T09:25:14Z",
"data": {
"invoice_id": "inv_123",
"customer_id": "cus_42",
"amount": 4900,
"currency": "usd",
"status": "paid"
}
}webhook-signature: v1,+6HzXJOO5aA9F/t1gC1vOyPRYYU9jz+Mo/4xlQTV7fs=Compact the same JSON to one line and the signature changes completely. HMAC signs bytes, not JSON values.
Common mistakes
- Re-serialized JSON. Signing
JSON.stringify(payload)and then letting an HTTP client serialize the object again can produce different bytes, through key order, spacing or unicode escaping. Serialize once, then sign and send that same string. Receivers have the mirror bug: they parse the body and re-stringify it before verifying. Verify against the raw body. - Using the secret as a string. The HMAC key is the base64-decoded part after
whsec_. Using the literal textwhsec_…as the key, or leaving the prefix on before decoding, gives a valid-looking signature that no receiver accepts. - Milliseconds instead of seconds.
Date.now()returns milliseconds (13 digits).webhook-timestampmust be seconds (10 digits). Receivers reject timestamps outside a tolerance window, typically five minutes, so a millisecond value fails even if the HMAC is right. - Hex instead of base64. Stripe and GitHub use hex digests. Standard Webhooks uses standard base64 with padding, prefixed
v1,. - Mangled body in curl.
curl -dstrips newlines from the data, which changes pretty-printed JSON. Use--data-binary, as the generated command does. - Comma-separated rotation signatures. Multiple signatures are separated by a space:
v1,abc… v1,def…. The comma belongs inside each entry, between the version and the signature.
Frequently asked questions
How do I generate a Standard Webhooks signature?
Build the string "{webhook-id}.{webhook-timestamp}.{body}", where body is the exact JSON string you will send. Base64-decode the part of your secret after "whsec_" and use those bytes as an HMAC-SHA256 key. Base64-encode the digest and send it as "v1,<signature>" in the webhook-signature header, alongside webhook-id and webhook-timestamp.
What does a whsec_ secret contain?
It is the prefix "whsec_" followed by base64-encoded random bytes, usually 24 to 64 of them. The prefix only marks the value as a webhook signing secret. Strip it and decode the rest before using it as the HMAC key. The "Generate random secret" button creates one from 24 bytes of browser randomness.
Is webhook-timestamp in seconds or milliseconds?
Seconds since the Unix epoch, which is a 10-digit number today. A 13-digit millisecond value is outside every receiver's tolerance window, typically five minutes, so the request is rejected even when the HMAC is correct.
How do multiple signatures work during secret rotation?
While a secret is being rotated, the sender signs each message with both the new and the old secret and puts both entries in webhook-signature, separated by a space: "v1,<new> v1,<old>". Receivers accept the request if any entry matches a secret they hold. The Webhook Relay Outbound Gateway keeps both signatures for 24 hours after a rotation.
Is it safe to paste a real signing secret into this generator?
The HMAC is computed in your browser with the Web Crypto API. The page does not send the secret, payload or headers anywhere. The "Verify it" link carries the values in the URL fragment, which browsers never send to a server. Even so, a throwaway secret is best for experiments, and production secrets belong in a secret manager.
Why does my receiver say the signature is invalid?
Usually the bytes differ. Either the receiver parsed and re-serialized the JSON before verifying, or the sender signed one string and sent another. Other causes are using the whsec_ string as the key instead of its decoded bytes, a millisecond timestamp, hex instead of base64 encoding, or a request older than the tolerance window. Compare against the signed content shown by this tool.
How is Standard Webhooks signing different from Stripe or GitHub signatures?
All three use HMAC-SHA256. Stripe signs "{timestamp}.{body}" and sends a hex digest in Stripe-Signature. GitHub signs only the body and sends "sha256=<hex>". Standard Webhooks also signs a unique message ID, encodes the digest as base64 and decodes its secret from base64 before use.
Send these for real
Webhook Relay's Outbound Gateway signs, retries and logs webhooks to your customers. You publish an event once. We deliver it to every subscribed endpoint with Standard Webhooks signatures, up to 8 delivery attempts within 48 hours, 24-hour secret rotation overlap, and 35 days of delivery history. From $9.99/mo, with no per-message fees.
Related free tools
Receiving these webhooks? The receiving guide has verification code for Node.js and Go, plus deduplication on webhook-id.
