Webhooks (outbound subscriptions)

Webhooks (outbound subscriptions)

Register HTTPS callbacks for lifecycle events. Management API shipped under P3-004; signing secret returned on create under P6-017.

Endpoints

MethodPathPurpose
POST/v1/webhooks/subscriptionsCreate subscription (returns secret once)
GET/v1/webhooks/subscriptionsList active (no secrets)
DELETE/v1/webhooks/subscriptions/{subscription_id}Deactivate
GET/v1/webhooks/deliveriesRecent 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.completed
  • adjudication.case_created, adjudication.case_resolved
  • intake.submitted, intake.batch_submitted
  • scorecard.created

See SUBSCRIBABLE_EVENTS in apps/backend/app/domain/webhooks.py / OpenAPI enum descriptions.

Signing contract

ItemValue
HeaderX-QueryTek-Review-Signature
AlgorithmHMAC-SHA256, hex digest
Signed bytesRaw HTTP body
Canonical JSONPlatform 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.