Skip to main content

API key events (api_key.*)

Lifecycle of the account's API keys: rotation, status change and revocation. In all of them, data is the key's public shape — identification (id, name, description, keyPrefix), source, scopes and timestamps. The token and its hash never appear in the payload: the secret is shown a single time, in the creation or rotation response, and nothing derivable from it travels through webhooks. Use keyPrefix to identify which credential it is.

Common data fields:

{
"id": "6b4d8e0f-2a3c-4d5e-9f1a-7c9e1b3d5f70",
"name": "ERP integration",
"description": "ERP invoice sync",
"keyPrefix": "kb_a1b2c3d4",
"source": "api",
"scopes": ["dunning.dashboard.charges.*"],
"lastUsedAt": "2026-07-22T18:40:11.000Z",
"expiresAt": "2026-07-30T12:00:00.000Z",
"revokedAt": null,
"createdAt": "2026-01-15T09:00:00.000Z"
}

An empty scopes means full business access (everything except administration); a restricted one grants only the permissions matching the wildcards.

api_key.rotated

Fires on rotation (POST /api/v1/api-keys/{id}/rotate). data is the old key, with two extra fields: successorId (id of the successor key, which inherits name, scopes and expiry policy) and graceUntil (end of the grace window — 7 days by default, configurable from 0 to 30 — when the old key starts answering 401). The old key's expiresAt is brought forward to graceUntil, never extended. The successor's token appears only in the rotation response, never in the webhook.

{
"event": "api_key.rotated",
"timestamp": "2026-07-23T12:00:00.000Z",
"organizationId": "0f7a3c1e-2b4d-4e6f-8a9b-1c2d3e4f5a6b",
"data": {
"id": "6b4d8e0f-2a3c-4d5e-9f1a-7c9e1b3d5f70",
"name": "ERP integration",
"description": "ERP invoice sync",
"keyPrefix": "kb_a1b2c3d4",
"source": "api",
"scopes": ["dunning.dashboard.charges.*"],
"lastUsedAt": "2026-07-22T18:40:11.000Z",
"expiresAt": "2026-07-30T12:00:00.000Z",
"revokedAt": null,
"createdAt": "2026-01-15T09:00:00.000Z",
"successorId": "9c1e3f5a-7b2d-4c6e-8a0f-1d3f5b7c9e21",
"graceUntil": "2026-07-30T12:00:00.000Z"
}
}

Subscribe to this event to know a secret swap is in progress across your integrations — after graceUntil, calls with the old key fail.

api_key.status_changed

Fires on a key status transition, with previousStatus and newStatus on top of the common fields. Today it is emitted on revocation ("active""revoked"), alongside the terminal fact api_key.revoked; handle it tolerantly — other transitions may be added.

{
"event": "api_key.status_changed",
"timestamp": "2026-07-23T12:00:00.000Z",
"organizationId": "0f7a3c1e-2b4d-4e6f-8a9b-1c2d3e4f5a6b",
"data": {
"id": "6b4d8e0f-2a3c-4d5e-9f1a-7c9e1b3d5f70",
"name": "ERP integration",
"description": "ERP invoice sync",
"keyPrefix": "kb_a1b2c3d4",
"source": "api",
"scopes": ["dunning.dashboard.charges.*"],
"lastUsedAt": "2026-07-22T18:40:11.000Z",
"expiresAt": "2026-07-30T12:00:00.000Z",
"revokedAt": "2026-07-23T12:00:00.000Z",
"createdAt": "2026-01-15T09:00:00.000Z",
"previousStatus": "active",
"newStatus": "revoked"
}
}

api_key.revoked

Fires on revocation (DELETE /api/v1/api-keys/{id}) — unlike rotation, there is no grace window: the key starts answering 401 immediately. data is the common shape with revokedAt filled in.

{
"event": "api_key.revoked",
"timestamp": "2026-07-23T12:00:00.000Z",
"organizationId": "0f7a3c1e-2b4d-4e6f-8a9b-1c2d3e4f5a6b",
"data": {
"id": "6b4d8e0f-2a3c-4d5e-9f1a-7c9e1b3d5f70",
"name": "ERP integration",
"description": "ERP invoice sync",
"keyPrefix": "kb_a1b2c3d4",
"source": "api",
"scopes": ["dunning.dashboard.charges.*"],
"lastUsedAt": "2026-07-22T18:40:11.000Z",
"expiresAt": "2026-07-30T12:00:00.000Z",
"revokedAt": "2026-07-23T12:00:00.000Z",
"createdAt": "2026-01-15T09:00:00.000Z"
}
}