SEC filing webhooks: push new filings to your app (2026)
Most code that watches SEC filings is a polling loop. It asks EDGAR "anything new?" every minute and hears "no" almost every time.
A webhook flips that around. The filing comes to you, the moment it lands, already parsed.
Here is what a SEC filing webhook is, how to register one in a single call, how to verify the signed delivery, and how it holds up when your server blinks.
POST /v1/webhooks, free, and each delivery is retried until your endpoint accepts it.What is a SEC filing webhook?
A SEC filing webhook is a URL you register with the API, together with a watchlist of tickers. When one of those companies files, the API sends an HTTP POST to your URL carrying the filing, signed so you can prove it came from us. It inverts polling: instead of your code asking "any new filings?" on a loop, the filing is delivered to you the instant it lands.
The raw filing data is free from the SEC. Being told the moment it appears is the hard part.
SEC EDGAR does publish Atom RSS feeds that update as filings are accepted, usually within one to three minutes, alongside the other free developer resources. But an RSS feed is still something you have to poll and parse. A webhook is the push version: no loop, no feed reader, just an endpoint that gets called.
Why use a webhook instead of polling?
Two reasons: cost and latency. Polling a watchlist for new filings means most requests come back empty, because a given company rarely files on any given minute. You burn API calls and rate limit on "nothing yet." A webhook fires only when a real filing lands, so you pay for events, not for checking, and you hear about it within a couple of minutes instead of on your next slow poll.
Think about what polling 50 tickers every minute actually looks like.
That is 72,000 checks a day, and on a normal day almost every one returns no new filing. The signal you wanted, a fresh 8-K or an insider buy, is a handful of those checks. A webhook sends you those few and stays silent the rest of the time.
The SEC also caps everyone at 10 requests a second, so a hard polling loop eats a budget you would rather spend on real work.
How do you set up a SEC filing webhook?
You register one with a single POST. Call POST /v1/webhooks with a JSON body: your https URL, the tickers to watch (up to 50), and the events you care about. You get back a webhook id and a signing secret, shown once, that you store to verify deliveries. Registering a webhook is free, and you are not charged per delivery.
curl -X POST "https://api.edgrapi.com/v1/webhooks" \
-H "X-API-Key: edgr_your_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/hooks/edgar",
"tickers": ["AAPL", "NVDA", "TSLA"],
"events": ["insider", "8-k", "activist"]
}'
# -> { "webhook": { "id": "wh_...", "secret": "whsec_...", ... } }
The three event types map to the filings people watch most: insider fires on a Form 4, covered in the Form 4 guide; 8-k fires on a material event, in the 8-K guide; and activist fires on a Schedule 13D or 13G, the over-5% stakes. Leave events off and you get insider and 8-K by default.
Registering never replays history. The first time the poller sees a watched company, it records where the filing history stands and fires nothing, so you only ever get filings that land after you subscribed.
What is in the webhook payload?
The parsed filing itself. Each delivery is a JSON POST with the event type, ticker, company, filing date, accession number, and a link, plus a data object holding the parsed content: for an insider trade, the owner and their transactions; for an 8-K, the item codes; for an activist filing, the 13D or 13G details. Most webhook services hand you a filing-landed notice and leave the fetch-and-parse to you.
Here is an insider delivery.
{
"event": "insider.filed",
"ticker": "NVDA",
"company": "NVIDIA CORP",
"filed": "2026-08-25",
"accession": "0001045810-26-000123",
"url": "https://www.sec.gov/Archives/edgar/data/1045810/...",
"ts": 1756100000,
"data": {
"owner": "HUANG JEN-HSUN",
"relationship": ["director", "officer"],
"transactions": [
{"code": "S", "signal": "sell", "shares": 100000,
"price_per_share": 178.4, "value": 17840000.0, "plan_10b5_1": true}
]
}
}
That means your handler can act on the transaction code straight away, no second call to go read the XML. The 8-K payload carries its items array, and the activist payload carries the parsed stake, the same shapes the REST endpoints return.
How do you verify the webhook signature?
Recompute the HMAC. Every delivery carries an X-Edgrapi-Signature header: an HMAC-SHA256 of the raw request body, keyed by your signing secret. On your end, compute the same HMAC over the exact bytes you received and compare with a constant-time function. If they match, the delivery is really from us; if not, drop it. Never trust the body before the signature checks out.
import hmac, hashlib
def verify(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature) # constant-time
Two details matter. Sign the raw bytes, not a re-serialized version of the parsed JSON, or the hashes will not match. And use a constant-time compare like hmac.compare_digest rather than ==, so you do not leak the secret through timing.
The payload also carries a ts timestamp. Reject a delivery whose timestamp is more than a few minutes old, and deduplicate on the accession number, and you have covered replay protection too, per the standard webhook replay guidance.
Webhook, WebSocket, or polling: which should you use?
Use a webhook for targeted, parsed events; a WebSocket stream for the full firehose; polling only as a fallback. A webhook pushes just your watched tickers' events, parsed and signed, with no connection to hold open. A stream sends every filing on EDGAR in real time, which you then filter and parse yourself. Polling is what you fall back to when you cannot accept an inbound request at all.
| Webhook | WebSocket stream | Polling | |
|---|---|---|---|
| You get | Only your tickers' events | Every filing on EDGAR | Whatever you ask for |
| Payload | Parsed | Raw, you parse it | Raw, you parse it |
| You run | An HTTP endpoint | A held-open connection | A request loop |
| Filtering | Server-side | Client-side | Per request |
If you need every form type at sub-second speed, a stream is the right tool, and you accept the parsing and the always-on connection. If you want the parsed event for a specific watchlist with the least to build, the webhook is fewer moving parts.
What happens if your endpoint is down?
You do not lose the filing. Delivery is at-least-once: if your endpoint does not return a 2xx, that event is retried on the next poll cycle, and the next, until it goes through, so a brief restart or deploy does not drop it. The cursor only moves past a filing once you have accepted it.
There is one thing to handle on your side.
Because a retry can re-send an event you already processed, make your handler idempotent: deduplicate on the accession number and ignore one you have seen. That is the same accession that dedupes replays, so a single guard covers both.
For anything you cannot afford to miss, reconcile on a schedule as a backstop: once a day, call the insider or 8-K endpoint for your watchlist and fill any gap. Push for speed, pull to be sure.
Start: register one webhook
Pick one ticker you follow and point a webhook at a URL you control. Before a real filing arrives, call POST /v1/webhooks/{id}/test and the API sends a signed sample delivery, so you can prove your signature check and your handler work end to end.
Grab a free key from the dashboard, register your watchlist, and the next Form 4 or 8-K from those companies lands on your endpoint, parsed and signed, a couple of minutes after it hits EDGAR. Point it at https://api.edgrapi.com and start with a single company.
Frequently asked questions
How do I get a webhook for new SEC filings?
Register one with a single call. POST /v1/webhooks with your https URL, the tickers to watch, and the events you want (insider, 8-k, activist). You get back a webhook id and a signing secret, shown once. From then on, every time a watched company files, your endpoint receives a signed POST with the parsed filing. Registering is free.
How fast do SEC filing webhooks fire after EDGAR?
Within a couple of minutes. Edgrapi's poller checks each watched company against EDGAR roughly every two minutes, and filings usually appear on EDGAR one to three minutes after the SEC timestamp. So a Form 4 or 8-K reaches your endpoint a few minutes after it is filed, far ahead of a quarterly report or a manual check.
Which SEC filings can I get a webhook for?
Three event types: insider trades (Form 4), 8-K material events, and activist stakes (Schedule 13D and 13G, including amendments). You pick any combination per webhook; leave the events field off and you get insider and 8-K by default. Each fires only for the tickers on that webhook's watchlist, up to 50 per hook.
How do I verify a webhook signature?
Each delivery carries an X-Edgrapi-Signature header: an HMAC-SHA256 of the raw request body, keyed by your signing secret. Recompute the same HMAC over the exact bytes you received and compare with a constant-time function like hmac.compare_digest. If it matches, the request is genuine; if not, reject it. Sign the raw bytes, not re-serialized JSON.
What if my server is down when a filing fires?
You do not lose it. Delivery is at-least-once: if your endpoint does not return a 2xx, the event is retried on the next poll cycle until it succeeds, so a brief restart or deploy does not drop it. Because a retry can re-send an event, deduplicate on the accession number so your handler processes each filing once.
Webhook, WebSocket, or polling for SEC filings?
Use a webhook for parsed events on a specific watchlist with the least to build. Use a WebSocket stream if you need every form type at sub-second speed and will filter and parse the firehose yourself. Use polling only as a fallback when you cannot receive an inbound request. For most watchlist monitoring, the webhook is the fewest moving parts.