Receiver sequence
- Read the raw request body without normalizing JSON first.
- Require the Lucion webhook ID, timestamp, and signature headers.
- Reject timestamps outside the five-minute window.
- Verify HMAC-SHA256 over timestamp.event_id.raw_body with constant-time comparison.
- Deduplicate by event ID plus subscription ID, then apply the side effect.
Verification outline
const rawBody = await request.text();
const eventId = request.headers.get("X-Lucion-Webhook-Id");
const timestamp = request.headers.get("X-Lucion-Webhook-Timestamp");
const signature = request.headers.get("X-Lucion-Webhook-Signature");
const valid = await verifyWebhookSignature(signature, { secret, eventId, timestamp, rawBody });
// Verify the raw payload before JSON.parse and before side effects.
// Store eventId + subscriptionId as the idempotency key.