Skip to main content
SignSealShip delivers signed webhook events for seven topics: The four order topics deliver only for orders your partner created through the Order API — orders placed elsewhere never reach your endpoint. Every delivery is an HTTPS POST with a JSON body, a SignSealShip-Signature HMAC header, and a SignSealShip-Event header naming the event.

Two ways to register

  • Passport-only shortcutPOST /api/passport/webhooks registers a passport.sealed-only endpoint. Subscriptions created this way have no topic list and never receive room or order events.
  • Topic-aware management — the /api/partner/webhooks endpoints below let you choose topics, including the room and order events.

Authentication

The /api/partner/webhooks management routes accept either credential:
  • Partner API keyAuthorization: Bearer sss_pk_..., for server-to-server management: your own provisioning code, a Zapier zap, or any integration that never sees a browser.
  • Dashboard session — the SignSealShip login session cookie, where 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.
A missing or bad key gets the standard 401; an unlinked session gets:
403 Forbidden
These routes share the partner-portal rate limit of 120 requests per minute.

List subscriptions

GET /api/partner/webhooks Returns a JSON array of your partner’s subscriptions, newest first. Legacy subscriptions created with the passport-only route report "topics": ["passport.sealed"].
curl
200 OK

Create a subscription

POST /api/partner/webhooks
string
required
Your webhook endpoint. Must be an absolute https:// URL.
string[]
required
One or more of passport.sealed, room.order_attached, room.passport_sealed, order.created, payment.cleared, signature.completed, shipment.delivered. Deliveries go only to subscriptions whose topics include the event; the order topics fire only for Order API orders.
curl
string
Subscription id (UUID) — use it to delete.
string
The signing secret, prefixed sss_whsec_. Shown exactly once — SignSealShip stores only its SHA-256 hash. Save it now.
string
The registered endpoint.
string[]
The subscribed topics.
string
"SignSealShip-Signature" — the header carrying the signature on every delivery.
Errors: 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}
string
required
The subscription id. You can only delete your own partner’s subscriptions.
curl
Returns {"ok": true}, or 404 {"error": "No such webhook."}.

Rotate a signing secret

POST /api/partner/webhooks/{id}/rotate Reissues the subscription’s signing secret without recreating the subscription.
string
required
The subscription id.
curl
string
The new signing secret, prefixed sss_whsec_. Shown exactly once — only its hash is stored. Save it now.
For 24 hours after rotation, deliveries are signed with both the old and the new secret — the SignSealShip-Signature header carries a v1 entry for each. Accept a delivery when any v1 entry matches (the samples below check a single v1; during a rotation window, test each entry) and you ride through the cutover without dropping a delivery. After the overlap the old secret stops signing.

List delivery attempts

GET /api/partner/webhooks/{id}/deliveries A metadata-only feed of the subscription’s recent delivery attempts, newest first — what was attempted, when, and how your endpoint answered. Event payload bodies are never stored or returned; use the feed to confirm your endpoint is receiving and answering deliveries, not to replay them.
string
required
The subscription id.
curl

Event payloads

All payloads are PII-free and never carry another partner’s data. Optional fields are omitted from the JSON entirely (not sent as null) when they don’t apply.

passport.sealed

id is the passport’s UUID.
passport.sealed
environment is live or test — the environment of the API key that sealed the passport. Test-keyed passports verify publicly with the distinct verdict verified_test, so sandbox evidence is never mistaken for production evidence.

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

Order events

order.created, payment.cleared, signature.completed, and shipment.delivered share one body shape. They deliver only for orders your partner created through the Order API, signed identically to every other delivery, with the SignSealShip-Event header carrying the topic. id is deterministic — {topic}:{orderCode} — so a redelivery deduplicates cleanly. externalReference echoes the external_reference you sent at order creation and is omitted when you sent none. status is the order’s status name at dispatch time.
payment.cleared

Verifying the signature

Every delivery carries two headers:
Delivery headers
The scheme mirrors Stripe’s t=,v1= format, so you can reuse familiar verification code:
1

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.
2

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.
3

Reject stale timestamps

Reject deliveries whose t is more than 5 minutes from now to blunt replay.
Verify against the raw request body bytes, before any JSON parsing or re-serialization — a re-serialized body will not match the signature.

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.sealed deliveries may be routed through an internal task queue when available, which can produce a redelivery — treat deliveries as at-least-once and deduplicate by id.
  • Room events are dispatched after the underlying write commits; if your endpoint is down, that notification is lost.
Design accordingly: treat webhooks as a nudge, not a ledger. Reconcile state with the read endpoints — GET /api/partner/orders/{code}, GET /api/rooms/{roomCode}, GET /api/verify/room/{verifyCode}, and GET /api/passport/{id} — whenever correctness matters. The delivery attempts feed tells you whether your endpoint has been answering.