SohoPay delivers webhooks as POST requests with a JSON body whenever a resource changes state. This page is the event catalog plus the delivery contract — payloads, signatures, retries, and deduplication. For registering endpoints and operational setup, see the webhooks guide.

Event envelope

Every delivery uses the same envelope; the event-specific object lives under data, and the globally unique event_id is your deduplication key (see below):

Event types by resource

Payment events

payment.settled

Settlement events

settlement.completed
merchant_amount always equals gross_amount — the 5% fee is charged to the operator’s credit line, never deducted from the merchant. See Fees.

Agent events

agent.aml_declined
Treat agent.aml_declined as terminal — subsequent payments from this agent return AML_DECLINED. See AML Screening.

Merchant events

merchant.kyb.approved

Verifying signatures

Every delivery includes Sohopay-Signature — a hex-encoded HMAC-SHA256 of ${timestamp}.${raw_body} using your endpoint’s signing secret — and Sohopay-Timestamp, the Unix seconds when it was signed. Verify before parsing: recompute the HMAC over the timestamp and the raw request body (never re-serialized JSON), compare in constant time, and reject deliveries older than 5 minutes to block replays.
Always compare with timingSafeEqual / hmac.compare_digest. A plain == comparison leaks timing information that lets an attacker forge signatures byte by byte.

Delivery, retries, and deduplication

Respond with any 2xx within 10 seconds to acknowledge a delivery. Anything else — a 4xx, 5xx, or timeout — triggers retries at 5s, 30s, 2m, 10m, 1h, then hourly up to 24 hours, after which the delivery is dropped. Retries and network races mean the same event can arrive more than once, so dedupe by event_id: record each processed ID (a unique-constrained column, or a Redis SETNX with a TTL beyond 24 hours) and acknowledge duplicates with a 200 without reprocessing. Ordering is not guaranteed either — a payment.settled can arrive before its payment.created — so key state transitions on resource status, not event arrival order. Acknowledge fast, process async: verify the signature, persist the event, return 200, then handle it from a queue. Slow handlers cause timeouts, which cause retries, which cause the duplicates you then have to dedupe.

Next steps

Webhooks Guide

Register endpoints, get signing secrets, and send test events.

Error Reference

The error codes behind payment.failed events.