Signature verification
Every webhook Osigu delivers is signed with HMAC-SHA256 using a secret Osigu generates during onboarding. Your receiver must verify the signature and reject any request where it doesn't match — or where the request is stale — otherwise anyone who guesses your webhook URL can inject fake events.
This page is the formal spec of the signature format. For an end-to-end walkthrough including framework examples, see Implement a webhook receiver.
The signature header
Every request carries:
X-Osigu-Signature: t=<unix_seconds>,v1=<hex_hmac_sha256>
t— the Unix-seconds timestamp of when Osigu signed the request.v1— the hex-encoded HMAC-SHA256 of the signed string.
Multiple v1= values may appear (comma-separated) during key rotation. Accept the request if any of them match.
What Osigu signs
The signed string is the concatenation:
<t>.<raw_request_body>
where:
<t>is the exact ASCII decimal string from the header.<raw_request_body>is the exact bytes of the HTTP body — no re-encoding, no whitespace normalisation, no JSON parsing beforehand.
The HMAC key is the webhook signing secret Osigu gave you.
Verification algorithm
1. Extract t and v1 values from X-Osigu-Signature.
2. Reject if |now - t| > 300 seconds. ← replay protection
3. signed_string = t + "." + raw_body
4. expected = hex(HMAC-SHA256(secret, signed_string))
5. Constant-time compare expected against v1.
6. If no match → reject with 401.
7. Otherwise → proceed to processing.
Constant-time compare — why it matters
A naive expected === v1 compare with a short-circuit !== is vulnerable to timing attacks. An attacker who can measure your response time can, byte by byte, guess the correct signature. Use your language's constant-time comparison primitive:
- Node.js:
crypto.timingSafeEqual - Python:
hmac.compare_digest - Go:
hmac.Equal - Java:
MessageDigest.isEqual - Ruby:
Rack::Utils.secure_compare
Replay protection — the 5-minute window
Even a valid signature is rejected if the t timestamp is more than 5 minutes off from your server's now(). This blocks replay attacks where an attacker who intercepts a legitimate delivery attempts to replay it hours later.
If your servers' clocks drift more than a few seconds, sync them with NTP. Otherwise valid deliveries will bounce off your receiver.
Key rotation
If your webhook secret leaks or you want to rotate proactively:
- Ask Osigu (email
webhooks@osigu.com) to issue a new secret. - Osigu enables dual-signing: deliveries carry
v1=<new>,v1=<old>for a 48-hour overlap. - You deploy code that accepts either — trivial if you already iterate over
v1=values. - After 48h Osigu switches to signing with only the new secret.
- You delete the old secret from your config.
Never do a hard cut-over. The dual-signing window prevents in-flight retries from failing during the rotation.
Example: Node.js verification
import crypto from 'node:crypto';
function verify(header, rawBody, secret) {
const parts = header.split(',').reduce((acc, kv) => {
const [k, v] = kv.split('=');
if (k === 'v1') (acc.v1 ||= []).push(v);
else acc[k] = v;
return acc;
}, { v1: [] });
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
const signed = `${t}.${rawBody}`;
const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex');
return parts.v1.some((sig) =>
sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))
);
}
Debug checklist
If your verification is always failing:
- Are you using the raw body bytes, not the JSON-parsed object? Middleware that parses JSON before your handler destroys the exact bytes that were signed.
- Are you concatenating with a literal dot between
tand body, no spaces? - Are you comparing hex to hex? Not base64.
- Is your secret exactly what Osigu gave you (no leading/trailing whitespace, no URL-decoded copy)?
- Is your server clock within 5 minutes of true time?
Sandbox has a test endpoint that echoes back the signed string Osigu computed — email support@osigu.com if you're stuck and we'll enable it for your account.
Related
- Implement a webhook receiver — end-to-end walkthrough with framework examples.
- Introduction — envelope and delivery guarantees.