Home / Blog / Tutorial

How to Build a Live Congress Trading Alert Bot in 15 Minutes

Most retail investors find out about congressional stock trades days or weeks after the fact — scrolling through Twitter, or stumbling on a news article. By then, the market has already reacted.

There's a better way. The STOCK Act requires members of Congress to disclose trades within 45 days. Those disclosures are public the moment they're filed. With the CongressInvests API, you can get notified within hours of a new filing — often before most financial media covers it.

In this guide, we'll build a Python bot that watches specific tickers and fires a Slack alert the moment a new congressional trade is disclosed.

What you'll need

No API key required for the polling approach in steps 1–3. You'll only need a Pro key for the real-time webhook setup in step 4.

Step 1: Your first API call

No setup needed. Run this from your terminal and you'll get back every NVDA trade filed by Congress in the last year:

bash
curl https://congressinfor-production.up.railway.app/trades/NVDA

The response tells you the member's name, chamber, trade type, disclosed dollar range, and — importantly — the last_updated timestamp so you know exactly how fresh the data is:

json — abbreviated
{
  "ticker":           "NVDA",
  "total":            15,
  "last_updated":     "2026-05-31T10:04:17Z",
  "data_lag_minutes": 14,
  "trades": [
    {
      "member":     "John Boozman",
      "chamber":    "Senate",
      "ticker":     "NVDA",
      "trade_type": "buy",
      "amount":     "$1,001 - $15,000",
      "tx_date":    "2026-03-19",
      "disclosed":  "2026-04-14",
      "link":       "https://efdsearch.senate.gov/..."
    }
  ]
}

Step 2: Write the polling watcher

Install requests if you haven't:

bash
pip install requests

This script polls a list of tickers every hour, tracks which trades it's already seen, and prints an alert for anything new. It stays well within the free tier's 100 req/day limit even across four tickers.

python — watcher.py
import requests
import time

BASE    = "https://congressinfor-production.up.railway.app"
TICKERS = ["NVDA", "AAPL", "MSFT", "TSLA"]
seen    = set()

def check():
    for ticker in TICKERS:
        resp = requests.get(f"{BASE}/trades/{ticker}", timeout=10)
        if not resp.ok:
            continue
        for t in resp.json().get("trades", []):
            key = f"{t['member']}:{t['ticker']}:{t['tx_date']}"
            if key not in seen:
                seen.add(key)
                alert(t)

def alert(t):
    action = "BOUGHT" if t["trade_type"] == "buy" else "SOLD"
    print(f"🔔  {t['member']} ({t['chamber']}) {action} {t['ticker']} — {t['amount']}")
    print(f"    Filed: {t['disclosed']}  |  {t['link']}\n")

while True:
    check()
    print("Checked. Sleeping 1 hour…")
    time.sleep(3600)

Step 3: Add Slack notifications

First, create an Incoming Webhook in your Slack workspace. It takes about two minutes and gives you a URL like https://hooks.slack.com/services/T.../B.../....

Replace the alert() function with one that posts a formatted Slack message:

python
SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"

def alert(t):
    action = "🟢 BOUGHT" if t["trade_type"] == "buy" else "🔴 SOLD"
    text = (
        f"*{action}* — {t['member']} ({t['chamber']})\n"
        f"*{t['ticker']}* · {t['amount']} · Filed {t['disclosed']}\n"
        f"<{t['link']}|View filing ↗>"
    )
    requests.post(SLACK_WEBHOOK, json={"text": text}, timeout=5)

Now when Nancy Pelosi files an NVDA trade, you'll get a Slack message within an hour of your next poll cycle.

Tip: Run this on a free-tier cloud VM (Railway, Render, or Fly.io) so it keeps running 24/7 without your laptop staying on.

Step 4: Go real-time with webhooks (Pro)

Polling every hour is fine, but the filing you care about most might land right after a poll. With a Pro API key, you can subscribe to push notifications that fire within minutes of a new filing being detected — no polling loop needed.

Subscribe to a ticker

bash
curl -X POST https://congressinfor-production.up.railway.app/webhooks/subscribe \
  -H "X-Api-Key: YOUR_PRO_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "https://your-server.com/hook",
    "ticker":      "NVDA",
    "events":      ["any"]
  }'

You can also subscribe by politician name instead of ticker — handy if you want to follow a specific member across all their trades:

bash
curl -X POST https://congressinfor-production.up.railway.app/webhooks/subscribe \
  -H "X-Api-Key: YOUR_PRO_KEY" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url": "https://your-server.com/hook", "politician": "Nancy Pelosi"}'

