Launch offer: 50% off.Paid plans only.See pricing
Skip to content

Documentation

Reference

Webhook Signature Verification

Headers, timestamp tolerance, replay protection, and verification examples.


Headers

Lamba webhook requests include:

  • X-Lamba-Event
  • X-Lamba-Tenant
  • X-Lamba-Timestamp
  • X-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

  1. Read the raw request body exactly as received.
  2. Read timestamp and signature headers.
  3. Recompute the expected signature using your webhook secret.
  4. Compare using constant-time comparison.
  5. Reject timestamps older than tolerance. Recommended tolerance: 5 minutes.
  6. Deduplicate on the webhook body id field.

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 id as the idempotency key.
  • Reject duplicate event IDs for a defined retention window.

Test vectors

Use deterministic vectors in tests:

  • timestamp: 1710000000
  • rawBody: {\"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.