Migrating to v2 signatures
Move a webhook to the current signature scheme without downtime
Zavu signs webhooks with HMAC-SHA256. There are two schemes, and each receiver is on one of them:
| Scheme | What is signed |
|---|---|
v1 | the request body |
v2 | the timestamp and the request body, joined as {t}.{body} |
v2 is the current scheme and covers more of the request. Webhooks created
from now on use it. Anything created earlier stays on v1 until you move it,
and there is no deadline.
Check where you are
terminalcurl https://api.zavu.dev/v1/senders/$SENDER_ID \ -H "Authorization: Bearer $ZAVUDEV_API_KEY"
JSON{ "webhook": { "url": "https://api.example.com/webhooks/zavu", "active": true, "signatureVersion": "v1" } }
In the dashboard, every sender webhook and its scheme is listed under Webhooks.
The three steps
Moving is deliberately not one switch. The middle step is where both signatures arrive at once, so you can deploy and confirm your new code before anything depends on it.
1. Turn on both
terminalcurl -X PATCH https://api.zavu.dev/v1/senders/$SENDER_ID \ -H "Authorization: Bearer $ZAVUDEV_API_KEY" \ -H "Content-Type: application/json" \ -d '{"webhookSignatureVersion": "v1+v2"}'
The header now carries both, sharing one timestamp:
CodeX-Zavu-Signature: t=1786113454,v1=52a50333…,v2=b4b2b61c…
Your current receiver reads v1 and is unaffected. Nothing about your
deliveries changes.
2. Verify v2, and confirm it in your own logs
Prefer v2 when it is present, fall back to v1. The same code then works
before, during, and after the move:
JavaScriptconst parts = {}; for (const piece of header.split(",")) { const i = piece.indexOf("="); if (i > 0) parts[piece.slice(0, i)] = piece.slice(i + 1); } const received = parts.v2 ?? parts.v1; const signedPayload = parts.v2 ? `${parts.t}.${rawBody}` : rawBody; const expected = crypto .createHmac("sha256", secret) .update(signedPayload) .digest("hex");
Full examples in five languages are in Security.
Deploy it, then wait for real deliveries and confirm they are being accepted.
This is the step that matters. Do not skip to step 3 on the strength of a
test request: a receiver that returns 200 before it verifies looks identical
to a working one from our side.
3. Turn v1 off
terminalcurl -X PATCH https://api.zavu.dev/v1/senders/$SENDER_ID \ -d '{"webhookSignatureVersion": "v2"}'
From the next delivery the header carries v2 only.
Rolling back
Set v1+v2 again. Both signatures return immediately, so a receiver that reads
either one keeps working while you sort things out.
terminalcurl -X PATCH https://api.zavu.dev/v1/senders/$SENDER_ID \ -d '{"webhookSignatureVersion": "v1+v2"}'
Several senders, one endpoint
If one endpoint serves more than one sender, move them one at a time and keep the tolerant verifier from step 2. It accepts both schemes, so senders on different settings can share a receiver for as long as you need.
When you create a new sender that points at an endpoint still verifying v1,
create it on v1+v2 explicitly:
terminalcurl -X POST https://api.zavu.dev/v1/senders \ -d '{ "name": "Support", "enableSmsOneway": true, "webhookUrl": "https://api.example.com/webhooks/zavu", "webhookSignatureVersion": "v1+v2" }'
Otherwise it defaults to v2 and that endpoint will reject its deliveries.