> ## Documentation Index
> Fetch the complete documentation index at: https://docs.keyport.sbs/llms.txt
> Use this file to discover all available pages before exploring further.

# Payloads & Signing

> Webhook delivery headers, payload structure, and how to verify signatures.

Every webhook delivery from KeyPort includes a signed JSON payload. You should verify the signature before trusting or processing the payload.

<Warning>
  Always verify the `X-KeyPort-Signature` header before processing a webhook payload. Skipping verification exposes your endpoint to spoofed requests from unauthorized sources.
</Warning>

## Request headers

KeyPort includes the following HTTP headers with every delivery:

```http theme={"theme":{"light":"github-light","dark":"github-dark"}}
Content-Type: application/json
X-KeyPort-Event: license.created
X-KeyPort-Timestamp: 1710000000
X-KeyPort-Signature: hmac-sha256=abc123...
```

| Header                | Description                                       |
| --------------------- | ------------------------------------------------- |
| `X-KeyPort-Event`     | The event type that triggered this delivery.      |
| `X-KeyPort-Timestamp` | Unix timestamp of when the event occurred.        |
| `X-KeyPort-Signature` | HMAC-SHA256 signature used to verify the payload. |

## Payload structure

The request body is a JSON object with the following shape:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "event": "license.created",
  "timestamp": "2026-04-07T00:00:00.000Z",
  "data": {
    "organization_id": "org_uuid",
    "product_id": "product_uuid"
  }
}
```

| Field                  | Description                                                      |
| ---------------------- | ---------------------------------------------------------------- |
| `event`                | The event type that fired. Matches the `X-KeyPort-Event` header. |
| `timestamp`            | ISO 8601 timestamp of when the event occurred.                   |
| `data.organization_id` | The UUID of your KeyPort organization.                           |
| `data.product_id`      | The 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.

<CodeGroup>
  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  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;
  }
  ```

  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const crypto = require('crypto');

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

    return `hmac-sha256=${expected}` === signature;
  }
  ```
</CodeGroup>

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.
