Skip to main content

Overview

Instead of polling our API for new data, you can register an HTTPS endpoint and we’ll POST to it the moment an event fires. Use webhooks to wake up an agent on a fresh earnings release, new financial statements, extracted KPIs and guidance, or insider filings; push records into your warehouse; or trigger downstream automation.
New to webhooks? The setup guide walks you through a working Python receiver end-to-end in under 15 minutes. This page is the reference you’ll come back to once you’re integrated.

How it works

Five steps to get from zero to receiving events:
1

Create a destination

Register an HTTPS endpoint and the events you want to receive from the Webhooks dashboard.
2

Set up your endpoint

Stand up an HTTPS handler that can accept JSON POSTs and read the raw request body. See the setup guide for Python + Node examples.
3

Verify the signature

Confirm each request came from us using the FD-Signature header. See Verifying the signature.
4

Test with a canned event

Fire a test event from the dashboard’s destination row and confirm your endpoint returns 2xx within 10 seconds.
5

Go live

Walk through the production checklist before flipping real customer flows onto your handler.
Webhooks are available on Pro and Enterprise plans. Manage your plan from the dashboard.

Production checklist

Before relying on webhooks for production-critical flows, work through each of these:
  1. Use HTTPS, not HTTP. Your endpoint URL must start with https://. We won’t deliver to plain http:// in production. Security →
  2. Confirm each request actually came from us. Use the FD-Signature header to check authenticity. Skip this and anyone could forge events. How to verify →
  3. Reply within 10 seconds. Send back 200 OK quickly, then do the heavy work in the background. Slow replies count as failures and we’ll retry. Retries →
  4. Don’t process the same event twice. We may deliver the same event more than once. Track which event.id values you’ve handled and skip duplicates. Idempotency →
  5. Keep your signing secret safe. Treat it like a password — never commit it to git, store it in a secret manager, and rotate it from the dashboard if it ever leaks. Security →

Event types

Earnings — within minutes of the filing hitting EDGAR: Financial statements — within minutes of a 10-K, 10-Q, 20-F, or 6-K being processed: Segmented financials — same filings, only when the filing reports segment breakdowns for that statement: Earnings intelligence — minutes after earnings.created, once we finish analyzing the release; each fires only when the release contains that data: Insider activity — once daily, early morning UTC, covering the previous day’s filings:

Payload format

Every delivery is a POST of a JSON envelope wrapping a resource. The envelope itself is identical across event types; data.object carries the resource and its shape depends on the event type.

Envelope

earnings.created resource

The data.object is identical to a single entry from the GET /earnings/ API response. Any parser you’ve written against the Earnings API will work unchanged against the webhook payload. See the Earnings API reference for the full field schema, types, and worked examples — we don’t duplicate it here so the two stay in lockstep. Here’s an example envelope wrapping a single entry:

Multiple events per earnings period

A single quarter of earnings can produce more than one earnings.created event as the SEC filing chain progresses:
  1. The 8-K earnings release fires first — typically hours after announcement.
  2. The 10-Q (or 10-K for the fiscal-year quarter) follows ~30–45 days later with the full GAAP-audited numbers, segments, and footnotes-derived metrics.
Both events have the same (ticker, report_period) but different accession_numbers and different data.object.source_type values ("8-K" vs "10-Q" vs "10-K"). Three reasonable ways to handle this in your handler:
  • Dedupe by (ticker, report_period) — process the first event you see, ignore the later one. Use when latency matters more than completeness.
  • Always process the most recent source_type — keep the 10-Q’s richer data, discard the earlier 8-K once it arrives. Use when you need the full GAAP record.
  • Process both — emit your downstream signal twice. Use when you have separate “first signal” and “final record” consumers (e.g., real-time alerting + analytics warehouse).
The dedup key on our side is event.id (the envelope UUID) — same one we use for retry-idempotency. The dedup key on your side is (ticker, report_period) if you want once-per-quarter semantics. Foreign issuers and microcaps that don’t file 8-Ks will fire only one event per period (the 10-Q / 10-K / 20-F).

Financial statement resources

The four statement events (income_statements.created, balance_sheets.created, cash_flow_statements.created, financial_metrics.created) and the three segment events (income_statement_segments.created, balance_sheet_segments.created, cash_flow_statement_segments.created) share one filing-level data.object shape: a header identifying the filing, plus an array named after the event’s dataset. Each array entry is identical to a single entry from the corresponding API response, so any parser you’ve written against these endpoints works unchanged here: A filing usually produces one entry per array; when we can also compute a trailing-twelve-months view from it, a second entry with "period": "ttm" appears alongside the quarterly or annual one. Example envelope for income_statements.created (fields elided; see the API reference for the full schema):
The segment events fire only when the filing actually reports segment breakdowns for that statement. Income statement segments are the most common; balance sheet and cash flow segment breakdowns are rarer, so expect those two events for fewer filings.

Earnings intelligence resources

