Solana Tracker LogoSolana Tracker
Swap
Developers
⌘K
Affiliate
All Resources
Pump.fun WebSocket API: Stream New Token Launches
Pump.funTypeScriptMay 28, 202612 min readUpdated October 7, 2026Solana Tracker

Pump.fun WebSocket API: Stream New Token Launches

Stream new Pump.fun launches with the Pump.fun WebSocket API: filter the Datastream latest room, backfill from REST on reconnect, and dedupe by mint.

  • pumpfun,
  • websocket,
  • datastream,
  • token-launches,
  • typescript
Pump.fun API›Solana Data API›

The Pump.fun WebSocket API from Solana Tracker is the Datastream latest room: one subscription delivers every new token and pool the indexer sees, and you keep the messages that carry a Pump.fun bonding-curve pool (market === "pumpfun"). Each message already includes token metadata, the curve pool with price, liquidity and curve progress, and a risk block, so you do not need a second request per launch. Pair it with GET /tokens/latest to backfill whatever you missed while disconnected.

This guide covers when the WebSocket feed is the right tool, the exact payload, a complete TypeScript consumer with reconnect, backfill and deduplication, and the mistakes that cause missed or double alerts. The same data is exposed as the Pump.fun API, which is a view of the Solana Data API; keys and plans live in the Data API dashboard.

When to use the Pump.fun WebSocket API

Use the Datastream feed when you want parsed, enriched launch events with minimal code: a launch alert bot, a new-token panel in a dashboard, a screener that starts tracking a mint the moment it appears. You get one message per new token or pool with fields ready to display, and the SDK handles the socket, heartbeats and reconnects.

It is not the only way to watch Pump.fun launches. If you need the raw create instruction, the exact slot, or you want to decode program accounts yourself, stream the Pump.fun program over Yellowstone gRPC instead; Pump.fun mints over Yellowstone gRPC walks through that path on Yellowstone gRPC. If you only need a periodic list, poll REST.

ApproachWhat you receiveLatencyEffortBest for
Datastream latest room (this guide)Parsed token, pools, risk per launchPush, as indexedLow: one subscriptionAlerts, dashboards, screeners
GET /tokens/latest (REST)Latest 100 tokens per page, pages 1-10Polling intervalLowestBackfill, low-frequency jobs
Yellowstone gRPC on the Pump.fun programRaw transactions and instructionsEarliest, pre-indexingHigh: you decode everythingInstruction-level bots, custom parsing

The latest room is not Pump.fun-only. It covers new tokens and pools from every source the indexer tracks, so filtering is your job, and it is the first thing to get right.

Identify Pump.fun launches in the feed

Each message has token, pools, events and risk. A Pump.fun launch carries a pool whose market is "pumpfun": that is the bonding-curve pool, with curvePercentage showing progress toward graduation. After migration the token trades on a "pumpfun-amm" (PumpSwap) pool instead.

Two details matter in practice. First, search every entry in pools. The official example filters on pools[0], but nothing in the schema promises the curve pool is first, so pools.find((p) => p.market === "pumpfun") is the safer check. Second, token.createdOn (for example "https://pump.fun") records the origin site; it is useful context but the pool market is the field that tells you the token is on the curve right now.

Timestamps use two units. pool.createdAt and pool.lastUpdated are Unix milliseconds. creation.created_time on the token or pool is Unix seconds. Mixing them is the most common reason launch times print as 1970.

Response shape

A trimmed latest message, using the documented example values:

{
  "type": "message",
  "room": "latest",
  "data": {
    "token": {
      "name": "Example Token",
      "symbol": "EXAMPLE",
      "mint": "AmJaZvdNptvofC4qe3tvuBNgqLm65p1of5pk6JFHpump",
      "decimals": 6,
      "createdOn": "https://pump.fun",
      "strictSocials": { "twitter": "https://twitter.com/example" },
      "creation": {
        "creator": "4Rz5xqikxtZ2s7wE9uQ6n2oLXQi6K65XGoYpKxf24Hqo",
        "created_tx": "5KtPn1...",
        "created_time": 1723727770
      }
    },
    "pools": [
      {
        "poolId": "GmJaZvdNptvofC4qe3tvuBNgqLm65p1of5pk6JFHpump",
        "market": "pumpfun",
        "liquidity": { "quote": 62.5, "usd": 8907.76 },
        "price": { "quote": 2.9854e-8, "usd": 0.00000429 },
        "marketCap": { "quote": 29.854, "usd": 4289.23 },
        "curvePercentage": 45.5,
        "deployer": "4Rz5xqikxtZ2s7wE9uQ6n2oLXQi6K65XGoYpKxf24Hqo",
        "security": { "freezeAuthority": null, "mintAuthority": null },
        "createdAt": 1723727770351,
        "lastUpdated": 1723727796824,
        "txns": { "buys": 45, "sells": 23, "total": 68, "volume": 12500 }
      }
    ],
    "events": { "1m": { "priceChangePercentage": 0 } },
    "risk": {
      "snipers": { "count": 0, "totalBalance": 0, "totalPercentage": 0, "wallets": [] },
      "insiders": { "count": 0, "totalBalance": 0, "totalPercentage": 0, "wallets": [] },
      "top10": 0,
      "dev": { "percentage": 0, "amount": 0 },
      "rugged": false,
      "risks": [
        { "name": "Bonding curve not complete", "level": "warning", "score": 4000 }
      ],
      "score": 5
    }
  }
}

