Skip to main content

Event envelope

All webhook events use a JSON envelope:

Managing accounts and event fan-in

If your integration uses managed accounts or individual accounts, a single managing account can receive events for every account it manages.
  • A webhook subscription owned by a managing account receives events for every account it manages, plus its own.
  • A webhook subscription owned by a managed account receives only that account’s own events.
This is derived from the subscription’s owner. There is no scope parameter to set when creating the subscription. To attribute an event to the correct managed account, read data.account_id from the payload. It is present on payment.completed, address.created, address.ready, transfer.created, transfer.failed, transfer.refunding, and transfer.refunded, in addition to the account.verification.* events that already carry it. Your handler should branch on type.

transfer.created

Emitted when a Brale transfer is created. Example:
Notes:
  • Treat data.id as the transfer ID.
  • data.account_id identifies the account the transfer belongs to. Managing accounts use this to attribute events to a specific managed account.
  • 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:
Notes:
  • Treat data.id as the transfer ID.
  • Treat data.status as the completed transfer state. The event name is transfer.completed; the API status is complete.
  • For outbound domestic wires and outbound ACH payouts, transfer.completed is not a guarantee that the payout cannot be returned later. If the bank returns the payout, the same transfer moves to failed and Brale emits transfer.failed. Keep accepting transfer.failed for transfers you recorded as complete.
  • 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, outbound domestic wire returns, or other rail/provider failures). For ACH-specific return details, inspect data.failure.ach_return. For wire return details, inspect data.failure.wire_return. Example:
Notes:
  • Treat data.id as the transfer ID.
  • data.account_id identifies the account the transfer belongs to. Managing accounts use this to attribute events to a specific managed account.
  • 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 for the full schema.
  • Additional fields may be present as the transfer schema evolves.
  • Your integration should ignore unknown fields.

Returned outbound wires

Brale uses the existing transfer.failed event for returned outbound domestic wires. There is no separate returned event. If Brale later refunds the payout, it reports the refund with the separate refund events transfer.refunding and transfer.refunded. The same transfer can produce transfer.completed and later transfer.failed if the wire is returned after completion. Example (illustrative, not an observed production payload):
How to handle it:
  • Read data.status for the payout state. Read data.failure.type for the failure classification when data.failure is present.
  • When data.failure.type is wire_return, read data.failure.wire_return, not data.failure.ach_return.
  • Do not require an ACH return code or a specific bank reason code to recognize a wire return. data.failure.wire_return.code is WIRE_RETURN when the provider supplies no code, and data.failure.wire_return.reason can be null.
  • Missing structured failure details do not mean the transfer succeeded. If data.status is failed, update your records even when data.failure is null.
  • transfer.failed confirms that the payout failed or was returned. It does not confirm that stablecoins were refunded or that refunded funds are available.
  • Track payout state, return classification, and any refund credit separately. Not every failed or returned payout is refunded. Use transfer.refunding and transfer.refunded to track a refund.
  • Do not assume event ordering. Retrieve the latest transfer when you need to reconcile its current state.

Refund events

Brale reports the refund lifecycle with two events in the transfer family. They are separate from transfer.failed: Refund events apply to a redemption (stablecoin to fiat payout) whose payout was returned or canceled and then refunded. They use the same transfer as the original payout, so data.id matches the data.id you received on transfer.failed or transfer.canceled. A refunded payout produces this sequence: transfer.failed (or transfer.canceled) → transfer.refunding → transfer.refunded After the refund starts, the transfer’s status is refunding and then refunded. Brale does not send transfer.completed, transfer.failed, or transfer.canceled for the transfer after that point. A failed or returned payout is not automatically followed by a refund. Do not wait for transfer.refunding or transfer.refunded after every transfer.failed. Subscriptions that use the transfer.* wildcard already receive both refund events. Subscriptions that list event types by name receive them only after you add transfer.refunding and transfer.refunded. See Subscribe to refund events.

transfer.refunding

Emitted when the refund process starts. Notes: Example (illustrative):
Notes:
  • Treat this event as “refund in progress.” It does not confirm that the refund is complete or that refunded funds are available.
  • Do not credit your end user’s balance as final based on this event alone. Wait for transfer.refunded.
  • data.status is always refunding on this event. Brale still delivers transfer.refunding if the refund completes before the event is sent, so the transfer’s current status can already be refunded.
  • created is the time the refund started.

