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

# Webhooks

> Receive signed callbacks for state changes.

Keette POSTs JSON events to your HTTPS endpoint when something changes in that environment. Payloads are built for **business automation**: amounts, fees, rates, rails, beneficiary details, and a transaction timeline ship inside `data` so you can act without a follow-up `GET`.

## Setup

1. Open **Settings → Developers → Webhooks**
2. Set a URL for **live** and/or **sandbox**
3. Subscribe to event types from the catalog
4. Store the signing secret (shown once; regenerate anytime)
5. Optionally send a **test ping** from the dashboard

## Envelope

Every delivery uses the same outer shape. Metadata comes first; **`data` is always the last key**.

```json theme={null}
{
  "id": "4d5d3ac2-3599-4241-801b-69f9912a1b2c",
  "object": "event",
  "type": "event.payout.processing",
  "created": 1730000000,
  "livemode": false,
  "environment": "sandbox",
  "data": { }
}
```

| Field | Meaning |
| - | - |
| `id` | Durable event id (also usable with `GET /v1/events/{id}`) |
| `object` | Always `event` |
| `type` | Catalog name, e.g. `event.payout.completed` |
| `created` | Unix seconds (UTC) |
| `livemode` | `true` when the workspace environment is live |
| `environment` | `live` or `sandbox` |
| `data` | Resource payload for that event |

## Money in `data`

Webhook money fields are **JSON numbers** in major units, always written with **6 decimal places**:

* `amount`, `fee`, `transfer_fee`, `total_debit` → `200.000000`, `0.310000`, …
* Each has a companion integer: `amount_minor`, `fee_minor`, `transfer_fee_minor`, `total_debit_minor` (Ledger minor units)
* `scale` is always present (usually `2`; stablecoins use `6`)

Use the float fields when displaying or reconciling against the product UI. Use `*_minor` when you need exact integer math.

## What else is in `data`

Regardless of event type, money events aim to include enough fields to drive decisions:

* **Identity** — `object`, `id`, `business_id`, `stan`, `status`
* **Rates** — `fx_rate`, `rate` (human label like `1 USD = 1,523.0769 NGN`), and a `rates` object
* **Rails** — `method` (e.g. `Bank transfer · NIP`), `rail`, `channel`, `scheme`
* **Counterparty** — full `beneficiary` (or `sender` on payins), including routing where stored
* **Documents** — on payouts with supporting files, `documents[]` with signed S3 `url` + `url_expires_at` (24h)
* **Timeline** — `timeline.steps` with stage, label, state (`done` / `current` / `upcoming` / `failed`), and timestamps

See [Event types](https://docs.keette.com/webhooks/events.md) for per-type examples.

## Guarantees

* Successful **non-GET** state changes enqueue a durable event
* Deliveries are signed ([Signatures](https://docs.keette.com/webhooks/signatures.md))
* Failed deliveries retry ([Retries](https://docs.keette.com/webhooks/retries.md))
* Events can be polled with `GET /v1/events`, fetched by id with `GET /v1/events/{id}`, and pinged with `POST /v1/ping`
* Money events are idempotent on `(tenant, environment, type, data.id)` so partner settle retries do not duplicate deliveries


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.