Webhooks allow you to set up a notification system that can be used to receive updates on certain requests made to the Waza API.


Usually, when you make a request to an API endpoint, you expect to get a near-immediate response. However, some requests may take a long time to process, which can lead to timeout errors. In order to prevent a timeout error, a pending response is returned. Since your records need to be updated with the final state of the request, you need to either:

  1. Make a request for an update (popularly known as polling) or,
  2. Listen to events by using a webhook URL.

You can configure webhook endpoints on our Dashboard to be notified about events from Waza. Each organisation can add a webhook endpoint for receiving payment events sent by Waza.

All webhook endpoints must be configured with https URLs that accept POST requests with JSON payloads.

Verify event origin

Since your webhook URL is publicly available, you need to verify that events originate from Waza and not a bad actor. Use signature validation to ensure events to your webhook URL are from Waza:

Signature validation

Events sent from Waza carry the x-waza-signature header. The value of this header is a HMAC SHA512 signature of the event payload signed using the secret you generated from your Waza dashboard. Verifying the header signature should be done before processing the event:

var crypto = require('crypto');
var secret = process.env.SECRET_KEY;
// Using Express
app.post("/my/webhook/url", function(req, res) {
    //validate event
    const hash = crypto.createHmac('sha512', secret).update(JSON.stringify(req.body)).digest('hex');
    if (hash == req.headers['x-waza-signature']) {
    // Retrieve the request's body
    const event = req.body;
    // Do something with event  
// only a post with paystack signature header gets our attention
if ((strtoupper($_SERVER['REQUEST_METHOD']) != 'POST' ) || !array_key_exists('x-waza-signature', $_SERVER) ) 

// Retrieve the request's body
$input = @file_get_contents("php://input");

// validate event do all at once to avoid timing attack
if($_SERVER['HTTP_X_WAZA_SIGNATURE'] !== hash_hmac('sha512', $input, WAZA_SECRET))


// parse event (which is json string) as object
// Do something - that will not take long - with $event
$event = json_decode($input);


Go live checklist

Now that you’ve successfully created your webhook URL, here are some ways to ensure you get a delightful experience:

  1. Add the webhook URL on your Waza dashboard
  2. Ensure your webhook URL is publicly available (localhost URLs cannot receive events)
  3. If using .htaccess kindly remember to add the trailing / to the URL
  4. Test your webhook to ensure you’re getting the JSON body and returning a 200 OK HTTP response
  5. If your webhook function has long-running tasks, you should first acknowledge receiving the webhook by returning a 200 OK before proceeding with the long-running tasks
  6. If we don’t get a 200 OK HTTP response from your webhooks, we flagged it as a failed attempt
  7. Failed attempts are retried hourly for the next 72 hours

Supported events

  "event": "payment.completed",
  "data": {
    "event_type": "payment.completed",
    "id": "50beb36e-2b28-4eb0-82ef-abd097339664",
    "sendAmount": 1000,
    "sendCurrency": "NGN",
    "businessId": 1,
    "dateCreated": "08/11/2022",
    "timeCreated": "17:03:18",
    "paymentReceiveDate": "Nov 8, 2022 17:11:23",
    "payoutSendDate": "Nov 8, 2022 17:11:25",
    "payoutCompletedDate": "Nov 8, 2022 17:18:00",
    "state": "COMPLETE",
    "feeAmount": 20,
    "feeCurrency": "NGN",
    "receiveAmount": 32,
    "receiveCurrency": "GHS",
    "beneficiaryName": "John Doe",
    "beneficiaryType": "INDIVIDUAL",
    "beneficiaryEmail": "[email protected]",
    "accountNumber": "233123456789",
    "routeName": "MOBILE",
    "channelName": "MTN",
    "beneficiaryAddress": "Sample address"

Event types

payment.completedA payment request has been paid for and delivered to your beneficiary