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

# Callout alerts bot

> New pump.fun callouts, filtered, posted to Telegram within seconds.

**For:** alpha groups, trading communities and personal alert channels.

The bot holds one socket on `/v2/ws/pump`. Each new callout passes your filters,
such as market cap at call time or verified callers, and lands in a Telegram
channel with the caller, the coin and the thesis.

## How it works

```mermaid theme={null}
flowchart LR
  A["/v2/ws/pump"] --> B[Bot process]
  B -- filter --> C[Telegram sendMessage]
```

## Starter code

Node 18 or later, with `ws`. Create the bot with
[@BotFather](https://t.me/BotFather) and add it to your channel.

```js bot.js theme={null}
import WebSocket from "ws";

const KEY = process.env.FOMOSCAN_KEY;
const TG = `https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/sendMessage`;
const CHAT = process.env.TELEGRAM_CHAT_ID;
const MAX_MCAP = 250_000;
const seen = new Set();

function keep(c) {
  return c.calledOutAtMcap != null && c.calledOutAtMcap <= MAX_MCAP && c.thesis;
}

async function post(c) {
  const caller = c.xUsername ? `@${c.xUsername}` : c.userName ?? c.wallet.slice(0, 6);
  const text = [
    `${c.isVerified ? "✅ " : ""}${caller} called $${c.coinSymbol ?? c.coinMint.slice(0, 6)}`,
    `MCap at call: $${Math.round(c.calledOutAtMcap).toLocaleString()}`,
    "",
    c.thesis,
    "",
    `https://pump.fun/coin/${c.coinMint}`,
  ].join("\n");
  await fetch(TG, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ chat_id: CHAT, text, disable_web_page_preview: true }),
  });
}

function connect() {
  const ws = new WebSocket("wss://api.fomoscan.sh/v2/ws/pump", {
    headers: { Authorization: `Bearer ${KEY}` },
  });
  ws.on("open", () => {
    ws.send(JSON.stringify({ type: "subscribe", subscription: { type: "all" } }));
  });
  ws.on("message", async (raw) => {
    const frame = JSON.parse(raw.toString());
    if (frame.type !== "pump_callout") return;
    const c = frame.callout;
    if (seen.has(c.id) || !keep(c)) return;
    seen.add(c.id);
    await post(c);
  });
  ws.on("error", (err) => console.error(err.message));
  ws.on("close", (code, reason) => {
    console.log("closed", code, reason.toString());
    if (code !== 1008) setTimeout(connect, 2000);
  });
}

connect();
```

## Details that matter

* **One socket, any number of channels.** Fan out inside the bot rather than
  opening a socket per channel. Each socket bills per minute on its own.
* **Narrow with a token list** when the group only follows some coins:
  `{ "type": "tokens", "tokens": ["<coin mint>"] }`, up to 50.
* **No replay.** After a reconnect, read `GET /v2/pump/thesis` and post what
  arrived while the bot was away, matching on `id`.
* **`1008` means out of credits.** The bot stops reconnecting until you top up.
  A reconnect without credits is refused with `402` before the upgrade.
* **Keep the `error` listener.** Without it, a failed handshake or a network
  drop throws in Node and stops the bot.

> **Build it with us**
>
> We build these projects together with the teams that ship them. Tell us what
> you are building on [Telegram](https://t.me/fomoscan_support) or at
> [fomoscan-support@proton.me](mailto:fomoscan-support@proton.me) and we will
> help you scope it, size the credits and get it live.


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