Webhook Signature Verification

How to verify webhook signatures to ensure events are from WarmySender.

Why Verify Signatures?
Without verification, anyone who discovers your endpoint URL could send fake events. Signature verification ensures only WarmySender can send valid events.

How It Works:
1) When you create a webhook, WarmySender shows a signing secret (it starts with whsec_) once, with a Copy secret button. You will not see this secret again. Save it before you close the panel.
1b) To replace a secret you missed: Settings > Webhooks, click the key icon on that row (Rotate secret), confirm, and copy the new secret. Only a workspace owner or admin can do this. The previous secret stops working immediately. The same replacement is available on the API as POST /api/v1/webhooks/{id}/rotate-secret (the secret is in that response once, and list/get never include it).
2) Every webhook delivery includes an X-Warmy-Signature header.
3) The header format is: t=<timestamp>,v1=<hex_signature>
4) To verify, reconstruct the signature using your secret and compare.

Test webhook:
The Test button on the row (and POST /api/v1/webhooks/{id}/test) queues a webhook.test event to that address. It is signed with the same X-Warmy-Signature header as every other event. Each try, including every retry, is signed at the moment it is sent: t= is the current time in milliseconds and v1= is computed from that time and the body. A retry does not reuse the original header, so a 5-minute freshness check does not reject a retry that was signed when it was sent. If your address does not accept the delivery, WarmySender tries again, up to 10 tries in total. The wait before each retry grows: 5 minutes, 15 minutes, 1 hour, 3 hours, 6 hours, 12 hours, 1 day, 2 days, then 3 days.

If deliveries keep failing:
After 50 events in a row each use up all 10 tries, the webhook is paused so it stops calling an address that is not accepting events. On that row, an owner or admin clicks Turn back on. That starts the count over. Pausing does not delete the endpoint.

Verification Steps:

Step 1: Extract timestamp and signature from X-Warmy-Signature header:

Step 2: Build the signature payload:

Step 3: Compute HMAC-SHA256:

Step 4: Compare signatures:

Step 5 (Optional): Check timestamp freshness:

Node.js Example:
const crypto = require('crypto');

function verify(secret, signatureHeader, body) {
const [tPart, vPart] = signatureHeader.split(',');
const timestamp = tPart.replace('t=', '');
const signature = vPart.replace('v1=', '');
const payload = timestamp + '.' + body;
const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(expected, 'hex'));
}

Python Example:
import hmac, hashlib

def verify(secret, signature_header, body):
parts = dict(p.split('=', 1) for p in signature_header.split(','))
payload = parts['t'] + '.' + body
expected = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(parts['v1'], expected)

Common Mistakes:

Related guides in Platform

Back to all documentation | Contact support