Signing webhooks
The public catalogue has exactly five event types:
| Type | Payload addition |
|---|---|
partner.ping | Console test payload |
signing_request.completed | terminal status |
signing_request.declined | terminal status |
signing_request.item.signed | signingRequestItemId, documentKind, signedAt, sessionStatus |
signing_request.cancelled | terminal status |
Every envelope contains id, type, dataVersion (2026-09-01), occurredAt, and thin data. Signing data always carries decimal-string groupId, signingRequestId, and contactId. It never contains tokens, reasons, hashes, email, IP, user agent, URLs or file content. An item event deliberately has sessionStatus, not terminal status.
Deliveries include X-Sx-Webhook-Timestamp, X-Sx-Webhook-Signature, and X-Sx-Webhook-Event-Id. Verify the raw body with <timestamp>.<raw bytes>, enforce ±300 seconds, and try every offered signature. v1=<base64> is HMAC-SHA256; v2=<key-id>:<base64> is RSA SHA256/PKCS#1 v1.5. At most two signatures appear during rotation or scheme migration. The RSA key id selects a candidate public key; it is never proof. Deduplicate by event id.
Return 2xx after durably recording the receipt even when verification fails, then investigate it; a 4xx is treated as permanent. Delivery is at least once with bounded retries, not an ordered exactly-once stream.
P6 separates production from consumption. The Partner API and every Academy mode (WEB, CRON, and ALL) are producer-only. A simultaneous PARTNER_ROLE=worker process is the sole owner of the partner-webhook queue processor and 15-second recovery sweep; it re-enqueues recovered rows through the same producer. The worker exposes only internal GET /health, never /v1 or Academy management routes. API and worker use the same Redis endpoint and BullMQ's default bull prefix. See Reconciliation for the permanent capture limitation that worker isolation does not close.