> ## Documentation Index
> Fetch the complete documentation index at: https://api.tandapay.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Get payment and payout outcomes pushed to your server.

Collections and payouts finish asynchronously, after the customer approves a prompt or the bank settles. Webhooks tell you the moment that happens, so you don't need to poll.

## Set up

**1. Build an HTTPS endpoint**

A `POST` route on your server, for example `https://merchant.co.ke/webhooks/tanda`.

**2. Register it in the dashboard**

Go to **Webhooks** in the [dashboard](https://app.tandapay.io). Sandbox and production each have their own URL.

**3. Generate a webhook secret**

Under **Developers → API keys**. You'll use it to [verify every request](https://api.tandapay.io/guides/webhooks/verify-signatures.md).

**4. Test it**

Create a sandbox transaction and call [Simulate webhook](https://api.tandapay.io/api-reference/endpoint/simulate-webhook.md) to send yourself an event.

## Delivery

| | |
| :- | :- |
| Timeout | Respond within **15 seconds** or the attempt counts as failed. |
| Retries | Up to **3** retries with backoff: after 5s, 15s and 45s. |
| Success | Any `2xx` response. Anything else triggers a retry. |
| Duplicates | Possible. The same event can arrive more than once. |
| Order | Not guaranteed. A `successful` update can arrive before `transaction_created`. |

> [!WARNING]
>
> Retries stop after the third retry. If your server was down for longer, find the missed events with [List webhook deliveries](https://api.tandapay.io/api-reference/endpoint/list-webhook-deliveries.md) and replay them with [Resend webhook](https://api.tandapay.io/api-reference/endpoint/resend-webhook.md), or check the transactions directly.

## Write a reliable handler

Verify, queue, acknowledge. Do the slow work in the background so you always answer within the timeout.

```javascript Node.js (Express) theme={null}
import express from "express";
import crypto from "node:crypto";

const app = express();

function isValidSignature(req) {
  const received = Buffer.from(req.get("X-Webhook-Signature") ?? "");
  const expected = Buffer.from(process.env.TANDA_WEBHOOK_SECRET);
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

app.post("/webhooks/tanda", express.json(), async (req, res) => {
  if (!isValidSignature(req)) return res.sendStatus(401);
  await queue.add("tanda-event", req.body); // process later
  res.sendStatus(200);
});
```

When you process an event:

* **Key on `transactionId`** (or your `externalReference`) so a duplicate does nothing.
* **Never move backwards.** Ignore a `pending` that arrives after `successful` or `failed`.
* **Acknowledge events you don't use.** Return `2xx` for unknown event types so they aren't retried.
* **Double-check before irreversible actions.** For high-value orders, confirm with [Get transaction](https://api.tandapay.io/api-reference/endpoint/get-transaction.md) before you ship.

## Inspect and replay

| To | Use |
| :- | :- |
| Find deliveries, including failures | [List webhook deliveries](https://api.tandapay.io/api-reference/endpoint/list-webhook-deliveries.md) |
| See one delivery and your server's response | [Get webhook delivery](https://api.tandapay.io/api-reference/endpoint/get-webhook-delivery.md) |
| Send a delivery again | [Resend webhook](https://api.tandapay.io/api-reference/endpoint/resend-webhook.md) |
| Fire a test event (sandbox) | [Simulate webhook](https://api.tandapay.io/api-reference/endpoint/simulate-webhook.md) |

> **[Verify webhooks](https://api.tandapay.io/guides/webhooks/verify-signatures.md)**
>
> Reject requests that didn't come from Tanda.

> **[Events and payloads](https://api.tandapay.io/guides/webhooks/events.md)**
>
> Every event, field and status.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.