Handle the webhook payload

Your server receives a POST with this payload on every new trade:

json
{
  "event":            "new_trade",
  "ticker":           "NVDA",
  "politician":       "Nancy Pelosi",
  "trade_type":       "Purchase",
  "amount":           "$1,000,001 - $5,000,000",
  "filing_date":      "2026-05-30",
  "transaction_date": "2026-05-15",
  "source":           "House",
  "api_url":          "https://congressinfor-production.up.railway.app/trades/NVDA"
}

A minimal Flask receiver that pipes it straight to Slack:

python — receiver.py
from flask import Flask, request, jsonify
import requests

app          = Flask(__name__)
SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"

@app.route("/hook", methods=["POST"])
def hook():
    t      = request.get_json()
    action = "🟢 BOUGHT" if t["trade_type"] == "Purchase" else "🔴 SOLD"
    text   = (
        f"*{action}* — {t['politician']}\n"
        f"*{t['ticker']}* · {t['amount']} · Filed {t['filing_date']}\n"
        f"<{t['api_url']}|View all {t['ticker']} trades ↗>"
    )
    requests.post(SLACK_WEBHOOK, json={"text": text}, timeout=5)
    return jsonify({"ok": True})

if __name__ == "__main__":
    app.run(port=5000)
Testing locally? Use ngrok to expose your local server: ngrok http 5000. Then subscribe using the ngrok HTTPS URL as your webhook_url.

Step 5: Get an AI trend analysis with one call Pro

The API includes a built-in AI analysis endpoint that tells you whether congressional trading in a stock is bullish, bearish, or mixed — based strictly on the disclosed filing data. No speculation, no outside information about the company.

This endpoint requires a Pro API key in the X-Api-Key header. The analysis is generated once per ticker per day and cached server-side, so all Pro subscribers get an instant response after the first call of the day.

bash
curl -H "X-Api-Key: YOUR_PRO_KEY" \
     https://congressinfor-production.up.railway.app/trades/NVDA/summary

The response adds an ai_analysis key to the normal trade payload:

json
{
  "ticker": "NVDA",
  "total": 50,
  "trades": [ ... ],
  "ai_analysis": {
    "sentiment":        "bullish",
    "summary":          "Congressional trading in NVDA shows 62% buys (31 trades) vs 38% sells over a 10-month period. House members account for the majority of activity...",
    "notable_patterns": "Cleo Fields (House) is responsible for ~27% of all trades with a concentrated buying campaign from June through October 2025...",
    "cached":           true,
    "stats": {
      "total_trades":   50,
      "buys":           31,
      "sells":          19,
      "unique_members": 16,
      "date_range": { "earliest": "2025-06-24", "latest": "2026-04-24" }
    }
  }
}
How caching works: The first caller for a ticker each day triggers a Claude API call (~1–2 seconds). Every subsequent call that day returns the cached result instantly. The cache resets at midnight UTC, so the analysis is always based on today's data.

You can pipe it into your Slack alert to add context alongside the raw trade:

python — enhanced Slack alert
HEADERS = {"X-Api-Key": "YOUR_PRO_KEY"}

def get_sentiment(ticker):
    resp = requests.get(f"{BASE}/trades/{ticker}/summary", headers=HEADERS, timeout=10)
    ai   = resp.json().get("ai_analysis", {})
    return ai.get("sentiment", "unknown"), ai.get("summary", "")

def alert(t):
    sentiment, summary = get_sentiment(t["ticker"])
    emoji   = {"bullish": "🟢", "bearish": "🔴", "mixed": "🟡"}.get(sentiment, "⚪")
    action  = "🟢 BOUGHT" if t["trade_type"] == "buy" else "🔴 SOLD"
    text    = (
        f"*{action}* — {t['member']} ({t['chamber']})\n"
        f"*{t['ticker']}* · {t['amount']} · Filed {t['disclosed']}\n"
        f"{emoji} *Congressional trend ({t['ticker']}):* {sentiment.upper()} — {summary}\n"
        f"<{t['link']}|View filing ↗>"
    )
    requests.post(SLACK_WEBHOOK, json={"text": text}, timeout=5)
No hallucination guarantee: the model is explicitly instructed to use only the trade records returned by the API. It cannot reference the company's business, earnings, news, or any outside context. The buy/sell ratio that drives the sentiment label is computed deterministically in Python before Claude sees it.

What's next

Ready to go real-time?

Pro tier unlocks webhook alerts, 50,000 requests/day, and priority cache refresh — starting at $29/month.

Get Pro access