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

# WebSocket

> Real-time transaction status updates via WebSocket

Subscribe to one transaction at a time and receive push updates as its status changes.

## Connection

```text theme={null}
wss://DOMAIN/ws?tx_id=<merchant_transaction_id>
```

### Authentication

Authenticate with the same `client_token` you use for HTTP. Send it in the `Authorization` header on the upgrade request:

```http theme={null}
GET /ws?tx_id=550e8400-... HTTP/1.1
Host: DOMAIN
Authorization: Bearer <client_token>
Upgrade: websocket
Connection: Upgrade
```

<Note>
  Open WebSocket connections from your backend server, not the browser. Browsers cannot send custom headers on the upgrade request, and `client_token` must not be exposed to the frontend. Your backend subscribes and pushes status updates to the browser through your own channel (SSE, polling your API, or a WebSocket relay).
</Note>

### Query parameters

| Parameter | Required | Description                                        |
| --------- | -------- | -------------------------------------------------- |
| `tx_id`   | Yes      | A single `merchant_transaction_id` to subscribe to |

One connection subscribes to one transaction. To watch multiple transactions, open multiple connections.

## Events

### `tx.update`

Fired on every status change for the subscribed transaction.

```json theme={null}
{
  "event": "tx.update",
  "merchant_transaction_id": "550e8400-e29b-41d4-a716-446655440000",
  "type": "buy",
  "status": "completed",
  "payment_method": "card",
  "currency": "USDT",
  "fiat_currency": "EUR",
  "fiat_amount": "100.00",
  "crypto_amount": "99.50",
  "occurred_at": "2026-04-01T10:03:45Z"
}
```

| Field                     | Type   | Notes                                                                         |
| ------------------------- | ------ | ----------------------------------------------------------------------------- |
| `event`                   | string | Always `"tx.update"`                                                          |
| `merchant_transaction_id` | string | Echo of the subscription                                                      |
| `type`                    | string | `"buy"` or `"sell"`                                                           |
| `status`                  | string | Normalized status — see [Transaction Status](/on-off-ramp/transaction-status) |
| `payment_method`          | string | Omitted if unknown                                                            |
| `currency`                | string | Crypto currency                                                               |
| `fiat_currency`           | string | Fiat currency                                                                 |
| `fiat_amount`             | string | Fiat amount                                                                   |
| `crypto_amount`           | string | Crypto amount (may be empty before `processing`)                              |
| `occurred_at`             | string | ISO 8601 timestamp of the status-change event                                 |

## Code examples

```javascript Node.js theme={null}
const WebSocket = require("ws");

const ws = new WebSocket(
  "wss://DOMAIN/ws?tx_id=550e8400-e29b-41d4-a716-446655440000",
  { headers: { Authorization: "Bearer <client_token>" } }
);

ws.on("message", (data) => {
  const msg = JSON.parse(data);
  if (msg.event === "tx.update") {
    console.log(msg.status);
    if (["completed", "failed", "cancelled"].includes(msg.status)) {
      ws.close();
    }
  }
});

ws.on("close", () => {
  // Reconnect with exponential backoff
});
```

## Reconnection

Always implement reconnection with exponential backoff and a cap:

```javascript theme={null}
let attempt = 0;

function connect(txId) {
  const ws = new WebSocket(
    `wss://DOMAIN/ws?tx_id=${txId}`,
    { headers: { Authorization: "Bearer <client_token>" } }
  );

  ws.on("open", () => { attempt = 0; });

  ws.on("close", () => {
    const delay = Math.min(1000 * Math.pow(2, attempt++), 30000);
    setTimeout(() => connect(txId), delay);
  });

  ws.on("message", (data) => handleMessage(JSON.parse(data)));
}

connect("550e8400-e29b-41d4-a716-446655440000");
```

## Fallback to polling

If your environment cannot use WebSocket (proxy, ad blocker, browser without backend support), fall back to polling [`GET /widget/transactions/{id}`](/on-off-ramp/api-transaction) every 5–10 seconds.
