> ## Documentation Index
> Fetch the complete documentation index at: https://docs.splashpay.co.tz/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive real-time notifications whenever the status of a payment changes.

# Webhooks

SplashPay uses webhooks to notify your application whenever a payment reaches a final state.

Instead of continuously polling the Payment Status endpoint, configure a webhook endpoint to receive instant updates.

<Card title="Real-time Notifications" icon="webhook">
  SplashPay automatically sends an HTTP **POST** request to your webhook URL whenever a payment status changes.
</Card>

***

## Configure Your Webhook URL

You can configure your webhook URL from the SplashPay Merchant Dashboard or via the Merchant API.

Example:

```text theme={null}
https://merchant.example.com/webhooks/splashpay
```

Your endpoint must:

* Support HTTPS
* Accept HTTP POST requests
* Return **HTTP 200 OK** after successful processing
* Be publicly accessible

***

## Webhook Events

SplashPay currently sends the following events.

| Event               | Description                                        |
| ------------------- | -------------------------------------------------- |
| `payment.success`   | Payment completed successfully.                    |
| `payment.failed`    | Payment failed.                                    |
| `payment.cancelled` | Payment was cancelled by the customer or provider. |
| `payment.expired`   | Payment expired before completion.                 |

***

# Sample Webhook

SplashPay sends webhook notifications using the following JSON structure.

```json theme={null}
{
  "event": "payment.success",
  "created_at": "2026-06-24T09:59:24.430757Z",
  "data": {
    "reference": "INV-xcxoddfudjhg",
    "amount": "1000.00",
    "fee": "15.00",
    "net_amount": "985.00",
    "currency": "TZS",
    "status": "success",
    "customer_name": "John Doe",
    "customer_email": "john.doe@example.com",
    "customer_phone": "255744123456",
    "payment_method": "mobile_money",
    "provider": "selcom",
    "channel": "TIGOPESATZ",
    "provider_reference": "1769142083",
    "metadata": null
  }
}
```

***

# Webhook Payload

| Field                     | Type        | Description                     |
| ------------------------- | ----------- | ------------------------------- |
| `event`                   | string      | Event name.                     |
| `created_at`              | datetime    | Time the webhook was generated. |
| `data.reference`          | string      | Merchant payment reference.     |
| `data.amount`             | decimal     | Payment amount.                 |
| `data.fee`                | decimal     | Processing fee charged.         |
| `data.net_amount`         | decimal     | Amount settled after fees.      |
| `data.currency`           | string      | Transaction currency.           |
| `data.status`             | string      | Payment status.                 |
| `data.customer_name`      | string      | Customer full name.             |
| `data.customer_email`     | string      | Customer email.                 |
| `data.customer_phone`     | string      | Customer phone number.          |
| `data.payment_method`     | string      | Payment method used.            |
| `data.provider`           | string      | Payment provider.               |
| `data.channel`            | string      | Provider channel or network.    |
| `data.provider_reference` | string      | Provider transaction reference. |
| `data.metadata`           | object/null | Merchant-defined metadata.      |

***

# Verify Webhook Signature

Every webhook request includes a signature that allows you to verify the payload originated from SplashPay.

| Header                  | Description                                   |
| ----------------------- | --------------------------------------------- |
| `X-SPLASHPAY-SIGNATURE` | HMAC SHA-256 signature of the payload         |
| `X-SPLASHPAY-TIMESTAMP` | Unix timestamp used to generate the signature |

Compute the HMAC using your **Webhook Secret**.

```text theme={null}
HMAC_SHA256(
    timestamp + "." + request_body,
    WEBHOOK_SECRET
)
```

If the computed signature matches the `X-SPLASHPAY-SIGNATURE` header, the request is authentic.

***

# Example Verification (PHP)

```php theme={null}
$timestamp = $_SERVER['HTTP_X_SPLASHPAY_TIMESTAMP'];
$signature = $_SERVER['HTTP_X_SPLASHPAY_SIGNATURE'];

$payload = file_get_contents('php://input');

$expected = hash_hmac(
    'sha256',
    $timestamp.'.'.$payload,
    env('SPLASHPAY_WEBHOOK_SECRET')
);

if (!hash_equals($expected, $signature)) {
    abort(401, 'Invalid Signature');
}
```

***

# Example Verification (Node.js)

```javascript theme={null}
import crypto from "crypto";

const payload = JSON.stringify(req.body);

const expected = crypto
    .createHmac(
        "sha256",
        process.env.SPLASHPAY_WEBHOOK_SECRET
    )
    .update(
        `${req.headers["x-splashpay-timestamp"]}.${payload}`
    )
    .digest("hex");

if (
    expected !==
    req.headers["x-splashpay-signature"]
) {
    return res.status(401).send("Invalid signature");
}
```

***

# Return HTTP 200

After successfully processing the webhook, return:

```http theme={null}
HTTP/1.1 200 OK
```

Example

```json theme={null}
{
    "success": true
}
```

***

# Retry Policy

If SplashPay does not receive a successful response (`HTTP 200`), the webhook will be retried automatically.

| Attempt | Delay       |
| ------- | ----------- |
| 1       | Immediately |
| 2       | 1 minute    |
| 3       | 5 minutes   |
| 4       | 15 minutes  |
| 5       | 30 minutes  |
| 6       | 1 hour      |
| 7       | 6 hours     |
| 8       | 24 hours    |

After the final retry, the webhook is marked as failed.

***

# Best Practices

<Steps>
  <Step title="Always verify signatures">
    Reject webhook requests with invalid signatures.
  </Step>

  <Step title="Process asynchronously">
    Respond with HTTP 200 immediately, then process the webhook in the background.
  </Step>

  <Step title="Use idempotent processing">
    The same webhook may be delivered more than once. Use the payment `reference` to avoid duplicate processing.
  </Step>

  <Step title="Store provider references">
    Persist both the merchant reference and provider reference for reconciliation.
  </Step>

  <Step title="Do not trust browser redirects">
    Always rely on webhooks to determine the final payment status.
  </Step>
</Steps>

***

# Event Lifecycle

```text theme={null}
Payment Created
      │
      ▼
Customer Pays
      │
      ▼
SplashPay
      │
      ▼
Webhook Sent
      │
      ▼
Merchant Server
      │
      ▼
HTTP 200 OK
```

***

# Testing Webhooks

During development you can expose your local server using tools such as:

* ngrok
* Cloudflare Tunnel
* LocalTunnel

Example:

```text theme={null}
https://abc123.ngrok.io/webhooks/splashpay
```

***

# Supported Payment Methods

Webhooks are sent for all SplashPay collection methods:

* Mobile Money
* Card Payments
* Dynamic TANQR

***

# Next Steps

<CardGroup cols={2}>
  <Card title="Payment Status" href="/collections/status" icon="circle-check">
    Retrieve the latest status of any payment.
  </Card>

  <Card title="Collections Overview" href="/collections/overview" icon="book-open">
    Learn more about SplashPay Collections.
  </Card>
</CardGroup>
