TweetStream

Twitter to Discord Webhook Setup

Complete tutorial for routing real-time Twitter/X alerts into Discord channels with webhooks, code examples, and rich embed formatting.

Why Route Twitter Alerts to Discord?

Many crypto teams already coordinate in Discord through trading rooms, research channels, and execution dashboards. Routing Twitter/X alerts there keeps the signal in the same workspace where the team can review it.

Discord webhooks can post formatted Twitter/X alerts to the channels your team already monitors, so no one has to watch another app or inbox.

What Is a Discord Webhook?

A Discord webhook is a URL that accepts HTTP POST requests and posts messages into a specific channel. You do not need a bot account, OAuth tokens, or gateway connections. Just send JSON to the URL and the message appears in the channel.

Webhook URL format: https://discord.com/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN

Discord webhooks support plain text messages, rich embeds (with titles, colors, fields, images, and footers), file attachments, and up to 10 embeds per message.

Step 1: Create a Discord Webhook

  1. Open your Discord server and navigate to the channel where you want alerts
  2. Click the gear icon (Edit Channel) next to the channel name
  3. Go to Integrations → Webhooks → New Webhook
  4. Name the webhook (e.g., 'Twitter Alerts') and optionally set an avatar
  5. Click 'Copy Webhook URL' — save this URL securely
  6. Click Save

Keep your webhook URL private. Anyone with the URL can post messages to your channel.

Step 2: Understand the Payload Format

Discord webhooks accept a JSON payload with two main fields: content for plain text and embeds for rich formatted messages. For trading alerts, embeds are preferred because they support structured data, color-coding, and inline fields.

Step 3: Send Alerts with Node.js

javascript
const WEBHOOK_URL = 'https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN';
 
function normalizeHandle(handle) {
  return String(handle ?? '').replace(/^@/, '');
}
 
function postLabel(tweet) {
  if (tweet.author.platform === 'binance_square') return 'Binance Square post';
  if (tweet.author.platform === 'truth_social') return 'Truth';
  return 'X post';
}
 
function postUrl(tweet, handle) {
  if (tweet.author.platform === 'binance_square') {
    return /^\d+$/.test(tweet.tweetId)
      ? `https://www.binance.com/en/square/post/${tweet.tweetId}`
      : undefined;
  }
  if (tweet.author.platform === 'truth_social') {
    return tweet.link ?? `https://truthsocial.com/@${handle}/posts/${tweet.tweetId}`;
  }
  return tweet.link ?? `https://x.com/${handle}/status/${tweet.tweetId}`;
}
 
async function sendPostAlert(tweet) {
  const handle = normalizeHandle(tweet.author.handle);
  const embed = {
    title: postLabel(tweet),
    description: tweet.text,
    color: 3447003, // Blue
    url: postUrl(tweet, handle),
    author: { name: handle ? `@${handle}` : postLabel(tweet) },
    fields: [],
    footer: { text: 'TweetStream Alert' },
    timestamp: new Date().toISOString(),
  };
 
  // Add detected tokens as fields
  if (tweet.tokens?.length) {
    for (const token of tweet.tokens) {
      embed.fields.push({
        name: token.symbol,
        value: `$${token.price} | ${token.chain}`,
        inline: true,
      });
    }
  }
 
  const response = await fetch(WEBHOOK_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ embeds: [embed] }),
  });
 
  if (response.status === 429) {
    const retryAfter = response.headers.get('Retry-After');
    console.log(`Rate limited. Retry after ${retryAfter}s`);
  }
}

Step 4: Send Alerts with Python

python
import requests
import time
from typing import Optional
 
WEBHOOK_URL = "https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN"
 
def normalize_handle(handle: object) -> str:
    return str(handle or "").removeprefix("@")
 
def post_label(tweet: dict) -> str:
    platform = tweet.get("author", {}).get("platform", "twitter")
    if platform == "binance_square":
        return "Binance Square post"
    if platform == "truth_social":
        return "Truth"
    return "X post"
 
