| Event | Fires when |
|---|---|
passport.sealed | A Proof Passport seal completes |
room.order_attached | An order is attached to one of your Closing Rooms |
room.passport_sealed | A Closing Passport version is sealed for one of your rooms |
SignSealShip-Signature HMAC header, and a SignSealShip-Event header naming the event.
Two ways to register
- Partner API key —
POST /api/passport/webhooksregisters apassport.sealed-only endpoint. Subscriptions created this way have no topic list and never receive room events. - Partner dashboard session — the
/api/partner/webhooksendpoints below let you choose topics, including the room events. They are authenticated by your dashboard login session, not the API key.
Dashboard session authentication
The/api/partner/webhooks routes are authenticated by the SignSealShip login session cookie, and your signed-in user must be linked to your partner account. Linking happens once, by proving possession of a valid partner API key — sign in at https://signsealship.com/partner and paste your key (the underlying call is POST /api/partner/link with {"apiKey": "..."}). The first linked member of a partner becomes owner; later ones member.
An unlinked session gets:
403 Forbidden
partner-portal rate limit of 120 requests per minute per session.
List subscriptions
GET /api/partner/webhooks
Returns a JSON array of your partner’s subscriptions, newest first. Legacy subscriptions created with the API-key route report "topics": ["passport.sealed"].
curl
200 OK
Create a subscription
POST /api/partner/webhooks
Your webhook endpoint. Must be an absolute
https:// URL.One or more of
passport.sealed, room.order_attached,
room.passport_sealed. Deliveries go only to subscriptions whose topics
include the event.curl
Subscription id (UUID) — use it to delete.
The signing secret, prefixed
sss_whsec_. Shown exactly once —
SignSealShip stores only its SHA-256 hash. Save it now.The registered endpoint.
The subscribed topics.
"SignSealShip-Signature" — the header carrying the signature on every
delivery.400 {"error": "A https:// webhook URL is required."}, 400 when topics is empty or contains anything outside the allowed set.
Delete a subscription
DELETE /api/partner/webhooks/{id}
The subscription id. You can only delete your own partner’s subscriptions.
curl
{"ok": true}, or 404 {"error": "No such webhook."}.
Event payloads
All payloads are PII-free and never carry another partner’s data. Optional fields are omitted from the JSON entirely (not sent asnull) when they don’t apply.
passport.sealed
id is the passport’s UUID.
passport.sealed
room.order_attached
id is deterministic per event, room, and subject — {event}:{roomCode}:{orderCode} — so you can deduplicate a redelivery. orderCode is the attached order’s public code; passportVerifyCode is omitted.
room.order_attached
room.passport_sealed
passportVerifyCode is the room’s latest sealed passport verify code (for /v/room/{passportVerifyCode}); orderCode is omitted.
room.passport_sealed
Verifying the signature
Every delivery carries two headers:Delivery headers
t=,v1= format, so you can reuse familiar verification code:
Derive the key
The HMAC key is
lowercase_hex(sha256(secret)) — the SHA-256 of your raw
signing secret, as a lowercase hex string, used as UTF-8 bytes.Compute the expected signature
HMAC-SHA256 over the string
"{t}.{rawBody}", where t is the value from
the header and rawBody is the exact request body bytes. Compare the
lowercase hex result to v1 with a constant-time comparison.Delivery semantics — honest version
Webhook delivery is best-effort and fire-and-forget. A slow or failing endpoint can never block, slow, or fail the seal or room operation that triggered it.- Outbound POSTs time out after 10 seconds; a non-2xx response is logged and not retried by the dispatcher.
passport.sealeddeliveries may be routed through an internal task queue when available, which can produce a redelivery — treat deliveries as at-least-once and deduplicate byid.- Room events are dispatched after the underlying write commits; if your endpoint is down, that notification is lost.
GET /api/rooms/{roomCode}, GET /api/verify/room/{verifyCode}, and GET /api/passport/{id} — whenever correctness matters.