Guides
WebSocket events
The BroSettlement WebSocket stream notifies an organization about durable account, wallet, deposit, and transaction changes so clients can refresh affected REST resources without relying on continuous polling.
Connect
Staging endpoint:
wss://brosettlement-staging-api.brolabel.io/v1/wsThe API key requires the websockets:read scope. Sign the handshake with the API key's Ed25519 private key:
WS_CONNECT
/v1/ws
TIMESTAMP
NONCEUse the same API key ID, Unix timestamp in seconds, nonce, and padded standard Base64 signature required by the current integration environment. The REST six-line canonical value is not valid for a WebSocket connection.
Delivery model
- Events are scoped to the API key's organization.
- A durable business event has a globally unique ID that remains stable when redelivered.
- Delivery is at least once: clients must expect duplicates.
occurredAtis the durable state-transition time, not the delivery time.correlationIdconnects REST requests, transactions, chain activity, and support logs.- Event schemas begin at version
1and evolve additively. - Unknown fields must be ignored.
- Private keys, raw signatures, seed material, user secrets, and internal MPC payloads must never appear in an event.
Event envelopes
The MVP-compatible envelope uses type, event, and eventId:
{
"type": "EVENT",
"event": "transaction.updated",
"eventId": "evt_...",
"version": 1,
"orgId": "org-id",
"occurredAt": "2026-07-07T13:38:33.000Z",
"correlationId": "request-or-flow-id",
"data": {}
}The durable topic-stream envelope uses op, subject, id, and sequence:
{
"op": "event",
"subId": "s1",
"id": "evt_...",
"sequence": 987654,
"subject": "transaction.updated",
"occurredAt": "2026-07-07T13:38:33.000Z",
"orgId": "org-id",
"correlationId": "request-or-flow-id",
"version": 1,
"data": {}
}Support both envelopes during migration when your environment can emit either form. Normalize eventId/id and event/subject before processing.
Required event families
| Event | Meaning |
|---|---|
system.connected | Authentication succeeded and the stream is ready |
system.error | Authentication, policy, topic, or payload error |
account.created | A ledger account was durably created |
wallet.created | A wallet and address were created and are readable |
wallet.creation_failed | Accepted wallet provisioning failed asynchronously |
wallet.status_changed | A wallet moved between lifecycle states |
transaction.created | A durable transaction record or intent was created |
transaction.updated | Client-visible transaction state or on-chain detail changed |
transaction.confirmed | The transaction reached successful finality |
transaction.failed | Processing failed before successful on-chain completion |
transaction.failed_on_chain | Blockchain execution failed or reverted |
deposit.observed | An incoming transfer was detected |
deposit.confirmed | The deposit met the confirmation policy |
deposit.credited | The confirmed deposit was credited to the operating ledger |
Read-only REST calls do not emit a business event merely because they were called.
Account events
account.created follows a successful POST /api/v1/ledger/accounts.
{
"accountId": "account-id",
"orgId": "org-id",
"name": "Customer 123",
"externalId": "customer-123",
"createdAt": "2026-07-07T13:00:00.000Z",
"requestId": "http-request-id",
"idempotencyKey": null
}A synchronous validation or authentication rejection that creates no account does not produce account.created.
Wallet events
wallet.created identifies the durable wallet and address:
{
"walletId": "wallet-id",
"accountId": "account-id",
"orgId": "org-id",
"chain": "tron:nile",
"address": "T...",
"status": "ACTIVE",
"createdAt": "2026-07-07T13:00:00.000Z",
"requestId": "http-request-id",
"idempotencyKey": "client-operation-id"
}If an asynchronously accepted wallet fails later, emit wallet.creation_failed with accountId, chain, status, and a machine-readable errorCode. Use wallet.status_changed for transitions such as PENDING to ACTIVE, DISABLED, ARCHIVED, or FAILED.
A synchronous 4xx rejection that creates no durable wallet does not require a business event. The REST error is the source of truth.
Transaction events
Use one transaction.* family for withdrawals and internal transfers. Do not split the lifecycle into withdrawal.*, transfer.*, or internal_transfer.* families.
The payload distinguishes the movement:
isInternalTransfer: falseandtransferKind: "EXTERNAL_WITHDRAWAL"for an external withdrawal;isInternalTransfer: trueandtransferKind: "INTERNAL_TRANSFER"for a same-organization movement.
Minimum lifecycle payload:
{
"transactionId": "transaction-id",
"transactionGroupId": null,
"sourceTransactionId": null,
"destinationTransactionId": null,
"orgId": "org-id",
"accountId": "account-id",
"walletId": "wallet-id",
"destinationAccountId": null,
"destinationWalletId": null,
"chain": "tron:nile",
"asset": "TRX",
"amountAtomic": "7000000",
"amountDirection": "DEBIT",
"isInternalTransfer": false,
"transferKind": "EXTERNAL_WITHDRAWAL",
"fromAddress": "T...",
"toAddress": "T...",
"txHash": null,
"status": "PENDING",
"sourceStatus": "PENDING",
"destinationStatus": null,
"failureCode": null,
"failureMessage": null
}transaction.updated covers reservations, MPC signing progress, broadcast, confirmation progress, on-chain details, and externally relevant failure information. Terminal events carry the final REST-visible status: CONFIRMED, FAILED, or FAILED_ON_CHAIN.
Keep amountAtomic as a base-10 string. Do not parse it through floating-point types.
Deposits
Deposits originate from blockchain observation rather than a client REST POST:
deposit.observed— the incoming transfer was detected.transaction.created— the client-visible deposit transaction exists.deposit.confirmed— required confirmations were reached.transaction.confirmed— the transaction reached terminal success.deposit.credited— the operating ledger was credited.
An implementation may combine compatible lifecycle signals, but a confirmed deposit must produce at least one deposit or transaction event. Clients must not be forced to discover a durable confirmed deposit only through polling.
Deposit data must identify transactionId when available, walletId, accountId, chain, asset, amountAtomic, txHash, block information, and confirmation progress.
Synchronous rejection and durable failure
These cases have different event behavior:
Result of POST /api/v1/transactions | WebSocket expectation |
|---|---|
Synchronous 4xx, no transaction ID, no durable record or reservation | No transaction.* business event; handle the REST error |
| Transaction ID or durable intent exists, then processing fails | Emit transaction.created, then terminal transaction.failed |
| Blockchain execution fails or reverts | Emit transaction.failed_on_chain |
Once a transaction is visible through list/detail REST endpoints, every later terminal failure must also be visible through WebSocket. A failed reservation must be voided exactly once.
For an internal transfer with paired debit and credit rows, include sourceTransactionId, destinationTransactionId, and leg statuses. A failed source leg must not leave the destination leg permanently PENDING.
REST reconciliation
Treat an event as a signal to refresh authoritative state:
| Event family | REST resources to refresh |
|---|---|
account.* | /api/v1/ledger/accounts/{accountId} |
wallet.* | /api/v1/wallets/{walletId} and account wallets |
transaction.* | /api/v1/transactions/{id}, related balances, and journal entries |
deposit.* | Transaction detail, wallet/account balances, and ledger entries |
The transactions list and transaction detail must converge to the same status. Do not display a final customer balance solely from an unverified event payload.
Listener requirements
- Log received frames without logging API private keys or signing secrets.
- Parse JSON while preserving the original payload for investigation.
- Treat
system.connectedas transport state, not a business event. - Deduplicate business events by stable
eventIdorid. - Persist
sequenceor the last event ID when resume is available. - Acknowledge durable events when the server requires explicit ack.
- Reconnect with bounded exponential backoff and jitter.
- Resume from the last committed position when supported.
- Refresh affected REST resources after
transaction.*,wallet.*, and deposit events. - Commit the resume position only after local processing succeeds.
Current staging compatibility
The event requirements document records these staging compatibility gaps:
- some deployments may still emit legacy
SYSTEM_CONNECTEDinstead ofsystem.connected; - the current
/v1/wstransport may not yet expose topics, ack, or resume; - event coverage for deposits and paired internal-transfer legs must be verified in the target environment.
Normalize the legacy connection event during migration, but do not invent missing business events client-side. Use REST reconciliation to recover state and confirm the enabled WebSocket contract before production launch.
Security
- Reject or discard an event whose
orgIddoes not match the authenticated organization. - Never place credentials or MPC signing material in logs.
- Treat event data as untrusted input and validate required fields.
- Preserve
correlationIdand RESTrequestIdfor support. - Use a dedicated least-privilege API key with
websockets:read.