operating_kpis.created, forward_guidance.created, and non_gaap_metrics.created fire minutes after earnings.created, once we finish analyzing the release. Each fires only when the release contains that kind of data, and they share a release-level data.object shape: Each array entry is identical to a single entry from the corresponding API response: Because earnings.created fires when the release is parsed and these events fire when the analysis completes, expect a gap of a few minutes between them for the same accession_number. Use the accession number to join the two if your pipeline consumes both.

insider_trades.created resource

Fires when a company insider reports buying or selling stock: SEC Form 4, Form 5 annual statements that include transactions, and their amendments (4/A, 5/A). One event per filing. The data.object is a filing-level record: a header identifying the filing, plus an insider_trades array with one entry per reported transaction. Each entry is identical to a single entry from the GET /insider-trades API response, so any parser you’ve written against that API works unchanged here. See the API reference for the full field schema. Example envelope:
Amendments. An amendment (4/A, 5/A) is its own filing with its own accession_number, so it fires its own event containing the complete, amended set of transactions. Treat its contents as replacing what the original filing reported.

insider_ownership.created resource

Fires when an insider reports positions they hold rather than trades: SEC Form 3 initial ownership statements, Form 5 filings that include holdings, and their amendments (3/A, 5/A). One event per filing.
A single Form 5 that reports both transactions and holdings fires both events: one insider_trades.created and one insider_ownership.created, sharing the same accession_number.
The data.object carries the same filing-level header as insider_trades.created (ticker, accession_number, filing_date, form_type), with an insider_ownership array in place of insider_trades. Each entry describes one held position:

Headers

Every request includes:

Verifying the signature

The FD-Signature header lets you confirm the request actually came from us and wasn’t tampered with. The signature is an HMAC-SHA256 of {timestamp}.{raw_body} using your destination’s signing secret.
Always verify against the raw request bytes. If you parse the body to JSON and re-serialize before hashing, the bytes won’t match and verification will fail.
Use a constant-time comparison (hmac.compare_digest / crypto.timingSafeEqual) — a normal == is vulnerable to timing attacks.

Retries

A delivery succeeds when your endpoint returns any 2xx response within 10 seconds. Anything else — a 4xx, a 5xx, a connection error, or a timeout — counts as a failure, and we’ll retry on this schedule: After three failed attempts we stop trying that specific event. The destination stays active and keeps receiving new events normally — you’ll just see this delivery marked Dead in the dashboard.
Auto-disable safety net. If a destination piles up 50 consecutive failed deliveries, we automatically disable it so we don’t keep pounding on a broken endpoint. You’ll see Auto-disabled on the row with a short reason. Once you’ve fixed the issue, re-enable it from the row’s expanded view.

Idempotency

The same event.id can arrive more than once — replays, retries that succeed late, network races. Your handler must be idempotent:
  • Dedupe on id (e.g., insert into a processed_events table with a unique constraint and ignore conflicts).
  • Don’t trust delivery order; events for the same resource can interleave.

Test mode

The Send test event button on each destination row fires a canned event with livemode: false, shaped exactly like a real event of the destination’s subscribed event type: an earnings destination gets an earnings.created test, an insider destination gets an insider-shaped test. If your handler parses the test event, it parses real events. Test events are delivered only to the destination you fired them from, and are throttled to 1 per minute per destination.

Security

  • HTTPS only. We refuse to deliver to plain http:// URLs in production.
  • SSRF defense. We resolve your URL’s IP at every delivery attempt and reject private (RFC 1918), loopback, link-local, and metadata-service ranges.
  • Signing-secret rotation. Use Regenerate secret on the destination’s row whenever you suspect a leak. The old secret stops working as soon as you rotate, so deploy the new secret to your handler in lockstep.
  • Secret access. Re-copy your secret from the destination’s row in the dashboard if you misplace it. Retrieval is gated by your dashboard login; API keys cannot read signing secrets.

Limits

  • 5 active destinations per account. Disable or delete unused ones to free up slots.
  • A destination can subscribe to any combination of event types — one endpoint can receive everything.

Troubleshooting

Destination keeps auto-disabling. Open the destination’s expanded row and check Recent failures. Your endpoint is probably 5xx-ing or timing out beyond 10s. The fastest debug path is replaying a recent failed delivery from Recent events and inspecting the response body that came back. Signature verification fails. Three usual suspects:
  1. You parsed the body before computing the HMAC. Always verify the raw bytes.
  2. The signing secret in your config is stale after a rotation. Re-copy from the dashboard.
  3. Your server has clock drift > 5 minutes. We reject signatures outside the skew window.
No events arrive at all. Confirm the destination is Active (not Disabled). Fire a test event from the row’s menu — if that doesn’t arrive within ~5 seconds, the destination URL is unreachable from our network (check firewalls / IP allowlists). Still stuck? Open a ticket via Support and include the destination id + a recent event id from the dashboard.

Next steps