Register an endpoint
Register a publicly reachable HTTPS URL and the events you want:Response
Event catalog
Events cover payments (payment.created, payment.settled, payment.failed), settlements (settlement.initiated/completed/failed, merchant.settlement.completed), agents (agent.created, agent.key_rotated, agent.aml_review, agent.aml_declined), and KYB (merchant.kyb.approved, merchant.kyb.rejected). Full payload schemas for every event are in the webhook events reference.
Every delivery has the same envelope: an event_id (evt_ prefix), type, created_at, and a data object containing the resource.
Verify signatures
Every delivery carries two headers:Sohopay-Signature— hex HMAC-SHA256 of${timestamp}.${body}using your webhook secretSohopay-Timestamp— Unix seconds when the delivery was signed
Delivery, retries, and idempotency
A delivery is considered successful only on a 2xx response within 10 seconds. On anything else, SohoPay retries at 5s, 30s, 2m, 10m, 1h, then hourly up to 24 hours, after which the delivery is dropped (the event remains queryable via the API). Design your handler accordingly:- Respond 2xx fast, process async. Verify the signature, enqueue, return. Slow handlers time out and cause duplicate deliveries.
- Dedupe by
event_id. Retries reuse the sameevent_id. Keep a processed-IDs table (24h retention is enough) and skip repeats. - Don’t assume ordering.
payment.settledcan arrive beforepayment.createdunder retry. Treat each event as a snapshot, not a diff.
Test locally
1
Point a webhook at webhook.site
Create a URL at webhook.site and register it as a sandbox webhook. You’ll see raw deliveries — headers, body, and signature — without writing any code.
2
Send a test event
Trigger a synthetic delivery of any event type to your endpoint:
3
Verify against your real handler
Replay the captured body and headers against your local handler, then re-register the webhook with your real (tunneled or deployed) URL and settle a sandbox payment end to end.
Test it
- Registered a sandbox webhook and stored the
whsec_secret -
POST /webhooks/{id}/testdelivery passes your signature verification - A tampered body or wrong secret is rejected with
401 - A delivery with a 10-minute-old timestamp is rejected with
400 - Replaying the same delivery twice processes the event only once (dedupe by
event_id)
Next steps
Webhook Events Reference
Full payload JSON for every event type.
Settlements & Payouts
The settlement events you’ll consume most.
Testing Guide
The full sandbox-to-mainnet readiness checklist.

