# Real-time events (WebSocket)

Partners can open a WebSocket connection to receive real-time events for a specific client — for example when an order is placed, a transaction settles, or a journey completes.

## Connection

Open a WebSocket to:

- **Sandbox** – `wss://api.sandbox.fondo.se/v2/partner/client/{clientId}/subscribe`
- **NPC** – `wss://api.npc.fondo.se/v2/partner/client/{clientId}/subscribe`
- **Production** – `wss://api.fondo.se/v2/partner/client/{clientId}/subscribe`


Use the `wss://` scheme — TLS is required.

On success the server sends a `hello` event:

```json
{ "eventType": "hello" }
```

## Authentication

Authenticate the WebSocket handshake with the same OAuth2 bearer token used for REST requests, sent as an `Authorization: Bearer <token>` header on the upgrade request:

```
GET /v2/partner/client/{clientId}/subscribe HTTP/1.1
Host: api.sandbox.fondo.se
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: <client-generated nonce>
Authorization: Bearer <YOUR_TOKEN_HERE>
```

The token is checked once at handshake time. The connection then stays open until either side closes it — it is not severed when the token's session TTL expires, so you do not need to reconnect on token refresh.

Browsers cannot set the `Authorization` header on the WebSocket handshake. Connect from a backend.

If your partner account uses IP allowlisting, the source IP of the WebSocket handshake must also be on the allowlist; otherwise the upgrade is rejected with `403`.

## Common errors

| You see | Cause |
|  --- | --- |
| `404 Not Found` on a plain `GET` | The endpoint only responds to a WebSocket handshake. Sending a normal `GET` (curl, Postman, browser navigation, the API Reference "Try it" button) returns `404`. Use a WebSocket client. |
| `400` on the upgrade | Missing or malformed `Authorization` header. |
| `401` on the upgrade | Token is invalid, expired, or doesn't belong to a session. Re-fetch a token via [Authentication](/guides/api-guide/getting-started/authentication). |
| `403` on the upgrade | The token's partner does not own `clientId`, or your source IP is not allowlisted. |


## Heartbeat

The server sends a `heartbeat` event roughly every five seconds to keep the connection alive:

```json
{ "eventType": "heartbeat" }
```

## Event structure

Every event is a JSON object with an `eventType` field. Events that carry data also include a `payload` object:

- **`.create`** events contain `payload.current` — the created resource.
- **`.update`** events contain `payload.current` and `payload.previous` — the resource before and after the change.
- **`.delete`** events contain `payload.previous` — the resource that was removed.


Example:

```json
{
  "eventType": "order.create",
  "payload": {
    "current": { "id": "abc123", "status": "pending", "…": "…" }
  }
}
```

## Event types

| Event type | Trigger |
|  --- | --- |
| `account.create` | Account created |
| `account.update` | Account updated |
| `account.delete` | Account deleted |
| `client.update` | Client updated |
| `client.delete` | Client deleted |
| `order.create` | Order created |
| `order.update` | Order updated |
| `order.delete` | Order canceled |
| `cashTransaction.create` | Cash transaction created |
| `cashTransaction.update` | Cash transaction updated |
| `cashTransaction.delete` | Cash transaction deleted |
| `fundTransaction.create` | Fund transaction created |
| `fundTransaction.update` | Fund transaction updated |
| `fundTransaction.delete` | Fund transaction deleted |
| `journey.update` | Journey updated |
| `journey.delete` | Journey deleted |
| `instrument.reloaded` | Fund data refreshed for all instruments |


## Scoping

Each connection receives events for the `clientId` in the URL only. The exception is `instrument.reloaded`, which is broadcast to all connections.

## Reconnection

The server may close the connection at any time (for example during a deployment) with close code `1001`. Clients should implement reconnection with exponential back-off.

There is no event replay or sequence numbering. Any state you missed during the disconnect must be reconciled via the REST API — for example by re-fetching orders modified since your last known timestamp. Treat the WebSocket as a low-latency notifier, not as a durable event log.

## Examples

See the [`getWebsocket` operation](/openapi/api/subscription/getwebsocket) in the API Reference for the full event schema and response codes.