The SDK unwraps the envelope and hands your callback the data object. Its fields match TokenInfo, PoolInfo and TokenRisk exported by @solana-tracker/data-api, which is why the same handler can process REST rows from getLatestTokens(). The risk object carries sniper, insider, developer and top-10 concentration plus the individual risk factors; score runs from 1 to 10 and higher means riskier.

Stream Pump.fun launches in TypeScript

The consumer below subscribes to latest, keeps Pump.fun curve launches, applies an optional liquidity floor, and deduplicates by mint. On every connected event, which fires on the first connect and after each automatic reconnect, it reads page 1 of /tokens/latest and replays any Pump.fun rows it has not printed yet.

Setup:

npm init -y && npm pkg set type=module
npm install @solana-tracker/data-api@^0.5.0
npm install --save-dev tsx typescript @types/node
node --env-file=.env --import tsx index.ts

.env.example:

# Data API key from /account/data-api (used for the REST backfill)
ST_API_KEY=
# Datastream key or the full wss://datastream.solanatracker.io/<key> URL (Premium plan or higher)
ST_DATASTREAM_KEY=
# Only print launches whose curve pool has at least this much USD liquidity
MIN_LIQUIDITY_USD=0
# Suppress repeat alerts for the same mint for this many minutes
DEDUPE_MINUTES=30

index.ts:

import { Client, DataApiError, Datastream, RateLimitError, type PoolInfo, type TokenInfo } from "@solana-tracker/data-api";

// Configuration: fail fast with one clear line per missing variable.
const apiKey = process.env.ST_API_KEY?.trim();
const streamKey = process.env.ST_DATASTREAM_KEY?.trim();
if (!apiKey) exit("Missing ST_API_KEY (Data API key from /account/data-api)");
if (!streamKey) exit("Missing ST_DATASTREAM_KEY (Datastream key or wss:// URL)");
const wsUrl = streamKey.startsWith("wss://") ? streamKey : `wss://datastream.solanatracker.io/${streamKey}`;
const MIN_LIQUIDITY_USD = Number(process.env.MIN_LIQUIDITY_USD ?? "0");
const DEDUPE_MS = Number(process.env.DEDUPE_MINUTES ?? "30") * 60_000;
if (!Number.isFinite(MIN_LIQUIDITY_USD) || MIN_LIQUIDITY_USD < 0) exit("MIN_LIQUIDITY_USD must be >= 0");
if (!Number.isFinite(DEDUPE_MS) || DEDUPE_MS <= 0) exit("DEDUPE_MINUTES must be > 0");

function exit(message: string): never {
  console.error(message);
  process.exit(1);
}

type LaunchLike = { token: TokenInfo; pools: PoolInfo[]; risk?: { score?: number } };

// Search every pool; the first pool is not guaranteed to be the curve pool.
const curvePool = (item: LaunchLike) => item.pools?.find((p) => p.market === "pumpfun");

const client = new Client({ apiKey });
const ds = new Datastream({
  wsUrl, // contains the key: never log it
  autoReconnect: true,
  reconnectDelay: 1_000,
  reconnectDelayMax: 30_000,
  randomizationFactor: 0.5,
});

// One alert per mint per window, shared by live messages and REST backfill.
const seen = new Map<string, number>();
const pruneTimer = setInterval(() => {
  const now = Date.now();
  for (const [mint, expires] of seen) if (expires <= now) seen.delete(mint);
}, 60_000);