def post_url(tweet: dict, handle: str) -> Optional[str]:
    platform = tweet.get("author", {}).get("platform", "twitter")
    if platform == "binance_square":
        post_id = str(tweet["tweetId"])
        return f"https://www.binance.com/en/square/post/{post_id}" if post_id.isascii() and post_id.isdigit() else None
    if platform == "truth_social":
        return tweet.get("link") or f"https://truthsocial.com/@{handle}/posts/{tweet['tweetId']}"
    return tweet.get("link") or f"https://x.com/{handle}/status/{tweet['tweetId']}"
 
def send_post_alert(tweet: dict) -> None:
    handle = normalize_handle(tweet.get("author", {}).get("handle"))
    embed = {
        "title": post_label(tweet),
        "description": tweet["text"],
        "color": 3447003,  # Blue
        "url": post_url(tweet, handle),
        "author": {"name": f"@{handle}" if handle else post_label(tweet)},
        "fields": [],
        "footer": {"text": "TweetStream Alert"},
    }
 
    # Add detected tokens
    for token in tweet.get("tokens", []):
        embed["fields"].append({
            "name": token["symbol"],
            "value": f"${token['price']} | {token['chain']}",
            "inline": True,
        })
 
    payload = {"embeds": [embed]}
    resp = requests.post(WEBHOOK_URL, json=payload)
 
    if resp.status_code == 429:
        retry_after = resp.json().get("retry_after", 1)
        time.sleep(retry_after)
        requests.post(WEBHOOK_URL, json=payload)

Rich Embed Formatting for Trading Alerts

Discord embeds support extensive formatting. Here are the key fields for trading alerts:

  • title — The alert headline (e.g., account name or token symbol)
  • description — The tweet text or alert body
  • color — Decimal color value (green: 3066993, red: 15158332, blue: 3447003)
  • fields — Name/value pairs for structured data (token, price, chain)
  • footer — Timestamp or source attribution
  • thumbnail — Small image (e.g., token logo or user avatar)
  • url — Link back to the original tweet

Discord Webhook Rate Limits

Discord enforces webhook rate limits and returns HTTP 429 with a Retry-After value when you send too quickly. Exact limits can vary by route and context, so build around the response headers instead of hard-coding one universal number.

For high-volume alert channels, implement these strategies:

  • Queue messages and send in batches rather than one-at-a-time
  • Use multiple webhooks across different channels for different alert types
  • Respect Retry-After when you receive 429 responses
  • Bundle multiple alerts into a single message using multiple embeds (up to 10 per message)
  • Treat response headers as the source of truth for retry timing

Troubleshooting Common Issues

  • Webhook returns 401 Unauthorized — The webhook URL has been deleted or regenerated. Create a new webhook.
  • Messages not appearing — Check that the webhook is assigned to the correct channel and the bot has Send Messages permission.
  • Embeds not rendering — Verify your JSON structure. The embeds field must be an array, even for a single embed. Color must be a decimal number, not hex.
  • Rate limited (429) — Slow down your request rate. Queue messages and implement backoff.
  • Content too long — Embed descriptions are limited to 4,096 characters. Total embed content across all embeds must not exceed 6,000 characters.

TweetStream Discord delivery

If you do not want to build and maintain a webhook pipeline yourself, TweetStream delivers alerts directly to Discord with zero code. Configure your accounts and webhooks in the dashboard, and TweetStream handles filtering, enrichment (OCR, token detection, live prices), formatting, and delivery. See the Discord crypto alerts page for setup details.

Put this workflow into production faster

This guide covers the workflow. TweetStream handles monitored-post ingestion, token detection, and OCR, then delivers posts, updates, deletes, pins, profile changes, follows, unfollows, and enriched JSON payloads over WebSocket. Supported plans also include history. Your client reconnects the WebSocket as documented and routes received events downstream. Start a 3-day trial with your own accounts.

Start 3-day trial

Frequently Asked Questions

TweetStream Team

Last updated: June 2026

Put the live feed to work

Start with the X accounts that matter, then route events to your bots, alerts, and trading workflows.