Copy page
Verifying Requests
Every webhook request includes an aktify-signature header. Use this to verify that the request originated from Aktify and hasn't been tampered with.
Signature Format
The signature format depends on the webhook version:
| Version | Format | Description |
|---|---|---|
| v1 (Legacy) | t=<unix_timestamp_ms>,v1=<signature> | HMAC computed over the request body only |
| v2 | t=<unix_timestamp_ms>,v2=<signature> | HMAC computed over the timestamp and request body |
| Component | Description |
|---|---|
t | Unix timestamp (milliseconds) when the request was sent |
v1 | HMAC SHA-256 hex digest of JSON.stringify(body) |
v2 | HMAC SHA-256 hex digest of ${timestamp}.${JSON.stringify(body)} |
The version component (v1 or v2) tells you which verification method to use. Both versions use your client secret as the HMAC key.
v1 Verification (Legacy Events)
v1 signatures sign only the request body. The timestamp is included in the header but is not part of the signed payload, so you should independently check for stale timestamps to guard against replay attacks.
const crypto = require('crypto');
function verifyV1Webhook(requestBody, signatureHeader, clientSecret) {
const [tPart, v1Part] = signatureHeader.split(',');
const timestamp = tPart.split('=')[1];
const signature = v1Part.split('=')[1];
const expectedSignature = crypto
.createHmac('sha256', clientSecret)
.update(JSON.stringify(requestBody))
.digest('hex');
if (signature !== expectedSignature) {
throw new Error('Invalid signature - request may not be from Aktify');
}
// Recommended: reject requests older than 5 minutes to prevent replay attacks
const fiveMinutesAgo = Date.now() - (5 * 60 * 1000);
if (parseInt(timestamp) < fiveMinutesAgo) {
throw new Error('Request timestamp too old');
}
return true;
}
v2 Verification
v2 signatures include the timestamp in the HMAC input, cryptographically binding the timestamp to the payload. This means any modification to the timestamp invalidates the signature, providing built-in replay protection.
const crypto = require('crypto');
function verifyV2Webhook(requestBody, signatureHeader, clientSecret) {
const [tPart, v2Part] = signatureHeader.split(',');
const timestamp = tPart.split('=')[1];
const signature = v2Part.split('=').slice(1).join('=');
const expectedSignature = crypto
.createHmac('sha256', clientSecret)
.update(`${timestamp}.${JSON.stringify(requestBody)}`)
.digest('hex');
if (signature !== expectedSignature) {
throw new Error('Invalid signature - request may not be from Aktify');
}
// Reject requests older than 5 minutes
const fiveMinutesAgo = Date.now() - (5 * 60 * 1000);
if (parseInt(timestamp) < fiveMinutesAgo) {
throw new Error('Request timestamp too old');
}
return true;
}