Webhooks (outbound subscriptions)
Register HTTPS callbacks for lifecycle events. Management API shipped under P3-004; signing secret returned on create under P6-017.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /v1/webhooks/subscriptions | Create subscription (returns secret once) |
GET | /v1/webhooks/subscriptions | List active (no secrets) |
DELETE | /v1/webhooks/subscriptions/{subscription_id} | Deactivate |
GET | /v1/webhooks/deliveries | Recent delivery attempts |
URL rules (SSRF)
- Scheme must be
https - Hostname must be an FQDN (contains
.) - Private / reserved IP literals rejected
Events
Subscribable set (also * wildcard) includes:
document.status_changed,document.uploaded,document.completedadjudication.case_created,adjudication.case_resolvedintake.submitted,intake.batch_submittedscorecard.created
See SUBSCRIBABLE_EVENTS in apps/backend/app/domain/webhooks.py / OpenAPI enum descriptions.
Signing contract
| Item | Value |
|---|---|
| Header | X-QueryTek-Review-Signature |
| Algorithm | HMAC-SHA256, hex digest |
| Signed bytes | Raw HTTP body |
| Canonical JSON | Platform uses json.dumps(..., sort_keys=True) when building the body |
Store the secret from the create response immediately — it is not returned on list.
Verify sample
python3 scripts/integrator/verify_webhook_signature.py \
--secret "$SIGNING_SECRET" \
--body-file payload.json \
--signature "$HEADER_VALUE"
Delivery status (live)
Outbound dispatch is live (P6-056): matching lifecycle events enqueue webhook_deliveries rows and the job worker POSTs signed HTTPS payloads to subscriber URLs with retries (up to MAX_DELIVERY_ATTEMPTS, default 3) and backoff per ADR-031. Poll GET /v1/webhooks/deliveries for attempt history (pending, delivered, failed). Implement signature verification against this contract before go-live.
Payload envelope (intended)
{
"event": "document.status_changed",
"tenant_id": "<uuid>",
"timestamp": "<iso8601>",
"data": { }
}
Payloads carry IDs/status metadata — not document plaintext.