Headers
Lamba webhook requests include:
X-Lamba-EventX-Lamba-TenantX-Lamba-TimestampX-Lamba-Signature
Public customer deliveries default to v1 signatures. Legacy non-versioned signatures are not part of the customer surface contract unless an endpoint is explicitly documented otherwise.
Signed payload format
Canonical payload input:
{timestamp}.{rawBody}
Signature:
v1=<lowercase-hex-hmac-sha256>
Verification steps
- Read the raw request body exactly as received.
- Read timestamp and signature headers.
- Recompute the expected signature using your webhook secret.
- Compare using constant-time comparison.
- Reject timestamps older than tolerance. Recommended tolerance: 5 minutes.
- Deduplicate on the webhook body
idfield.
Code samples
import crypto from 'node:crypto';
export function verifyLambaWebhook({
secret,
timestamp,
rawBody,
providedSignature,
}: {
secret: string;
timestamp: string;
rawBody: string;
providedSignature: string;
}) {
const payload = `${timestamp}.${rawBody}`;
const expected = `v1=${crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex')}`;
return crypto.timingSafeEqual(
Buffer.from(expected, 'utf8'),
Buffer.from(providedSignature, 'utf8')
);
}Replay protection
- Enforce timestamp tolerance: 300 seconds.
- Store the webhook event
idas the idempotency key. - Reject duplicate event IDs for a defined retention window.
Test vectors
Use deterministic vectors in tests:
timestamp:1710000000rawBody:{\"id\":\"evt_01J...\",\"type\":\"session.created\"}secret:whsec_test_123- canonical payload:
1710000000.{"id":"evt_01J...","type":"session.created"}
Verify that your implementation matches the expected signature for that canonical payload.