# Event envelope

> Every Fingerly webhook is an HTTPS POST with the same headers, the same signature scheme and the same versioned JSON envelope around its data.

Last updated: 2026-09-17

Every webhook Fingerly sends has the same shape: four headers you can act on, and a JSON envelope whose `data` depends on the event type.

## Event types

| Type | Sent when | Environments |
| --- | --- | --- |
| [`identification.completed`](https://docs.fingerly.io/reference/webhooks/identification-completed) | Any identification finishes. | `live`, `test` |
| [`visitor.suspect`](https://docs.fingerly.io/reference/webhooks/visitor-suspect) | An identification reaches the `high` level. | `live`, `test` |
| [`identification.refused`](https://docs.fingerly.io/reference/webhooks/identification-refused) | An identify request is refused. | `live`, `test` |
| [`billing.status_changed`](https://docs.fingerly.io/reference/webhooks/billing-status-changed) | Your organization starts or stops accepting traffic. | `live` |
| [`usage.daily_settled`](https://docs.fingerly.io/reference/webhooks/usage-daily-settled) | A day of usage is settled. | `live`, `test` |

`webhook.test` is sent only when you choose **Send test event** for an endpoint. You cannot subscribe to it, and its `data` holds only `endpoint_id`. Ignore event types you do not handle.

## Headers

- `Content-Type` (string): `application/json`.
- `User-Agent` (string): `Fingerly-Webhooks/1.0`.
- `X-Fingerly-Event-ID` (string): The envelope's `id`.
- `X-Fingerly-Event-Type` (string): The envelope's `type`, so you can route before parsing.
- `X-Fingerly-Timestamp` (string): Unix seconds when this attempt was signed. It changes on every retry.
- `X-Fingerly-Signature` (string): `sha256=` followed by the lowercase hex HMAC-SHA256 signature. While a [secret is rotated](https://docs.fingerly.io/docs/webhooks#rotate-a-secret), one such value per secret, separated by commas.

## Signature

The signature is an HMAC-SHA256 over the timestamp, a full stop, and the raw request body, keyed with the endpoint's signing secret. Use the whole secret, `whsec_` prefix included, as the key.

```text Signature
signed_payload = X-Fingerly-Timestamp + "." + raw_body
signature      = "sha256=" + hex(hmac_sha256(signing_secret, signed_payload))
```

- Compare signatures in constant time.
- Split the header on commas and accept the delivery if any signature matches.
- Reject a timestamp more than five minutes from your clock.
- Verify the exact bytes you received. Parsing and re-serialising the JSON changes them.

## Envelope

- `id` (string): The event's ID, a UUIDv7. The same on every retry: deduplicate on it. Also sent as `X-Fingerly-Event-ID`.
- `type` (string): Always `<event type>`.
- `version` (integer): The envelope version, `1`.
- `organization_id` (string): Your organization.
- `environment` (string): `live` for production traffic, `test` for staging and development.
- `created_at` (string): When the underlying fact happened, RFC 3339 in UTC. Not when it was delivered.
- `data` (object): The event's data. Its fields are listed on each event's page.

## Compatibility

- New fields may be added to `data` and to the envelope within version `1`. Ignore fields you do not know.
- New event types are only delivered to endpoints that subscribe to them.
- Do not rely on the order of JSON keys.

> **Tip:** Route on `X-Fingerly-Event-Type` and store the raw body first. Processing it from your own queue keeps your endpoint fast and lets you replay events after a bug fix.

## Headers

```http Headers
POST /webhooks/fingerly HTTP/1.1
Content-Type: application/json
User-Agent: Fingerly-Webhooks/1.0
X-Fingerly-Event-ID: 01a0a851-0c4e-7f23-b8d1-6e2f94c0a7b5
X-Fingerly-Event-Type: identification.completed
X-Fingerly-Timestamp: 1789551672
X-Fingerly-Signature: sha256=6f1c0a3e9b…
```

## Payload

```json Body
{
  "id": "01a0a851-0c4e-7f23-b8d1-6e2f94c0a7b5",
  "type": "identification.completed",
  "version": 1,
  "organization_id": "01a0a7f2-3c18-7b40-8d2e-5f6a9b1c0d37",
  "environment": "live",
  "created_at": "2026-09-16T09:41:12Z",
  "data": {
    "request_id": "01a0a84b-e6a2-7c09-9f51-0b3d7a26c8e4",
    "…": "…"
  }
}
```
