Skip to content

Migrating to v2 signatures

Move a webhook to the current signature scheme without downtime

View .md

Zavu signs webhooks with HMAC-SHA256. There are two schemes, and each receiver is on one of them:

SchemeWhat is signed
v1the request body
v2the 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

terminal
curl 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

terminal
curl -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:

Code
X-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:

JavaScript
const 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

terminal
curl -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.

terminal
curl -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:

terminal
curl -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.

Next Steps