The canonical integration contract (statuses, commission, signature
verification,
--no-verify-jwt, local vs cloud Inngest) lives in the
Integration Guide — the source of truth. Webhook
not firing in local dev, reconciling on net_amount, USER_CANCELLED
being terminal… the Integration FAQ covers the most
common pitfalls.Events
| Event | When |
|---|---|
payment.completed | The transaction has reached its final status (success or failure). |
Only one event is currently emitted:
payment.completed. The final status (SUCCESS, PIN_INVALID, TIMEOUT, …) is carried by the status field in the payload, not by the event name.The
@sandpay/node SDK exposes webhooks.parseEvent(body) which returns a typed WebhookPayload after verification. See Node SDK.Configuration
From/webhooks in the dashboard:
- Add your endpoint’s HTTPS URL.
- SandPay generates a signing secret (
whsec_...) — copy it, it will not be shown again. - Enable the endpoint. You can pause it at any time.
Payload format
The raw field
The raw field contains the native operator response, synthesised to reproduce the real shape (_simulated: true is set at the top level so you can detect sandbox payloads).
This design lets you write your integration code (e.g. payload.raw.financialTransactionId for MTN, payload.raw.responseCode for Moov) directly against the sandbox.
The
raw field is additive: if you don’t read it, your webhook handler continues to work as before. Older records (created before the raw passthrough was released) return raw: null.Signature verification
Every delivery is signed. The headers are:JSON.parse).
With the Node SDK
Manual verification
Retry policy
SandPay attempts delivery up to 5 times with exponential backoff managed by Inngest. Each attempt has a 10-second timeout. If the 5th attempt fails:- The delivery is marked
failedin/webhooks/deliveries. - The organisation owner receives an alert email.
- You can manually replay the delivery from the UI.
2xx code is considered a success. 3xx redirects are not followed automatically — make sure to return a 200.