DEV Community

Zain Khan
Zain Khan

Posted on

I Built a Workflow That Watches Congress Members' Stock Trades in Real Time

Congress members are required to disclose their stock trades, but "disclosed" doesn't mean "easy to find." One source is a raw SEC Atom feed. The other is a government site that occasionally hands you a PDF where the actual trade details are buried in a fixed-width table with no clean structure. Nobody's checking either of these by hand every 45 minutes, so nobody catches a new filing until it's already old news.

I built a workflow that does check, every 45 minutes, pulling from both sources directly, enriching the raw filings with actual transaction data, and firing off an email the moment something genuinely new shows up.

The stack

Built entirely on n8n, pulling straight from SEC EDGAR and House.gov, with results logged to Google Sheets and alerts sent through Gmail.

The interesting part of this one isn't the orchestration, it's the data access. Both SEC EDGAR and House.gov actively push back against generic scraping. SEC EDGAR returns a 403 to any request without a declared, compliant User-Agent, that's their actual fair-access policy, not a bug. House.gov's disclosure ZIPs behave similarly with generic scraping tools. So instead of routing these requests through a scraping service that can't set custom headers, this workflow calls n8n's built-in HTTP helper directly inside Code nodes, with a real User-Agent string identifying the tool and a contact email, exactly what SEC's policy asks for. No trickery, just compliance.

How it works
A 45-minute schedule kicks off two parallel fetches: the SEC EDGAR Form 4 Atom feed, and the House.gov disclosure landing page.
On the House side, a code step scrapes the current year's ZIP archive URL out of the landing page HTML, downloads it as raw binary (again via the built-in HTTP helper, since ScrapeUnblocker 403s on binary files), and decompresses it.
The decompressed archive gets parsed for Periodic Transaction Reports specifically, filtering by FilingType: P, whether the underlying file is XML or tab-delimited text.
On the EDGAR side, the Atom feed gets parsed for Form 4 filings, keeping only the reporting person's entry and dropping the duplicate issuer-side row that the feed also includes.
Both sources merge into one shared schema, then get checked against a running list of filing IDs already logged in Google Sheets. A recency gate also drops anything older than 20 days, so the very first run doesn't treat the entire historical archive as "new."
Genuinely new filings get enriched. For EDGAR, that means fetching the filing's own directory index, locating the ownership XML, and parsing out real transaction data, shares, price, dollar value, deduping identical legs that option exercises tend to double-list. For House filings not covered by the bulk XML, the workflow downloads the actual PTR PDF and extracts trade rows straight out of the PDF text with pattern matching, since the PDF-extraction step wipes out all the upstream fields it would otherwise rely on.
Everything gets logged to Google Sheets, and if anything new made it through, an email alert goes out with the details.
The part worth calling out

The PDF parsing step is the most fragile part of this by design, and the code says so honestly in its own comments: it's best-effort, and a scanned (non-text) PDF will yield nothing. That's a reasonable tradeoff. Government PDFs aren't guaranteed to be clean, and building a workflow that fails loudly on bad input is far better than one that silently fabricates data. The dedup logic against Google Sheets, plus the recency gate, is what keeps this from becoming an alert-spam machine on day one.

Setting it up yourself
Point the schedule trigger at whatever check interval works for you (45 minutes is a reasonable default for filings that don't post more than a few times a day).
Edit the USER_AGENT string in both the EDGAR fetch and enrichment code nodes to include your real contact email, SEC requires this, and it's a one-line edit.
Connect Google Sheets for the seen-filings log and the trade log itself.
Connect Gmail (or swap in Slack, Telegram, whatever fits your workflow) for the alert step.

Final thought
Public disclosure only means something if someone's actually watching it. Most of these filings sit in feeds and PDFs that are technically public but practically invisible unless you're checking constantly. Pulling directly from the source, respecting each site's actual access policy instead of routing around it, is what makes this the kind of workflow you can leave running unattended and trust.

Top comments (0)