function report(item: LaunchLike, source: "live" | "rest"): void {
  const pool = curvePool(item);
  const mint = item.token?.mint;
  if (!pool || !mint) return;
  if ((pool.liquidity?.usd ?? 0) < MIN_LIQUIDITY_USD) return;
  const now = Date.now();
  if ((seen.get(mint) ?? 0) > now) return;
  seen.set(mint, now + DEDUPE_MS);

  const createdSec = pool.creation?.created_time ?? item.token.creation?.created_time;
  const createdAt = pool.createdAt ?? (createdSec ? createdSec * 1000 : undefined); // ms
  const creator = pool.deployer ?? pool.creation?.creator ?? item.token.creation?.creator ?? "?";
  console.log(
    [
      new Date(createdAt ?? now).toISOString().slice(11, 19),
      source.padEnd(4),
      (item.token.symbol || "?").slice(0, 12).padEnd(12),
      mint,
      `curve ${pool.curvePercentage?.toFixed(0) ?? "?"}%`,
      `liq $${(pool.liquidity?.usd ?? 0).toFixed(0)}`,
      `risk ${item.risk?.score ?? "?"}/10`,
      `creator ${creator}`,
    ].join("  "),
  );
}

// The REST client has no timeout or retry of its own.
async function withRetry<T>(label: string, fn: () => Promise<T>, attempts = 3): Promise<T> {
  for (let attempt = 1; ; attempt++) {
    let timer: NodeJS.Timeout | undefined;
    try {
      const timeout = new Promise<never>((_, reject) => {
        timer = setTimeout(() => reject(new Error(`${label} timed out`)), 15_000);
      });
      return await Promise.race([fn(), timeout]);
    } catch (error) {
      const retryable =
        error instanceof RateLimitError ||
        !(error instanceof DataApiError) ||
        error.status === undefined ||
        error.status >= 500;
      if (!retryable || attempt >= attempts) throw error;
      const retryAfterMs = error instanceof RateLimitError && error.retryAfter ? error.retryAfter * 1000 : 0;
      await new Promise((r) => setTimeout(r, Math.max(500 * 2 ** (attempt - 1), retryAfterMs)));
    } finally {
      clearTimeout(timer);
    }
  }
}

// Backfill the gap after every (re)connect, at most once per 15 s.
let lastBackfill = 0;
let backfilling = false;
async function backfill(): Promise<void> {
  if (backfilling || Date.now() - lastBackfill < 15_000) return;
  backfilling = true;
  lastBackfill = Date.now();
  try {
    const rows = await withRetry("GET /tokens/latest", () => client.getLatestTokens(1));
    const pump = rows.filter((r) => curvePool(r));
    pump.sort((a, b) => (curvePool(a)?.createdAt ?? 0) - (curvePool(b)?.createdAt ?? 0));
    console.log(`[backfill] ${pump.length} Pump.fun launches in ${rows.length} latest tokens`);
    for (const row of pump) report(row, "rest");
  } catch (error) {
    console.warn(`[backfill] skipped: ${error instanceof Error ? error.message : String(error)}`);
  } finally {
    backfilling = false;
  }
}

ds.on("connected", () => {
  console.log("[datastream] connected");
  void backfill();
});
ds.on("reconnecting", (attempt: number) => console.warn(`[datastream] reconnecting, attempt ${attempt + 1}`));
ds.on("error", () => console.error("[datastream] connection error (check key and plan)"));

// Subscribing opens the socket; the SDK rejoins the room after reconnects.
const listener = ds.subscribe.latest().on((item) => report(item, "live"));

process.once("SIGINT", () => {
  clearInterval(pruneTimer);
  listener.unsubscribe();
  ds.disconnect();
  process.exit(0);
});

A few choices in that code are deliberate:

  • The SDK owns reconnects. autoReconnect uses capped exponential backoff with jitter and rejoins every room after the socket comes back. You do not need your own reconnect loop, only a reaction to connected.
  • Subscribing opens the socket. subscribe.latest() connects on demand, so there is no separate connect() call to race against.
  • The URL is a secret. The Datastream URL embeds your key. The error handler prints a fixed message instead of the event payload, and nothing logs wsUrl.
  • REST calls are wrapped. The Data API client neither retries nor times out, so the backfill adds both and treats failures as non-fatal: the live stream keeps running if REST is down.

Backfill and deduplication on reconnect

A WebSocket only delivers what happens while you are connected. Every disconnect, deploy or laptop sleep opens a gap. GET /tokens/latest returns the 100 most recent tokens per page (pages 1 to 10), with the same token, pools and risk fields, so replaying it after each connected closes short gaps without a second code path.

Two rules make this safe. Deduplicate by mint across both sources, because the backfill will usually return launches you already printed live. And throttle the backfill: with a rejected key or an unstable network the socket can connect and drop every second or two, and an unthrottled handler would hit REST on every cycle. The example allows at most one backfill per 15 seconds. For longer outages, read more pages, and store the last processed launch time so a restart can resume rather than replay.

