Register a URL once and receive a signed JSON payload the instant a congressional trade, filing, or contract lands — no polling.
Pro plan required. Webhooks are a Pro feature — you need a Pro API key to register one (free keys are rejected with a 403). See plans →
You need a Pro API key and a public URL that accepts POST. For testing, grab a throwaway URL at webhook.site.
curl -X POST https://api.disclosedcapitol.com/alerts/subscriptions \
-H "DC-API-Key: <your-pro-key>" \
-H "Content-Type: application/json" \
-d '{
"alert_type": "trade.created",
"delivery": "webhook",
"webhook_url": "https://your-server.com/hook",
"webhook_secret": "a-strong-secret-you-choose",
"filter_value": "NVDA"
}'
# → { "id": 12, "status": "created" }curl -X POST https://api.disclosedcapitol.com/alerts/subscriptions/12/test \
-H "DC-API-Key: <your-pro-key>"
# → { "delivered": true, "event": "trade.created", "test": true }Every delivery is a POST with a { "event", "data" } body. We sign the raw request body with your webhook_secret using HMAC-SHA256, hex-encoded, in the X-DisclosedCapitol-Signature header.
Verify against the raw bytes, before parsing JSON, using a constant-time compare.
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)const crypto = require("crypto");
function verify(rawBody, signature, secret) {
const expected = crypto.createHmac("sha256", secret)
.update(rawBody).digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected), Buffer.from(signature));
}Each data object mirrors the matching REST resource, so REST and webhook consumers parse identically.
A new congressional filing appears in the public index — before the PDF is even parsed.
member · party · chamber · tickers · est_amount_high · is_high_signal
A new congressional stock trade is ingested (post-parse).
politician_name · party · chamber · ticker · trade_type · amount_range · dates
A new SEC Form-4 — a company insider buys or sells their own stock.
insider_name · ticker · transaction_type · shares · total_value · has_politician_overlap
A new federal lobbying (LDA) filing is ingested.
registrant_name · client_name · amount · filing_period · url
A new federal government contract award (USASpending).
recipient_name · ticker · awarding_agency · amount · award_date
A new SEC 13D/G — an activist or >5% stakeholder discloses a position.
filer_name · issuer_name · ticker · percent_of_class · position_value
Your endpoint should return 2xx fast (do slow work async), verify the signature before trusting the payload, and dedupe on data.id.
A minimal endpoint that verifies, deduplicates, and acknowledges fast.
import hmac, hashlib
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = "a-strong-secret-you-choose"
seen = set() # use a real store in production
@app.post("/hook")
def hook():
raw = request.get_data() # raw bytes — verify BEFORE parsing
sig = request.headers.get("X-DisclosedCapitol-Signature", "")
expected = hmac.new(SECRET.encode(), raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, sig):
abort(401)
evt = request.get_json()
key = (evt["event"], evt["data"]["id"])
if key in seen:
return "", 200 # duplicate — ack & ignore
seen.add(key)
handle(evt["event"], evt["data"]) # your logic
return "", 200 # ack fast; do slow work async| GET | /alerts/webhook-events | public | Event catalog |
| GET | /alerts/webhooks/guide | public | Full machine-readable guide |
| POST | /alerts/subscriptions | key | Create a subscription |
| GET | /alerts/subscriptions | key | List subscriptions |
| DEL | /alerts/subscriptions/{id} | key | Delete a subscription |
| POST | /alerts/subscriptions/{id}/test | key | Test-fire |
| POST | /alerts/subscriptions/{id}/rotate-secret | key | Rotate signing secret |
| GET | /alerts/log | key | Delivery history |
Base URL api.disclosedcapitol.com · full machine-readable guide at /alerts/webhooks/guide.