Documentation

Webhooks

Register a URL once; matching events on your watchlist arrive as a signed POST within seconds of being persisted — no polling loop, no missed events between requests. Available on the Business plan.

Webhooks are push delivery to your endpoint. For public poll feeds (Atom / JSON) without a watchlist, see Atom feed (RSS+).

1Create an endpoint

POST /api/user/webhooks — an empty event_types list subscribes to every event type. The secret is returned once on create/update — subsequent GET calls redact it to null, so store it when you create the endpoint.

curl -X POST https://api.openfilings.org/api/user/webhooks \
  -H "X-API-Key: of_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.example.com/openfilings-webhook",
    "event_types": ["filing.discovered"],
    "secret": "whsec_your_own_random_string",
    "active": true
  }'

Response:

{
  "id": "b6b8e0b0-....-....-....-............",
  "url": "https://yourapp.example.com/openfilings-webhook",
  "event_types": ["filing.discovered"],
  "secret": "whsec_your_own_random_string",
  "active": true,
  "created_at": "2026-08-08T09:00:00+00:00",
  "updated_at": "2026-08-08T09:00:00+00:00"
}

PATCH /api/user/webhooks/{id} updates any subset of url, event_types, secret, active — omitted fields are left unchanged. POST /api/user/webhooks/{id}/test fires one signed webhook.test ping synchronously so you can verify your handler without waiting for a real filing.

2Envelope

Every delivery is one JSON object — event, version, timestamp, and an event-specific data object:

{
  "event": "filing.discovered",
  "version": "1",
  "timestamp": "2026-08-08T09:14:02.331Z",
  "data": {
    "canonical_key": "sec:0000320193",
    "ticker": "AAPL",
    "market_id": "us",
    "form_type": "10-Q",
    "filing_id": "3f1b7e2a-....-....-....-............",
    "filing_date": "2026-08-08"
  }
}

3Verify the signature

Requests carry X-Webhook-Signature and X-Webhook-Timestamp (the same value embedded in the body's timestamp):

POST /openfilings-webhook HTTP/1.1
Content-Type: application/json
X-Webhook-Signature: sha256=6b0f...e2a1
X-Webhook-Timestamp: 2026-08-08T09:14:02.331Z

{"event":"filing.discovered","version":"1", ...}

Signature = HMAC-SHA256(secret, f"{timestamp}." + raw_body_bytes), hex-encoded with a sha256= prefix. Verify against the raw request body bytes — re-serializing parsed JSON before verifying will break the signature if key order or whitespace differs. Use a constant-time comparison (hmac.compare_digest / crypto.timingSafeEqual).

Python — FastAPI (recommended)

import os
from fastapi import Depends, FastAPI
from openfilings.integrations.fastapi import openfilings_webhook_dependency
from openfilings.webhooks import WebhookEvent

app = FastAPI()
verify = openfilings_webhook_dependency(os.environ["OPENFILINGS_WEBHOOK_SECRET"])

@app.post("/openfilings-webhook")
async def on_event(event: WebhookEvent = Depends(verify)) -> dict:
    if event.event == "filing.discovered":
        print(event.data["ticker"], event.data["filing_id"])
    return {"ok": True}

pip install openfilings[fastapi] openfilings_webhook_dependency verifies the raw body and yields a typed WebhookEvent. The core verify_webhook() helper only imports the standard library and works in a Lambda without the rest of the SDK.

Python — Flask

from flask import Flask, request
from openfilings import verify_webhook
from openfilings.webhooks import InvalidSignatureError

app = Flask(__name__)
SECRET = "whsec_your_own_random_string"

@app.route("/openfilings-webhook", methods=["POST"])
def on_event():
    try:
        event = verify_webhook(request.get_data(), request.headers, SECRET)
    except InvalidSignatureError:
        return "invalid signature", 400
    if event.event == "filing.discovered":
        print(event.data["ticker"], event.data["filing_id"])
    return "ok", 200

Or pip install openfilings[flask] and verify_flask_webhook(request, SECRET).

Python — standard library only

import hashlib
import hmac

def verify(body: bytes, signature_header: str, timestamp_header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), f"{timestamp_header}.".encode() + body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)

Node.js

const crypto = require("crypto");

function verify(rawBody, signatureHeader, timestampHeader, secret) {
  const expected =
    "sha256=" +
    crypto
      .createHmac("sha256", secret)
      .update(`${timestampHeader}.`)
      .update(rawBody)
      .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}

4Event catalog

eventFires when
filing.discoveredA new filing was persisted for a ticker on your watchlist.
press_release.discoveredA wire headline matched a watched ticker (Pro+).
insider_transaction.discoveredA Form 4 / insider transaction was parsed for a watched ticker.
earnings_release.upcomingA tracked issuer has an earnings release coming up.
earnings_release.reportedA tracked issuer's earnings release was reported.
earnings_guidance.availableForward guidance became available on a tracked call.
event_transcript.availableAn earnings call transcript finished processing.

Pass the exact strings above in event_types — leave the list empty to receive all of them. A synthetic webhook.test event is only sent by the manual test-ping endpoint, never as a real notification.

5Retry policy

  • A non-2xx response or timeout is retried up to 5 times with a fixed 60-second delay between attempts.
  • Any 4xx response is treated as permanent — your endpoint rejected the payload, so it is not retried. Fix the handler, then use the test ping to confirm before the next real event.
  • Deliveries are best-effort, at-least-once — a handler should be idempotent on the envelope's identifiers (e.g. filing_id) in case of a retried delivery after a slow 2xx.
Outbound requests are made with a pinned egress helper that re-resolves and re-validates the target host on every attempt (rejects private/link-local ranges) — point url at a public HTTPS endpoint you control.

6Python SDK

pip install openfilings wraps the same REST API used above with typed models and automatic 429 backoff, plus the verify_webhook() helper shown in step 3:

client.webhooks.create(
    "https://yourapp.example.com/openfilings-webhook",
    event_types=["filing.discovered"],
    secret="whsec_...",
)
client.webhooks.test(webhook_id)