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

# Webhook events

# Webhook Events

## Event envelope

All webhook events use a JSON envelope:

```json theme={null}
{
  "id": "event-id",
  "type": "transfer.completed",
  "created": "2026-04-29T23:30:00.000000Z",
  "data": {
    "id": "resource-id",
    "status": "complete"
  }
}
```

| Field     | Type   | Description                                                                                                                   |
| --------- | ------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `id`      | string | Stable event identifier (the event ID). Use this for deduplication. This is not the same as `data.id`.                        |
| `type`    | string | Event type, such as `transfer.completed`.                                                                                     |
| `created` | string | UTC timestamp when the event was created.                                                                                     |
| `data`    | object | Event-specific payload. `data.id` is the resource ID (e.g., transfer ID or payment ID), which may differ from the event `id`. |

Your handler should branch on `type`.

```tsx theme={null}
switch (event.type) {
  case "transfer.completed":
    await handleTransferCompleted(event.data);
    break;
  case "transfer.failed":
    await handleTransferFailed(event.data);
    break;
  case "payment.completed":
    await handlePaymentCompleted(event.data);
    break;
  default:
    // Unknown future event type.
    // Store and acknowledge safely.
    break;
}
```

## `transfer.created`

Emitted when a Brale transfer is created.

Example:

```json theme={null}
{
  "id": "3D4ExampleEventId",
  "type": "transfer.created",
  "created": "2026-04-29T23:30:00.000000Z",
  "data": {
    "id": "3D4ExampleTransferId",
    "status": "pending",
    "amount": {
      "value": "100.00",
      "currency": "USD"
    }
  }
}
```

Notes:

* Treat `data.id` as the transfer ID.
* Additional fields may be present as the transfer schema evolves.
* Your integration should ignore unknown fields.

## `transfer.completed`

Emitted when a Brale transfer reaches `complete` status.

Use this event to replace polling transfer status.

Example:

```json theme={null}
{
  "id": "3D4ExampleEventId",
  "type": "transfer.completed",
  "created": "2026-04-29T23:30:00.000000Z",
  "data": {
    "id": "3D4ExampleTransferId",
    "status": "complete",
    "amount": {
      "value": "100.00",
      "currency": "USD"
    }
  }
}
```

Notes:

* Treat `data.id` as the transfer ID.
* Treat `data.status` as the completed transfer state.
* Additional fields may be present as the transfer schema evolves.
* Your integration should ignore unknown fields.

## `transfer.failed`

Emitted when a Brale transfer reaches `failed` status.

Use this event to react to permanent transfer failures (e.g., ACH returns or other rail/provider failures). For ACH-specific return details, inspect `data.failure.ach_return`.

Example:

```json theme={null}
{
  "id": "3D4ExampleEventId",
  "type": "transfer.failed",
  "created": "2026-04-29T23:30:00.000000Z",
  "data": {
    "id": "3D4ExampleTransferId",
    "status": "failed",
    "amount": {
      "value": "100.00",
      "currency": "USD"
    },
    "failure": {
      "type": "ach_return",
      "occurred_at": "2026-04-29T23:29:55.000000Z",
      "retriable": false,
      "ach_return": {
        "code": "R01",
        "reason": "Insufficient Funds",
        "category": "administrative"
      }
    }
  }
}
```

Notes:

* Treat `data.id` as the transfer ID.
* Treat `data.status` as the failed transfer state.
* `data.failure` is usually populated for `transfer.failed`, but may be `null` when Brale does not have structured failure details. Always null-check before reading nested fields.
* `data.failure.retriable` indicates whether the failure is retriable. Spelled `retriable`, not `retryable`.
* `data.failure.ach_return` is only set for ACH return failures. See [the transfer `failure` field](/api-reference/brale/get-transfer#failure) for the full schema.
* Additional fields may be present as the transfer schema evolves.
* Your integration should ignore unknown fields.

## `payment.completed`

Emitted when a payment reaches `complete` status.

Example:

```json theme={null}
{
  "id": "3D4ExamplePaymentId",
  "type": "payment.completed",
  "created": "2026-04-28T21:30:00.000000Z",
  "data": {
    "id": "3D4ExamplePaymentId",
    "status": "complete",
    "amount": {
      "value": "1000.00",
      "currency": "USD"
    },
    "direction": "inbound",
    "type": "wire",
    "inserted_at": "2026-04-28T20:00:00.000000Z",
    "updated_at": "2026-04-28T21:30:00.000000Z"
  }
}
```

## `transfer.canceled`

Emitted when a Brale transfer is canceled.

Example:

```json theme={null}
{
  "id": "3D4ExampleEventId",
  "type": "transfer.canceled",
  "created": "2026-04-29T23:30:00.000000Z",
  "data": {
    "id": "3D4ExampleTransferId",
    "status": "canceled",
    "amount": {
      "value": "100.00",
      "currency": "USD"
    }
  }
}
```

Notes:

* Treat `data.id` as the transfer ID.
* Treat `data.status` as the canceled transfer state.
* Additional fields may be present as the transfer schema evolves.
* Your integration should ignore unknown fields.

## `account.verification.documents_required`

Emitted when Brale's KYB review determines one or more documents must be uploaded.

Use this event to prompt the account holder to submit the requested documents. For each document with `status: "pending_upload"`, [stage the file](/api-reference/brale/stage-verification-document) (use `document_kind` and `person_label` from the payload), then [link staged submissions](/api-reference/brale/link-verification-documents). You can also [poll required documents](/api-reference/brale/list-verification-documents) instead of using this webhook.

Example:

```json theme={null}
{
  "id": "3Ar9BnQCKIrB3SYjKGBzCtFs6XL",
  "type": "account.verification.documents_required",
  "created": "2026-06-09T14:32:00Z",
  "data": {
    "account_id": "3Ar9BnQCKIrB3SYjKGBzCtFs6XL",
    "updated_at": "2026-06-09T14:32:00Z",
    "documents": [
      {
        "document_kind": "certificate_of_good_standing",
        "document_type": "business_document",
        "display_name": "Certificate of Good Standing",
        "status": "pending_upload",
        "person_label": null
      },
      {
        "document_kind": "government_photo_id",
        "document_type": "individual_document",
        "display_name": "Government-issued photo ID",
        "status": "pending_upload",
        "person_label": "Jane Doe"
      }
    ]
  }
}
```

Notes:

* Treat `data.account_id` as the account ID.
* Each entry in `data.documents` describes a required document. `status` is `pending_upload` until the document is submitted.
* Include `person_label` when staging individual (KYC) documents.
* Your integration should ignore unknown fields.

## `account.verification.completed`

Emitted when account verification completes and the account is enabled.

Example:

```json theme={null}
{
  "id": "3Ar9BnQCKIrB3SYjKGBzCtFs6XL",
  "type": "account.verification.completed",
  "created": "2026-06-09T16:00:00Z",
  "data": {
    "account_id": "3Ar9BnQCKIrB3SYjKGBzCtFs6XL",
    "status": "complete",
    "updated_at": "2026-06-09T16:00:00Z"
  }
}
```

Notes:

* Treat `data.account_id` as the account ID.
* Treat `data.status` as the completed verification state (`complete`).
* This event fires only when the account reaches `complete` status.
* Your integration should ignore unknown fields.

## Handling future event types

Brale may add new event types over time.

Best practices:

* Use `GET /webhooks/event_types` to discover supported events.
* Branch on `event.type`.
* Acknowledge unknown event types safely.
* Ignore unknown fields in `data`.
* Do not assume event ordering.
