Forward TweetStream tweets to Discord via webhooks

Forward filtered tweets into any Discord channel via webhooks — no bot user, no OAuth.

Why Discord webhooks

Discord channel webhooks take a single HTTP POST and render the body as a native embed. No Discord bot user, no OAuth flow, no gateway connection on your side.

Pair that with TweetStream's filtered stream and you get a zero-maintenance alerting channel — the worker you run is a thin translator between our WebSocket and Discord's webhook endpoint.

1. Get a Discord webhook URL

In the target channel, open Edit Channel → Integrations → Webhooks → New Webhook. Copy the webhook URL — it encodes the channel ID and a secret token. Treat it as a credential.

2. Install worker dependencies

The examples below assume a fresh project. Install the package imports first so the snippets run without module errors.

Node.js:

npm install ws

Python:

pip install websockets aiohttp

3. Forward tweets (Node.js)

A minimal Node.js worker that posts every tweet from the stream as a Discord embed.

import WebSocket from "ws";

const API_KEY = process.env.TWEETSTREAM_API_KEY!;
const DISCORD_WEBHOOK_URL = process.env.DISCORD_WEBHOOK_URL!;
const WS_URL = "wss://ws.tweetstream.io/ws";
const PROTOCOLS = ["tweetstream.v1", `tweetstream.auth.token.${API_KEY}`];

function tweetUrl(handle: string, id: string) {
  return `https://x.com/${handle}/status/${id}`;
}

async function forwardToDiscord(embed: Record<string, unknown>) {
  const res = await fetch(DISCORD_WEBHOOK_URL, {
    body: JSON.stringify({ embeds: [embed] }),
    headers: { "content-type": "application/json" },
    method: "POST",
  });
  if (!res.ok) {
    console.error("discord webhook failed", res.status, await res.text());
  }
}

function connect() {
  const ws = new WebSocket(WS_URL, PROTOCOLS);

  ws.on("message", async (raw) => {
    const env = JSON.parse(raw.toString());
    if (env.t !== "tweet" || env.op !== "content") return;

    const tweet = env.d;
    const handle = tweet.author?.handle ?? "unknown";

    await forwardToDiscord({
      author: { name: `@${handle}` },
      description: tweet.text,
      title: "New tweet",
      timestamp: new Date(tweet.createdAt ?? Date.now()).toISOString(),
      url: tweetUrl(handle, tweet.tweetId),
    });
  });

  ws.on("close", () => {
    console.warn("socket closed, reconnecting in 5s");
    setTimeout(connect, 5_000);
  });

  ws.on("error", (err) => {
    console.error("socket error", err);
    ws.close();
  });
}

connect();

3b. Forward tweets (Python)

Same worker in Python using websockets and aiohttp.

import asyncio
import json
import os

import aiohttp
import websockets

API_KEY = os.environ["TWEETSTREAM_API_KEY"]
DISCORD_WEBHOOK_URL = os.environ["DISCORD_WEBHOOK_URL"]
WS_URL = "wss://ws.tweetstream.io/ws"
SUBPROTOCOLS = ["tweetstream.v1", f"tweetstream.auth.token.{API_KEY}"]


async def forward(session, tweet):
    handle = tweet["author"].get("handle", "unknown")
    embed = {
        "title": "New tweet",
        "description": tweet["text"],
        "url": f"https://x.com/{handle}/status/{tweet['tweetId']}",
        "author": {"name": f"@{handle}"},
    }
    async with session.post(DISCORD_WEBHOOK_URL, json={"embeds": [embed]}) as resp:
        if resp.status >= 300:
            print("discord webhook failed", resp.status, await resp.text())


async def run():
    async with aiohttp.ClientSession() as session:
        async with websockets.connect(WS_URL, subprotocols=SUBPROTOCOLS) as ws:
            async for raw in ws:
                env = json.loads(raw)
                if env["t"] == "tweet" and env["op"] == "content":
                    await forward(session, env["d"])


asyncio.run(run())

4. Filter on detected tokens (optional)

Swap the listener to react on tweet/meta envelopes so you only alert when TweetStream has detected a ticker, contract address, or DEX URL.

// Only forward tweets that mention tracked tickers or contract addresses.
ws.on("message", async (raw) => {
  const env = JSON.parse(raw.toString());
  if (env.t !== "tweet") return;

  // tweet/meta carries the detection payload.
  if (env.op === "meta") {
    const tokens = env.d.detected?.tokens ?? [];
    if (tokens.length === 0) return;

    await forwardToDiscord({
      title: `${tokens.length} token${tokens.length > 1 ? "s" : ""} detected`,
      description: tokens
        .map((t: { symbol: string; priceUsd?: number }) =>
          t.priceUsd ? `$${t.symbol} — $${t.priceUsd.toFixed(6)}` : `$${t.symbol}`,
        )
        .join("\n"),
    });
  }
});

Rate-limit and deployment notes

  • Discord webhooks allow roughly 30 requests per minute per webhook. Batch or throttle if your stream exceeds that.
  • Discord embeds render better than raw text — use title, description, url, and author fields.
  • Keep the webhook URL in a secret manager. Rotating it means regenerating the integration in Discord.
  • For alerts per tracked handle, set discordWebhook on each tracked account via the dashboard — TweetStream will route matching tweets without a worker process.

Start real-time Twitter WebSocket alerts today

WebSocket delivery, OCR, and token detection - no infrastructure to build.

Start 7-Day Trial

From $199/mo · Basic/Elite 7-day trial · OCR + token detection included

Related Pages