Delivery
How a webhook is delivered, what counts as a successful delivery, and how to recover events your endpoint missed.
Tonder sends each event to your endpoint once. Your endpoint's response is recorded, but it does not trigger a redelivery — so your integration needs its own way to recover an event it rejected or missed.
How delivery works
A transaction status change or event occurs.
Tonder sends a POST request to your endpoint.
Your endpoint's response is recorded as a successful or a failed delivery.
Success and failure criteria
A delivery is successful if your endpoint responds with a 2xx code within 30 seconds.
| Response | What we do |
|---|---|
| 2xx within 30 seconds | Delivery recorded as successful. |
| 4xx (400, 401, 404, etc.) | Delivery recorded as failed. An internal alert is raised. The event is not resent. |
| 5xx (500, 502, 503, etc.) | Delivery recorded as failed. An internal alert is raised. The event is not resent. |
| Timeout (no response after 30 seconds) | Delivery recorded as failed. An internal alert is raised. The event is not resent. |
What this means for your integration
An event your endpoint rejects or misses is not redelivered. Build for that:
- Answer
2xximmediately, before running your business logic, so that an internal error on your side does not turn a delivered event into a failed one. - Reconcile by polling
GET /api/v1/transactions/{id}/for any period during which your endpoint was unavailable or returning errors. - If you detect lost events, contact support with the date range.
Do not treat the webhook as your only source of truth. A payment or withdrawal whose notification you never received still reached its final status — the transaction endpoint always has it.
Implementation considerations
- Use
metadata.external_idto deduplicate and avoid processing the same event twice. - Keep detailed logs of every webhook for debugging.
- Ensure a malformed payload or a processing error doesn't crash your system.
- Dedupe on
metadata.external_id(works across Short + Long formats). - Keep an
idempotency_keyindex in your DB. - Treat duplicate Pending + Success in Hybrid mode as intentional.
- Always verify final status via
GET /api/v1/transactions/{id}/. - Handle
Declinedfor withdrawals as a terminal state.
