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

# Webhooks

> Get notified automatically when a payment is completed.

A **webhook** is a message Afara sends to your website or server when something happens, such as a customer completing a payment. You do not need to keep checking. This is the most reliable way to know a payment has gone through, because it works even if the customer closes their browser early.

> [!TIP]
>
> If you only take a few orders and check them by hand in the dashboard, you can skip webhooks. If you have an online shop that should update orders automatically, set one up.

## Set up your webhook address

**1. Prepare an address on your server**

You need a web address that starts with `https://` and can receive messages, for example `https://yourshop.com/afara-webhook`. Ask your developer or website provider if you are not sure.

**2. Add it in the dashboard**

In the merchant portal, click the **gear icon**, open **API Keys**, and scroll to **Webhooks & Callback**. Enter the address in **Test Webhook URL** for testing, or **Webhook URL** for real payments.

**3. Save**

Click **Update**. Then take a test payment and check that your server receives the message.

> [!NOTE]
>
> Test and live payments use separate addresses, so you can safely try things out without affecting your real shop.

## What Afara sends

When a payment succeeds, Afara sends a `POST` request to your address with a JSON body that describes the event:

```json theme={null}
{
  "event": "transaction.success",
  "data": {
    "transactionReference": "WEBTRF-ABC1234567",
    "status": "SUCCESS",
    "amount": 1000.00,
    "currency": "NGN"
  }
}
```

> [!NOTE]
>
> The sample above shows the main fields. The full payload also includes channel, fees, customer and timestamps, and we are confirming the exact field list against the live system.

## Check that the message really came from Afara

Every webhook includes a header called `X-Webhook-Signature`. It looks like this:

```text theme={null}
X-Webhook-Signature: sha256=f8a3b2c1d4e5...
```

To confirm the message is genuine and has not been changed on the way, your server calculates the same signature and compares it.

**1. Get your secret key**

Copy your **secret key** (it starts with `TSK_` for test or `SK_` for live) from **Settings > API Keys**. Keep it on your server only.

**2. Read the raw body**

Use the request body exactly as it arrived. Do not re-format it first.

**3. Calculate the signature**

Create an **HMAC-SHA256** hash of the raw body using your secret key.

**4. Compare**

The result, written in lowercase hexadecimal and prefixed with `sha256=`, must match the header. If it does not, reject the request.

```javascript Node.js theme={null}
const crypto = require("crypto");

function isValidWebhook(rawBody, signatureHeader, secretKey) {
  const expected =
    "sha256=" + crypto.createHmac("sha256", secretKey).update(rawBody).digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

```php PHP theme={null}
<?php
$rawBody   = file_get_contents("php://input");
$signature = $_SERVER["HTTP_X_WEBHOOK_SIGNATURE"] ?? "";
$expected  = "sha256=" . hash_hmac("sha256", $rawBody, "YOUR_SECRET_KEY");

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit;
}

// The message is genuine. Mark the order as paid here.
http_response_code(200);
```

## Good habits

* **Reply quickly** with a success response (HTTP 200) and do slow work afterwards.
* **Expect repeats.** Make sure handling the same payment twice does not deliver the order twice. Use the transaction reference to spot duplicates.
* **Check the amount and reference** against your own order before you mark it as paid.


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