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

# Real-time quotes (WebSocket)

> Receive live rates and firm quotes over Socket.IO instead of polling the quote endpoint.

The quote WebSocket pushes the rate, or a full quote, every time it changes. It uses the same pricing rules as [Get Quote](/stack/api-reference/liquidity-ramps/get-quote), so a quote received through the socket can be used to create an operation.

| Environment | URL | Path |
| - | - | - |
| Test | `wss://ws-test.kravata.co` | `/kmsktransaction/socket.io` |
| Production | `wss://ws.kravata.co` | `/kmsktransaction/socket.io` |

The server uses [Socket.IO](https://socket.io/) (protocol v4). Use an official Socket.IO client, such as `socket.io-client` for JavaScript or `python-socketio` for Python.

## Connect

Authenticate with the same access token you use for the REST API (see [Authentication](/stack/authentication)). Send it in the `auth` payload as `token`, or in the `Authorization: Bearer <token>` header. Your [IP allowlist](/stack/api-reference/authentication/update-ip-allowlist) also applies to the socket.

```javascript theme={null}
import { io } from "socket.io-client";

const socket = io("wss://ws.kravata.co", {
  path: "/kmsktransaction/socket.io",
  transports: ["websocket"],
  // A function, so a fresh token is used every time the client reconnects.
  auth: async (cb) => cb({ token: await getAccessToken() }),
});

socket.on("connect_error", (err) => console.error(err.message)); // "unauthorized" or "forbidden"
```

The token is validated once, when connecting. An open connection stays active after the token expires; only a reconnection needs a valid token.

| Connection error | Cause |
| - | - |
| `unauthorized` | Missing, invalid or expired token, or the token is not a client token. |
| `forbidden` | Your IP is not in your IP allowlist. |

## Subscribe

Emit `subscribe` with the same body as [Get Quote](/stack/api-reference/liquidity-ramps/get-quote). Unlike the REST endpoint, `amount` is optional, and it selects what you receive:

| You send | You receive in `rate_update` |
| - | - |
| No `amount` and no `paymentMethod` | The live rate: `symbolOrigin`, `symbolDestination`, `calculatedValues.calculateRate` and `date`. |
| No `amount`, with `paymentMethod` | The live rate plus `paymentMethod` and the fixed cost of that rail (`calculatedValues.CostInfra`). |
| With `amount` | A **firm quote**, the same response as Get Quote, including `quoteId` and `expiresAt`. |

```javascript theme={null}
// Live rate
socket.emit("subscribe", { direction: "WITHDRAWAL", symbolOrigin: "USDC", symbolDestination: "COP" }, (ack) => console.log(ack));

// Firm quote for 100 USDC paid out through Bre-B
socket.emit(
  "subscribe",
  { direction: "WITHDRAWAL", amount: 100, executionMode: "NORMAL", paymentMethod: "BREB", symbolOrigin: "USDC", symbolDestination: "COP" },
  (ack) => console.log(ack),
);

socket.on("rate_update", (payload) => console.log(payload));
```

The acknowledgement is `{"status": "ok"}`, or an error:

```json theme={null}
{ "status": "error", "reason": "invalid subscription", "errors": [{ "loc": ["direction"], "msg": "Field required", "type": "missing" }] }
```

Each connection has one subscription: emitting `subscribe` again replaces it. Emit `unsubscribe` to stop receiving updates without disconnecting.

## Updates

<CodeGroup>
  ```json Live rate theme={null}
  {
    "symbolOrigin": "USDC",
    "symbolDestination": "COP",
    "calculatedValues": { "calculateRate": 3910.25 },
    "date": "2026-10-01T15:04:00.123456+00:00"
  }
  ```

  ```json Live rate with rail theme={null}
  {
    "symbolOrigin": "USDC",
    "symbolDestination": "COP",
    "paymentMethod": "BREB",
    "calculatedValues": { "calculateRate": 3910.25, "CostInfra": 3500 },
    "date": "2026-10-01T15:04:00.123456+00:00"
  }
  ```

  ```json Firm quote theme={null}
  {
    "direction": "WITHDRAWAL",
    "paymentMethod": "BREB",
    "amountOrigin": 100,
    "amountDestination": 387525.0,
    "symbolOrigin": "USDC",
    "symbolDestination": "COP",
    "calculatedValues": { "calculateRate": 3910.25, "CostInfra": 3500, "amountReceive": 387525.0 },
    "quoteId": "6f1d2c3b-4a5e-4f60-9b7c-8d9e0f1a2b3c",
    "expiresAt": "2026-10-01T15:05:00Z"
  }
  ```
</CodeGroup>

If a quote cannot be calculated, `rate_update` carries `{"status": "error", "reason": "..."}`. Validation problems show their message; other failures show `Quote unavailable`.

### When updates are sent

* Immediately after you subscribe.
* Every time the market rate changes.
* In addition, every 60 seconds between **8:00 and 22:00 (Bogotá time)**, so a firm quote always has a valid `quoteId`.

## Use a firm quote

A `quoteId` is valid for **60 seconds** and for the same amount and assets it was issued for. Send it in the `quoteId` field when you [create a withdrawal](/stack/api-reference/liquidity-ramps/create-withdrawal) or a [deposit](/stack/api-reference/liquidity-ramps/create-deposit) to apply that exact quote. Every new update replaces the previous `quoteId`, so always use the latest one.
