Skip to main content
Every webhook delivery from KeyPort includes a signed JSON payload. You should verify the signature before trusting or processing the payload.
Always verify the X-KeyPort-Signature header before processing a webhook payload. Skipping verification exposes your endpoint to spoofed requests from unauthorized sources.

Request headers

KeyPort includes the following HTTP headers with every delivery:
Content-Type: application/json
X-KeyPort-Event: license.created
X-KeyPort-Timestamp: 1710000000
X-KeyPort-Signature: hmac-sha256=abc123...
HeaderDescription
X-KeyPort-EventThe event type that triggered this delivery.
X-KeyPort-TimestampUnix timestamp of when the event occurred.
X-KeyPort-SignatureHMAC-SHA256 signature used to verify the payload.

Payload structure

The request body is a JSON object with the following shape:
{
  "event": "license.created",
  "timestamp": "2026-04-07T00:00:00.000Z",
  "data": {
    "organization_id": "org_uuid",
    "product_id": "product_uuid"
  }
}
FieldDescription
eventThe event type that fired. Matches the X-KeyPort-Event header.
timestampISO 8601 timestamp of when the event occurred.
data.organization_idThe UUID of your KeyPort organization.
data.product_idThe UUID of the product associated with the event.

Verifying the signature

The X-KeyPort-Signature header contains an HMAC-SHA256 signature. To verify it, concatenate the X-KeyPort-Timestamp value, a literal ., and the raw request body, then compute the HMAC using your whsec_... signing secret.
import crypto from 'crypto';

function verifyWebhook(
  rawBody: string,
  timestamp: string,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  return `hmac-sha256=${expected}` === signature;
}
Pass the raw request body string — not a parsed JSON object — to the function. Parsing and re-serializing the body can change whitespace or key order and cause the signature check to fail.