Skip to content

Verifying webhooks

Each destination gets its own signing secret when it is created in the portal (it starts with whsec_). Use it to verify the x-outpost-signature header sent with every delivery.

  1. Take the raw request body exactly as received, before parsing or re-serialising it.
  2. Compute HMAC-SHA256 over those bytes using your destination’s signing secret, hex-encoded.
  3. The header is v0=<signature>. Compare using a constant-time comparison.
  4. If a secret is being rotated, the header carries both signatures, comma-separated — v0=<new>,<old>. Accept the request if any of them matches a secret you hold.
  5. Reject anything that fails verification.
import { createHmac, timingSafeEqual } from 'node:crypto'
function isFromLevno(rawBody, signatureHeader, secret) {
const expected = createHmac('sha256', secret).update(rawBody).digest('hex')
const received = signatureHeader.replace(/^v0=/, '').split(',')
return received.some(
(signature) =>
signature.length === expected.length &&
timingSafeEqual(Buffer.from(signature), Buffer.from(expected)),
)
}

REST API keys authenticate requests a partner sends to Levno. A webhook signing secret authenticates Events requests Levno sends to the partner. Do not reuse one credential for both directions.