Webhooks — events, signatures, Slack and Teams
Webhooks deliver workspace events to your endpoint as JSON POSTs. They are an Enterprise+ feature, configured at /settings/webhooks. Slack, Microsoft Teams, and Discord endpoints are auto-detected and rendered with native message cards.
Event types
| Event | When it fires |
|---|---|
brief.generated | A daily Brief completes synthesis and validation |
brief.retracted | A Brief paragraph is retracted via the moderation queue |
forecast.prediction_created | A new Forecast prediction crosses the publish threshold |
watch.alert_fired | A watched agency, vendor, or keyword triggers an alert |
saved_search.alert_fired | A saved-search delta crosses the per-search threshold |
case.evidence_added | Evidence is pinned to a case |
Each event payload includes id, event, created_at, workspace_id, and a typed data block. The full payload schema lives in the public API docs at /docs/api.
Signature verification
Every delivery includes an X-FedGrade-Signature header with the HMAC-SHA256 of the raw body, signed with your endpoint's signing secret. Verify before processing:
signed = hmac_sha256(signing_secret, raw_body)
ok = constant_time_equal(signed_hex, header_value)
Reject any request where the signature doesn't match. Reject any request whose X-FedGrade-Timestamp is more than 5 minutes off your server clock — that protects against replay.
Rotate the signing secret at /settings/webhooks/{endpoint} → "Rotate secret." Like API keys, rotation has a 24-hour grace window during which both the old and new secret will validate.
Slack, Teams, Discord auto-rendering
If your endpoint hostname matches hooks.slack.com, outlook.office.com/webhook/, or discord.com/api/webhooks/, the delivery is reformatted into the native message format for that platform:
- Slack — Block Kit with a header, paragraph block, and "View in FedGrade" action button
- Teams — Adaptive Card with the same layout
- Discord — Embed with title, description, and inline-field metadata
No transformation is applied to other hostnames — your endpoint receives the raw JSON payload.
Retries
Failed deliveries (non-2xx response, or timeout past 10s) are retried with exponential backoff. The delay after attempt n is 2ⁿ seconds, capped at one hour — so in practice the retry gaps are ~2s, 4s, 8s, 16s. A retry sweep runs every 5 minutes and picks up deliveries whose next_retry_at has elapsed, so the observed gap is the backoff rounded up to the next sweep.
A delivery is attempted at most 5 times; after the fifth it is dead-lettered. Separately, an endpoint is auto-disabled after 10 consecutive failed deliveries (a running counter — there is no one-hour window). Disabled endpoints are flagged at /settings/webhooks with the reason recorded.
Testing
Each endpoint has a "Send test event" button that delivers a synthetic brief.generated payload with data.test == true. Use this for development setup and to verify signature handling without waiting for a real Brief.
Audit
Every webhook delivery (success or failure) is logged at /settings/webhooks/{endpoint}/deliveries with the request ID, timestamp, response status, response body (first 4KB), and signature header. Deliveries are retained for 30 days; Government tier extends to 7 years.
Last updated 2026-05-28.