transfer.refunded

Emitted when the refund completes. Notes: Example (illustrative):
Notes:
  • Treat this event as confirmation that the refund has completed.
  • data.status is refunded. created is the time the refund completed.
  • Delivery order is not guaranteed. You can receive transfer.refunded before transfer.refunding, or without having processed transfer.refunding.

Handle refund events

Both refund events carry the transfer resource in data: Ignore unknown fields in data. Retrieve the latest transfer when you need its current state.
How to handle them:
  • Verify the signature before you process the event.
  • Deduplicate on the event id and type together. Brale can deliver the same event more than once, and transfer.refunding and transfer.refunded for the same transfer share the same id.
  • Do not assume delivery order. Never move a refund you recorded as complete back to in progress.
  • Retrieve the latest transfer to reconcile when events arrive out of order or you are unsure of the current state.

payment.completed

Emitted when a payment reaches complete status. Example:
Notes:
  • data.account_id identifies the account the payment belongs to. Managing accounts use this to attribute events to a specific managed account.
  • data.id is a payment ID. It is not a Transfer ID. Brale creates a separate Transfer for Automation deposits.
  • payment.completed means Brale received the fiat payment. It does not mean the stablecoin mint was delivered. Use transfer.completed or the Transfer’s status for that.
  • data.type identifies the payment rail. The example shows a wire. For FedNow deposits, do not hard-code an expected data.type value. Correlate with the Transfer instead.
  • To find the Transfer for an Automation deposit, list Transfers with automation_id and transfer_type (for example, transfer_type=fednow_credit), then match data.account_id, the amount, and source.payment_details.received_at. See Receive FedNow deposits.
  • Brale also emits payment.completed for inbound payments created by Simulate an Automation deposit on testnet. There is no simulation-specific webhook type, so the same handler covers simulated and real deposits.

transfer.canceled

Emitted when a Brale transfer is canceled. Example:
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.

Address events

Brale reports new addresses with two events in the address family. Both cover wallets (internal custodial wallets and external wallets) and external bank accounts, including bank accounts linked through Plaid. Subscribe to both with address.*. An address that is created already active produces both events. A custodial wallet created as pending produces address.created first, then address.ready when it becomes active. See Address lifecycle.

address.created

Emitted when a wallet or bank account is added to an account. Example (wallet, illustrative):
Notes:
  • The event id and data.id are both the address ID.
  • created is the time the address was added.
  • data.status is the address status when Brale builds the event: pending, active, archived, or disabled. A delayed delivery can report a status other than the one the address had when it was added.
  • Do not use the address in transfers based on this event alone. Wait for address.ready or check that data.status is active.

address.ready

Emitted when a wallet or bank account reaches active status and can be used in transfers. Example (bank account, illustrative):
Notes:
  • The event id is the address ID with a :ready suffix, so it never matches the address.created event id for the same address. data.id is the address ID without the suffix.
  • created is the time the address was last updated.
  • data.status is always active on this event.

Address event payload

Both address events carry the same data object. Every field is always present. Ignore unknown fields in data. Retrieve the address with the Addresses API when you need its current state.

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 (use document_kind and person_label from the payload), then link staged submissions. You can also poll required documents instead of using this webhook. Example:
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:
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.

account.verification.rejected

Emitted when account verification is declined. This is the terminal outcome for a review: the account will not become complete on its own, and no further verification events follow. Example:
Notes:
  • Treat data.account_id as the account ID.
  • Treat data.status as the declined verification state (rejected).
  • The account’s status from GET /accounts/{account_id} becomes rejected at the same time, so polling and webhooks agree.
  • Uploading more documents does not reopen a declined account. Contact Brale if you believe a decline is incorrect.
  • Your integration should ignore unknown fields.
Subscriptions to the account.verification.* wildcard receive this event automatically. If you subscribed to account.verification.completed by name, add account.verification.rejected to your subscription to be told about declines.

Handling future event types

Brale may add new event types over time. Best practices:
  • Use GET /accounts/{account_id}/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.
  • To automatically receive future events in a family (e.g., new transfer.*, address.*, or account.* events), subscribe with a namespace wildcard like transfer.* or account.* when you create or update a subscription. Wildcards work for every event family.