Centro API v1
API Reference Guides Login Talk to sales

WebSocket Events

Real-time event delivery for asynchronous workflows. Connect via WebSocket, subscribe to resources, and receive push notifications as they change state.

Why WebSocket?
Centro operations like payouts, quote requests, and beneficiary creation are asynchronous — they complete after the initial API response. Rather than polling, connect to our WebSocket endpoint and receive events the instant a resource changes 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.

HeaderDescription
X-API-KeyYour Centro API key
X-Tenant-IDYour tenant identifier

Option 2 — Query Parameters

Required for browser clients, which cannot set custom headers on the WebSocket handshake.

ParameterDescription
x-api-keyYour Centro API key
x-tenant-idYour 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

ResourceKey formatExample
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
Tip
A single connection can subscribe to multiple resources simultaneously. Send one subscribe message per resource key. All subscriptions are automatically cleaned up when the connection closes.

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

FieldTypeDescription
eventstringThe domain event type (e.g. PayoutCompleted)
payoutIdstring (UUID)The payout identifier
statusstring | nullCurrent status: pending, processing, completed, failed, cancelled, refunded
provider_referencestring | nullThe payment provider's transaction reference, if assigned
failure_reasonstring | nullHuman-readable reason for failure, if applicable
timestampstring (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 EventStatusNotes
PayoutCreatedpendingPayout record created
PayoutInitiatedpendingSubmission to provider initiated
PayoutAwaitingFundingpendingWaiting for funds to settle
PayoutPendingpendingProvider has received the request
PayoutProcessingprocessingProvider is actively processing
PayoutStatusChangedvariesProvider status update
PayoutFundingStatusChangedprocessingFunding status update (intermediate)
PayoutCompletedcompletedTerminal: payout delivered
PayoutExecutedcompletedTerminal: provider confirmed execution
PayoutFailedfailedTerminal: payout failed
PayoutExecutionFailedfailedTerminal: provider execution failure
PayoutFailureDetailReceivedfailedAdditional failure detail from provider
PayoutCancelledcancelledTerminal: cancelled before execution
PayoutRefundReceivedrefundedTerminal: 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.

FieldTypeDescription
eventstringAlways "QuoteReceived"
quoteIdstring (UUID)The individual quote identifier
requestIdstring (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.

FieldTypeDescription
eventstringAlways "QuoteRequestCompleted"
quoteRequestIdstring (UUID)The quote request identifier
totalQuotesintegerTotal number of quotes received
failedProviderCountintegerNumber of providers that failed or timed out
completionStatusstring"Completed" or "Failed"
{
  "event": "QuoteRequestCompleted",
  "quoteRequestId": "a1b2c3d4-5678-9abc-def0-123456789abc",
  "totalQuotes": 3,
  "failedProviderCount": 1,
  "completionStatus": "Completed"
}
Next step
After receiving 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.

FieldTypeDescription
eventstringThe domain event type (e.g. BeneficiaryCreated)
beneficiaryIdstring (UUID)The beneficiary identifier
{
  "event": "BeneficiaryCreated",
  "beneficiaryId": "f0e1d2c3-b4a5-6789-0abc-def012345678"
}
Tip
After receiving a beneficiary event, call GET /v1/beneficiaries/{id} to retrieve the full record including validation status and provider details.

Transfer Events

Subscribe with transfer-{id} to receive status updates. The payload is minimal — use the transfer ID to fetch the full record.

FieldTypeDescription
eventstringThe domain event type (e.g. TransferStatusUpdated)
transferIdstring (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.

Important
WebSocket events are best-effort, not guaranteed delivery. Always implement a polling fallback for critical workflows. For example, if you haven't received a terminal payout event within your expected timeframe, call 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"