# Webhooks

Securely handle Flutterwave events on your webhook server.

> 🚧 Getting Started
>
> Read these sections before implementing your webhook: [Structure of a Webhook Payload](#structure-of-a-webhook-payload), [Implementing a Webhook](#implementing-a-webhook), and [Best Practices](#best-practices-for-implementing-webhooks).

Webhooks are an important part of your payment integration. They allow Flutterwave to notify you about events that happen on your account, like a successful payment or a failed transaction.

A `webhook URL` is an endpoint on your server where you can receive notifications about such events. When an event occurs, we'll make a POST request to that endpoint, with a JSON body containing the details about the event, including the type of event and the data associated with it.

<br />

# When to Use Webhooks

Webhooks are supported for all kinds of payment methods, but they're especially useful for methods and events that happen outside your application's control, such as:

* Getting paid via mobile money or USSD
* A subscription charge in recurring payments.
* A pending payment updates to successful.

<br />

These are all asynchronous actions, as they are not controlled by your application, so you won't know when they are completed unless we notify you or you check later.

Setting up a webhook lets us notify you when these payments are completed. Within your webhook endpoint, you can then:

* Update customer membership records in your database when a subscription payment succeeds.

* Email a customer when a subscription payment fails.

* Update your order records when the status of a pending payment is updated to successful.

<br />

# Enabling Webhooks

Here is how to set up a webhook on your Flutterwave account:

1. Log in to your [dashboard](https://dashboard.flutterwave.com/login) and click on **Settings**.
2. Navigate to **Webhooks** to add your webhook URL.
3. Check all the boxes and save your Settings.

> 📘 Tip
>
> When testing, you can get an instant webhook URL by visiting [webhook.site](http://webhook.site/). This will allow you to inspect the received payload without having to write any code or set up a server.

<br />

# Structure of a Webhook Payload

All  webhook payloads follow the same basic structure:

* A `data` object, The content of this object will vary depending on the event, but typically it will contain details of the event, including:
  * an `id` containing the ID of the transaction, e.g. `chg_Hq4oBRTJ4r`.
  * a `status` describing the status of the transaction, e.g. `succeeded`.
  * payment or customer details, if applicable.
* A `type` field describing the type of event, e.g `charge.completed`.
* An `id`field containing the webhook ID, e.g. `wbk_W5p6ktwU0jQ8RO4By860`.
* A `timestamp`field e.g. `1735116884019`.

Here is a sample webhook payload for payment:

```json Successful Payment
{
  "data": {
    "amount": 2500,
    "created_datetime": 1735116842.116,
    "currency": "KES",
    "customer": {
      "address": null,
      "created_datetime": 1732977179.938,
      "email": "olaobajua@gmail.com",
      "id": "cus_csm0pcQim4",
      "meta": {},
      "name": null,
      "phone": null
    },
    "description": null,
    "id": "chg_Hq4oBRTJ4r",
    "meta": {},
    "payment_method": {
      "client_ip": null,
      "created_datetime": 1735116842.107,
      "customer_id": "cus_csm0pcQim4",
      "device_fingerprint": null,
      "id": "pmd_sG5zwZBN4L",
      "meta": {},
      "mobile_money": {
        "country_code": "234",
        "network": "MTN",
        "phone_number": "9067985861"
      },
      "type": "mobile_money"
    },
    "processor_response": {
      "code": "00",
      "type": "approved"
    },
    "redirect_url": "https://custom-redirect.com",
    "reference": "49c3c6f5-aedd-4443-9eb4-92c51758f04a",
    "status": "succeeded"
  },
  "id": "wbk_W5p6ktwU0jQ8RO4By860",
  "timestamp": 1735116884019,
  "type": "charge.completed"
}

```

<br />

# Implementing a Webhook

Creating a webhook endpoint on your server is the same as writing any other API endpoint, but there are a few important details to note:

* Verifying Webhook Signature
* Responding to Webhook Requests
* Webhook Timeout

## Verifying Webhook Signatures

When enabling webhooks, you must set a *secret hash*. Since webhook URLs are publicly accessible, this hash allows you to verify that incoming requests are from Flutterwave. Choose a random, secure value as your secret hash and store it as an environment variable on your server.

When a webhook is triggered, Flutterwave uses the secret hash you specify on your dashboard to hash the webhook response using `HMAC-SHA256`. The encrypted value is then returned as `flutterwave-signature` in the header of the webhook.

```node
//Sample hashing
const crypto = require('crypto');

function isValidFlutterwaveWebhook(rawBody, signature, secretHash) {
  const hash = crypto
    .createHmac('sha256', secretHash)
    .update(rawBody)
    .digest('base64');

  return hash === signature;
}

```

You should compute the hashed webhook on your webhook server and compare the result with the `flutterwave-signature` in the header. If both values don't match, you can discard the request as it isn't from Flutterwave.

```node
// complete hashing and header lookup
const express = require('express');
const bodyParser = require('body-parser');

const app = express();

// Capture raw body
app.use(bodyParser.json({
  verify: (req, res, buf) => {
    req.rawBody = buf.toString();
  }
}));

// Webhook Route
app.post('/webhook', (req, res) => {
  const flutterwaveSignature = req.headers['flutterwave-signature'];
  const isValid = isValidFlutterwaveWebhook(req.rawBody, flutterwaveSignature, process.env.FLW_SECRET_HASH);

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  // Process webhook
  console.log(req.body);
  res.sendStatus(200);
});

```

<br />

<br />

## Responding to Webhook Requests

To acknowledge receipt of a webhook, your endpoint **must** return a `200` HTTP status code. Any other response codes, including `3xx` codes will be treated as a failure. **We don't care about the response body or headers.**.

<br />

## Webhook Timeout

Webhook requests time out after **60 seconds**. Your webhook endpoint must respond within this window. If it doesn't, the request will be marked as failed. If webhook retries are enabled, we’ll attempt to resend the request.

> ℹ️ Handling Webhook Retries
>
> Be sure to enable webhook retries on your dashboard. If we don't get a `200` status code (for example, if your server is unreachable), we'll retry the webhook call **3 times**, with a **30-minute** interval between each attempt.

<br />

# Examples

Here are a few examples of implementing a webhook endpoint in some web frameworks:

> 🚧 Rails and Django
>
> Web frameworks like Rails or Django check POST requests for `CSRF` tokens, a security measure against cross-site request forgery. Exclude webhook endpoints from `CSRF` protection.

```javascript Node.js
// In an Express-like app:

app.post("/flw-webhook", (req, res) => {
    // If you specified a secret hash, check for the signature
    const secretHash = process.env.FLW_SECRET_HASH;
    const signature = req.headers["flutterwave-signature"];
    if (!signature || (signature !== secretHash)) {
        // This request isn't from Flutterwave; discard
        res.status(401).end();
    }
    const payload = req.body;
    // It's a good idea to log all received events.
    log(payload);
    // Do something (that doesn't take too long) with the payload
    res.status(200).end()
});
```
```php PHP
// In a Laravel-like app:

Route::post('/flw-webhook', function (\Illuminate\Http\Request $request) {
    // If you specified a secret hash, check for the signature
    $secretHash = config('services.flutterwave.secret_hash');
    $signature = $request->header('flutterwave-signature');
    if (!$signature || ($signature !== $secretHash)) {
        // This request isn't from Flutterwave; discard
        abort(401);
    }
    $payload = $request->all();
    // It's a good idea to log all received events.
    Log::info($payload);
    // Do something (that doesn't take too long) with the payload
    return response(200);
});
```
```python Python
# In a Django-like app:
import os

@require_POST
@csrf_exempt
def webhook(request):
    secret_hash = os.getenv("FLW_SECRET_HASH")
    signature = request.headers.get("flutterwave-signature")
    if signature == None or (signature != secret_hash):
        # This request isn't from Flutterwave; discard
        return HttpResponse(status=401)
    payload = request.body
    # It's a good idea to log all received events.
    log(payload)
    # Do something (that doesn't take too long) with the payload
    return HttpResponse(status=200)
```

<br />

# Best Practices for Implementing Webhooks

## Always Verify Critical Transaction Data

Before giving value to a customer based on a webhook notification, always re-query our API to verify the transaction details. This helps confirm that the data returned is consistent with what you’re expecting and has not been compromised.

For example, when you receive a successful payment notification, call the [transaction verification endpoint](/reference/charges_get) to confirm that the `status`, `amount`, `currency`, and `tx_ref` match the expected value in your system before confirming the customer's order.

```javascript
const payload = req.body;
const response = await flw.Transaction.verify({id: payload.id});
if (
    response.data.status === "successful"
    && response.data.amount === expectedAmount
    && response.data.currency === expectedCurrency
    && response.data.tx_ref === expectedReference ) {
    // Success! Confirm the customer's payment
} else {
    // Inform the customer their payment was unsuccessful
}
```

<br />

## Don't Rely Solely on Webhooks

Have a backup strategy in place, in case your webhook endpoint fails. For instance, if your webhook endpoint is throwing server errors, you won't know about any new customer payments because webhook requests will fail.

To get around this, we recommend setting up a background job that polls for the status of any pending transactions at regular intervals (for instance, every hour) using the [transaction verification endpoint](/reference/charges_get), till a successful or failed response is returned.

<br />

## Use a Signature

Remember, your webhook URL is public, and anyone can send a fake payload. We recommend using a [signature](#verifying-webhook-signatures) so you can be sure the requests you get are from Flutterwave.

<br />

## Respond Quickly

Your webhook endpoint needs to respond within a certain time limit, or we'll consider it a failure and try again. Avoid doing long-running tasks or network calls in your webhook endpoint so you don't hit the timeout.

If your framework supports it, you can have your webhook endpoint immediately return a `200` status code, and then perform the rest of its duties; otherwise, you should dispatch any long-running tasks to a job queue, and then respond.

<br />

## Be Idempotent

Occasionally, we might send the same webhook event more than once. You should make your event processing *idempotent* (calling the webhook multiple times will have the same effect), so you don't end up giving a customer value multiple times.

One way of doing this is recording the events you've processed, and then checking if the status has changed before processing the duplicate event:

```javascript
const payload = req.body;
const existingEvent = await PaymentEvent.where({id: payload.id}).find();
if (existingEvent.status === payload.status) {
    // The status hasn't changed,
    // so it's probably just a duplicate event
    // and we can discard it
    res.status(200).end();
}

// Record this event
await PaymentEvent.save(payload);
// Process event...
```