Production pitfalls

  • Filtering on pools[0]. Search the whole array for market === "pumpfun".
  • Seconds versus milliseconds. createdAt is ms; created_time is seconds.
  • Treating the alert as tradeable state. A launch message describes the token when it was indexed. Liquidity, curve progress and risk change within seconds; fetch fresh data, or get a fresh quote from the Raptor swap API, before acting.
  • Logging the socket URL. Error events and debug output can leak it. Log your own messages.
  • Unbounded memory. A dedupe set that never expires grows forever on a busy day. Expire entries, as the example does with DEDUPE_MINUTES.
  • Plan limits and bad keys. Datastream requires Premium or higher. With an invalid key the server accepts the socket and then closes it, so the symptom is a connect/disconnect loop rather than an error message. Check the key and plan when you see that pattern.
  • Slow handlers. Do network calls or database writes asynchronously, or push launches into a queue. Blocking the callback delays every following message.

To follow a launch after the first alert, subscribe to its curve progress and migration with the Pump.fun graduation API guide, and once early trading settles, inspect who bought first with the Pump.fun first buyers guide. For live trades on the new pool, see streaming Solana trades over WebSocket.

FAQ

Is there a Datastream room that only sends Pump.fun launches?

The latest room covers new tokens and pools from every tracked source, so you filter it client-side by pool market. There are Pump.fun-scoped rooms for later lifecycle stages, such as pumpfun:curve:{percentage} for bonding-curve progress, covered in the graduation guide.

Which plan includes the Pump.fun WebSocket API?

Datastream is available on the Premium, Business and Enterprise Data API plans. The REST backfill works on any plan with a Data API key. Compare plans in the Data API dashboard.

Why do I see the same mint twice?

Usually because the backfill returned a launch you had already printed live, or because a reconnect replayed recent data. Deduplicate by mint with an expiry window across both sources.

How do I get launches I missed while my process was down?

Call GET /tokens/latest (getLatestTokens(page)) after reconnecting. Each page holds up to 100 tokens and pages 1 to 10 are available, so short outages are covered; for longer ones, persist your progress and accept that the REST window is finite.

Should I use this or Yellowstone gRPC for Pump.fun launches?

Use the WebSocket feed when you want enriched, ready-to-use launch data. Use gRPC when you need the raw transaction and instruction data, slot-level timing, or your own decoding. Many systems use both: gRPC for detection, the Data API for enrichment.

References

  • Track Pump.fun tokens and graduations
  • Stream new tokens and pools over WebSocket
  • Datastream: latest tokens room
  • Get latest tokens (REST)
  • Data API quickstart
  • @solana-tracker/data-api on npm

Companion project

The full example lives at solanatracker/examples/04-stream-pumpfun-launches-websocket. It uses the same environment variables, adds BACKFILL_PAGES for multi-page backfill, and prints aligned rows with curve progress, market cap, risk score and creator.

cp .env.example .env && npm install && npm start
View source on GitHub›Run in StackBlitz›

Runnable Node.js project — clone from GitHub, add keys to .env, then npm start. Use a private local environment for credentials. Check the companion package version before running.

Related Guides

Pump.fun Graduation API: Curve Alerts and Migrations
Pump.fun

Pump.fun Graduation API: Curve Alerts and Migrations

Read more
Pump.fun gRPC: Stream and Decode New Token Mints
Pump.fun

Pump.fun gRPC: Stream and Decode New Token Mints

Read more
Pump.fun First Buyers API: Snipers, Insiders and PnL
Pump.fun

Pump.fun First Buyers API: Snipers, Insiders and PnL

Read more

Products

  • Data API
  • Pump.fun API
  • Solana RPC
  • Dedicated Nodes
  • Yellowstone gRPC
  • Raptor Swap API
  • Enterprise

Trading

  • Swap
  • Latest Tokens
  • Trending
  • Top Gainers
  • Memescope
  • Whale Watch
  • KOL Tracker

Tools

  • Wallet Tracker
  • Rugcheck
  • PnL Leaderboard
  • KOLScan
  • Axiom Leaderboard
  • Photon Leaderboard
  • Bloom Leaderboard
  • FOMO Leaderboard
  • GMGN Leaderboard
  • Pump.fun App Leaderboard
  • Terminal Leaderboard
  • Platform Compare
  • My Positions
  • Teams

Resources

  • Developer Guides
  • Blog
  • Documentation
  • API Reference
  • Status
  • Affiliate Program — 25% recurring, uncapped
Solana TrackerSolana Tracker© 2026
Terms of ServicePrivacy PolicyContact