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

# Callouts stream

> wss://api-id.cabalspy.xyz/v1/callouts/stream: every callout of your instance live: new callouts, price updates, milestones, first-day scores, the end of tracking and removals.

> [!NOTE]
>
> Callouts are switched on per instance on request. Contact us ([contact@cabalspy.xyz](mailto:contact@cabalspy.xyz)) to enable them. Without them every endpoint answers 403 [feature\_not\_enabled](https://docs.cabalspy.xyz/cabalspy-id/errors.md#feature_not_enabled).

A WebSocket with every [callout](https://docs.cabalspy.xyz/cabalspy-id/callouts.md) event of your instance as it happens: new callouts, each new price, milestones, the first-day score, the end of tracking and removals. Use it for a live callout feed, live multiples next to each callout, or new markers on a price chart.

It is available on every plan once callouts are switched on, and only carries your own instance's callouts.

## Connecting

Pass your API key as the `api_key` or `apiKey` query parameter, or as an Authorization Bearer header. The connection starts streaming right away, there is nothing to subscribe to.

```text theme={null}
wss://api-id.cabalspy.xyz/v1/callouts/stream?api_key=YOUR_KEY
wss://api-id.cabalspy.xyz/v1/callouts/stream?api_key=YOUR_KEY&chain=solana
wss://api-id.cabalspy.xyz/v1/callouts/stream?api_key=YOUR_KEY&chain=solana&mint=TOKEN_MINT
```

**api\_key** `string` *required*

Your API key. `apiKey` works too, or an Authorization Bearer header.

**chain** `string`

Only events on this chain. One of solana, bnb, base, eth, rh. Empty means every chain. An unknown chain is refused.

**mint** `string`

Only events of this token. Needs chain. The address is normalized as in [List callouts](https://docs.cabalspy.xyz/cabalspy-id/callouts-list.md), so EVM addresses match in any letter case.

**instance** `string`

Your Instance ID. Only needed when your account has several instances.

```javascript JavaScript theme={null}
const ws = new WebSocket("wss://api-id.cabalspy.xyz/v1/callouts/stream?api_key=YOUR_KEY&chain=solana");

ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  switch (msg.event) {
    case "callout.created":
      console.log("new callout", msg.data.author.username, msg.data.token.symbol);
      break;
    case "callout.update":
      console.log(msg.data.id, "now", msg.data.x, "peak", msg.data.peak_x);
      break;
    case "callout.milestone":
      console.log(msg.data.token.symbol, "hit", msg.data.milestone_x + "x");
      break;
    case "error":
      console.error(msg.error.code, msg.error.message);
      break;
  }
};
```

```python Python theme={null}
import asyncio, json, websockets

async def main():
    url = "wss://api-id.cabalspy.xyz/v1/callouts/stream?api_key=YOUR_KEY"
    async with websockets.connect(url) as ws:
        async for raw in ws:
            msg = json.loads(raw)
            if msg["event"] == "callout.created":
                print("new", msg["data"]["token"]["symbol"], msg["data"]["at_call"]["price_usd"])
            elif msg["event"] == "callout.milestone":
                print(msg["data"]["id"], msg["data"]["milestone_x"], "x")
            elif msg["event"] == "error":
                print("error", msg["error"])

asyncio.run(main())
```

## Messages

Every message is a JSON object with an `event` field and, except for `heartbeat`, `pong` and `error`, a `data` field.

| Event | When | data |
| - | - | - |
| `ready` | Right after connecting. | `events` (the callout events this stream sends), `chain` and `mint`. |
| `callout.created` | A user made a callout. | The [callout object](https://docs.cabalspy.xyz/cabalspy-id/callouts-list.md#the-callout-object). |
| `callout.update` | A new price was read: every minute in the first day, every 5 minutes after. Only sent when the price changed and no milestone was reached. | `id`, `chain`, `mint`, `price_usd`, `market_cap_usd`, `x` and `peak_x`. |
| `callout.milestone` | The peak multiple reached 2, 3, 5, 10, 20, 50 or 100x. | The callout object plus `milestone_x`. |
| `callout.scored` | The first 24 hours were scored. | The callout object with `first_day`. |
| `callout.final` | Tracking stopped: market cap below \$10,000, or no price, for 24 hours in a row. | The callout object with `status: "final"`. |
| `callout.removed` | You removed a callout. | `id`, `chain` and `mint`. |
| `heartbeat` | After 30 seconds without a message from you. | None, `ts` is Unix time in seconds. |
| `pong` | After you send `ping`. | None. |
| `error` | The connection is refused. | None, `error.code` and `error.message`. The server then closes with code 4403. |

```json ready theme={null}
{ "event": "ready", "data": { "events": ["callout.created", "callout.update", "callout.milestone", "callout.scored", "callout.final", "callout.removed"], "chain": "solana", "mint": null } }
```

```json callout.update theme={null}
{
  "event": "callout.update",
  "data": {
    "id": "5b0e7c3a-2d4f-4a6b-9c8d-1e2f3a4b5c6d",
    "chain": "solana",
    "mint": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
    "price_usd": 0.0000781,
    "market_cap_usd": 78100.0,
    "x": 1.8956,
    "peak_x": 1.9466
  }
}
```

```json callout.milestone theme={null}
{
  "event": "callout.milestone",
  "data": {
    "id": "5b0e7c3a-2d4f-4a6b-9c8d-1e2f3a4b5c6d",
    "chain": "solana",
    "mint": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
    "token": { "name": "invisibull", "symbol": "invisibull", "logo": "https://..." },
    "author": { "csid_id": "3f9a1c2e-5b7d-4e8f-a1b2-c3d4e5f60718", "external_id": "user_8412", "name": "Maze", "username": "MazeCCC", "avatar_url": "https://cdn.example.com/avatars/8412.png", "kol": false },
    "called_at": "2026-11-01T18:02:11Z",
    "status": "tracking",
    "at_call": { "price_usd": 0.0000412, "market_cap_usd": 41200.0 },
    "peak": { "price_usd": 0.0000851, "market_cap_usd": 85100.0, "x": 2.0655, "at": "2026-11-01T18:47:00Z" },
    "score_x": 2.0655,
    "...": "the full callout object",
    "milestone_x": 2
  }
}
```

```json callout.removed theme={null}
{ "event": "callout.removed", "data": { "id": "5b0e7c3a-2d4f-4a6b-9c8d-1e2f3a4b5c6d", "chain": "solana", "mint": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump" } }
```

```json heartbeat theme={null}
{ "event": "heartbeat", "ts": 1793548975 }
```

```json error theme={null}
{ "event": "error", "error": { "code": "feature_not_enabled", "message": "Callouts are not enabled for this instance." } }
```

## Staying connected

Send the text `ping` at any time, the server answers with `pong`. When you send nothing for 30 seconds, the server sends a `heartbeat` and checks that your key is still valid. A revoked or disabled key ends the connection.

Reconnect with a short backoff when the connection drops, and use [List callouts](https://docs.cabalspy.xyz/cabalspy-id/callouts-list.md) with `since` to fill any gap.

## Limits and errors

Each API key can hold at most 5 callouts stream connections at once, counted separately from the other streams.

| Code | Meaning |
| - | - |
| invalid\_api\_key | API key missing, invalid or not enabled for CabalSpy ID. |
| feature\_not\_enabled | Callouts are not switched on for your instance. |
| too\_many\_connections | At most 5 callouts stream connections per API key. |
| invalid\_parameter | chain is unknown, or mint is not a valid token address for chain. |
| missing\_parameter | mint was passed without chain. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.