WebSocket Events
Real-time event delivery for asynchronous workflows. Connect via WebSocket, subscribe to resources, and receive push notifications as they change state.
Connection
Connect to the Centro WebSocket endpoint using any standard WebSocket client:
wss://events.api.centro.3degrees.xyz
The connection is a standard WebSocket handshake over TLS. Once connected, the socket stays open until you close it or the server disconnects (e.g. idle timeout).
Authentication
Both X-API-Key and X-Tenant-ID are required on the
WebSocket handshake. The connection is rejected with HTTP 401 if either is missing.
Option 1 — HTTP Headers (recommended)
Best for server-to-server clients where you can set custom headers on the handshake.
| Header | Description |
|---|---|
X-API-Key | Your Centro API key |
X-Tenant-ID | Your tenant identifier |
Option 2 — Query Parameters
Required for browser clients, which cannot set custom headers on the WebSocket handshake.
| Parameter | Description |
|---|---|
x-api-key | Your Centro API key |
x-tenant-id | Your tenant identifier |
wss://events.api.centro.3degrees.xyz?x-api-key=YOUR_API_KEY&x-tenant-id=YOUR_TENANT_ID
Subscription Protocol
After connecting, send JSON messages to subscribe or unsubscribe from specific resources. Each subscription targets a single resource identified by a resource key.
Subscribe
{
"action": "subscribe",
"resourceKey": "payout-3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
Unsubscribe
{
"action": "unsubscribe",
"resourceKey": "payout-3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
Resource Key Formats
| Resource | Key format | Example |
|---|---|---|
| Payout | payout-{id} |
payout-3fa85f64-5717-4562-b3fc-2c963f66afa6 |
| Quote Request | quote-request-{id} |
quote-request-a1b2c3d4-5678-9abc-def0-123456789abc |
| Beneficiary | beneficiary-{id} |
beneficiary-f0e1d2c3-b4a5-6789-0abc-def012345678 |
| Transfer | transfer-{id} |
transfer-abcd1234-ef56-7890-abcd-ef1234567890 |
Payout Events
Subscribe with payout-{id} to receive payout lifecycle events.
Payout events include enriched payloads with status, provider reference,
failure reason, and timestamp.
Payload Schema
| Field | Type | Description |
|---|---|---|
event | string | The domain event type (e.g. PayoutCompleted) |
payoutId | string (UUID) | The payout identifier |
status | string | null | Current status: pending, processing, completed, failed, cancelled, refunded |
provider_reference | string | null | The payment provider's transaction reference, if assigned |
failure_reason | string | null | Human-readable reason for failure, if applicable |
timestamp | string (ISO 8601) | When the event occurred, in UTC |
Example: PayoutCompleted
{
"event": "PayoutCompleted",
"payoutId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "completed",
"provider_reference": "wise-txn-abc123",
"failure_reason": null,
"timestamp": "2026-03-25T10:00:00.0000000Z"
}
Example: PayoutFailed
{
"event": "PayoutFailed",
"payoutId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "failed",
"provider_reference": null,
"failure_reason": "Insufficient funds in account",
"timestamp": "2026-03-25T11:00:00.0000000Z"
}
Payout Lifecycle
A payout moves through a defined sequence of states from creation to a terminal outcome. Terminal states (completed failed cancelled refunded) indicate no further events will follow.
PayoutCreated / PayoutInitiated
│
▼
status: pending
│
▼
PayoutAwaitingFunding / PayoutPending
│
▼
status: pending → processing
│
▼
PayoutProcessing
│
▼
status: processing
│
┌─┴──────────┐
▼ ▼
PayoutCompleted PayoutFailed / PayoutExecutionFailed
│ │
▼ ▼
status: completed status: failed
Event Reference
| Domain Event | Status | Notes |
|---|---|---|
PayoutCreated | pending | Payout record created |
PayoutInitiated | pending | Submission to provider initiated |
PayoutAwaitingFunding | pending | Waiting for funds to settle |
PayoutPending | pending | Provider has received the request |
PayoutProcessing | processing | Provider is actively processing |
PayoutStatusChanged | varies | Provider status update |
PayoutFundingStatusChanged | processing | Funding status update (intermediate) |
PayoutCompleted | completed | Terminal: payout delivered |
PayoutExecuted | completed | Terminal: provider confirmed execution |
PayoutFailed | failed | Terminal: payout failed |
PayoutExecutionFailed | failed | Terminal: provider execution failure |
PayoutFailureDetailReceived | failed | Additional failure detail from provider |
PayoutCancelled | cancelled | Terminal: cancelled before execution |
PayoutRefundReceived | refunded | Terminal: funds returned |
Quote Request Events
Subscribe with quote-request-{id} to receive notifications when
individual quotes arrive and when the overall request completes. Two event types
are pushed:
QuoteReceived
Pushed each time a provider returns a quote for the request.
| Field | Type | Description |
|---|---|---|
event | string | Always "QuoteReceived" |
quoteId | string (UUID) | The individual quote identifier |
requestId | string (UUID) | The parent quote request identifier |
{
"event": "QuoteReceived",
"quoteId": "b2c3d4e5-f678-9012-3456-789abcdef012",
"requestId": "a1b2c3d4-5678-9abc-def0-123456789abc"
}
QuoteRequestCompleted
Pushed when all providers have responded or timed out.
| Field | Type | Description |
|---|---|---|
event | string | Always "QuoteRequestCompleted" |
quoteRequestId | string (UUID) | The quote request identifier |
totalQuotes | integer | Total number of quotes received |
failedProviderCount | integer | Number of providers that failed or timed out |
completionStatus | string | "Completed" or "Failed" |
{
"event": "QuoteRequestCompleted",
"quoteRequestId": "a1b2c3d4-5678-9abc-def0-123456789abc",
"totalQuotes": 3,
"failedProviderCount": 1,
"completionStatus": "Completed"
}
QuoteRequestCompleted, call
GET /v1/quotes?quote_request_id={id} to retrieve the complete
set of quotes and compare offers.
Beneficiary Events
Subscribe with beneficiary-{id} to receive creation status updates.
The payload is minimal — use the beneficiary ID to fetch the full record.
| Field | Type | Description |
|---|---|---|
event | string | The domain event type (e.g. BeneficiaryCreated) |
beneficiaryId | string (UUID) | The beneficiary identifier |
{
"event": "BeneficiaryCreated",
"beneficiaryId": "f0e1d2c3-b4a5-6789-0abc-def012345678"
}
Transfer Events
Subscribe with transfer-{id} to receive status updates.
The payload is minimal — use the transfer ID to fetch the full record.
| Field | Type | Description |
|---|---|---|
event | string | The domain event type (e.g. TransferStatusUpdated) |
transferId | string (UUID) | The Centro transfer identifier |
{
"event": "TransferStatusUpdated",
"transferId": "abcd1234-ef56-7890-abcd-ef1234567890"
}
Client Implementation Notes
Reconnection
The WebSocket connection may be closed by the server or network at any time. Re-establish the connection and re-subscribe on reconnect. Subscriptions are not persisted across connections.
At-Least-Once Delivery
Events are delivered on a best-effort basis. If a push fails (e.g. the
connection is gone), that event is not retried. Design your client to
handle missing events by polling the relevant GET endpoint
as a fallback if you require a guaranteed final state.
GET /v1/payouts/{id} to check the current status.
Duplicate Events
Under rare conditions (e.g. provider retry), you may receive duplicate events
for the same state transition. Use the timestamp and
status/event fields to deduplicate on the client side.
Multiple Subscriptions
A single connection can subscribe to multiple resources simultaneously. Send one subscribe message per resource key. There is no limit on the number of concurrent subscriptions per connection.
Full Example
End-to-end example: submit a payout and listen for its completion via WebSocket.
JavaScript / Node.js
const WebSocket = require("ws");
// 1. Connect with authentication
const ws = new WebSocket(
"wss://events.api.centro.3degrees.xyz",
{ headers: {
"X-API-Key": process.env.CENTRO_API_KEY,
"X-Tenant-ID": process.env.CENTRO_TENANT_ID
}}
);
ws.on("open", () => {
console.log("Connected");
// 2. Subscribe to a payout
ws.send(JSON.stringify({
action: "subscribe",
resourceKey: "payout-3fa85f64-5717-4562-b3fc-2c963f66afa6"
}));
});
ws.on("message", (data) => {
const event = JSON.parse(data);
console.log(`Event: ${event.event}, Status: ${event.status}`);
// 3. React to terminal states
const terminal = ["completed", "failed", "cancelled", "refunded"];
if (terminal.includes(event.status)) {
console.log(`Payout reached terminal state: ${event.status}`);
ws.close();
}
});
ws.on("close", () => console.log("Disconnected"));
ws.on("error", (err) => console.error("WebSocket error:", err));
Python
import asyncio, json, os
import websockets
async def listen():
uri = "wss://events.api.centro.3degrees.xyz"
headers = {
"X-API-Key": os.environ["CENTRO_API_KEY"],
"X-Tenant-ID": os.environ["CENTRO_TENANT_ID"],
}
async with websockets.connect(uri, extra_headers=headers) as ws:
# Subscribe to a payout
await ws.send(json.dumps({
"action": "subscribe",
"resourceKey": "payout-3fa85f64-5717-4562-b3fc-2c963f66afa6"
}))
# Listen for events
async for message in ws:
event = json.loads(message)
print(f"Event: {event['event']}, Status: {event.get('status')}")
if event.get("status") in ("completed", "failed", "cancelled", "refunded"):
print(f"Terminal state reached: {event['status']}")
break
asyncio.run(listen())
cURL (connection test)
# Verify connectivity (requires wscat or websocat)
wscat -c "wss://events.api.centro.3degrees.xyz" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-Tenant-ID: YOUR_TENANT_ID"