WaafipaydocsWaafiPay

Webhooks

WaafiPay offers webhook notifications to keep you informed about transaction status updates. A webhook is an HTTP callback that automatically sends real-time data to a pre-configured URL when specific events occur, such as the completion or failure of a transaction. This enables your system to immediately receive and process updates without the need for constant polling. Please note that webhook notifications are currently supported only for transactions processed via the Hosted Payment Page (HPP), and failed webhook deliveries do not trigger automatic retry attempts.

Merchants can register their webhook endpoints via our API, receiving real-time updates about transaction outcomes, including both successful completions and failures. To ensure robust integration, it is recommended to implement proper logging and error-handling mechanisms for webhook events on your end. This is especially important to prevent missing crucial information due to issues like failed customer redirection (such as HPP callbacks).

If you need any help with integration or have further questions, feel free to reach out. We're here to help!


1. Register a Webhook

You can only register one webhook URL to receive payment notifications. The provided URL should be publicly accessible.

In all API requests, you must include the HPP credentials: merchantUid, storeId, hppKey.

Request:

POST /asm
{
    "schemaVersion": "1.0",
    "requestId": "{{$guid}}",
    "timestamp": "{{$timestamp}}",
    "channelName": "WEB",
    "serviceName": "WEBHOOK_REGISTER",
    "serviceParams": {
        "merchantUid": {{MERCHANT_UID}},
        "storeId": {{STORE_ID}},
        "hppKey": {{HPP_KEY}},
        "url": "https://api.example.com/webhook",
        "description": "a description"
    }
}

Request Parameters

ParameterData TypePresenceDescription
schemaVersionStringConstantAPI schema version (e.g., "1.0").
requestIdUUID StringRequiredUnique request identifier (e.g., UUID).
timestampStringRequiredDate and time of the request.
channelNameStringConstantThe channel through which the request was made.
serviceNameStringConstantThe name of the service being called (e.g., WEBHOOK_REGISTER).
merchantUidStringRequiredUnique identifier provided by WaafiPay upon merchant account setup.
storeIdStringRequiredAPI store/user identifier for the merchant.
hppKeyStringRequiredAPI key for request authentication.
partnerUidStringOptionalWAAFI Merchant Number (PartnerUID).
descriptionStringOptionalDescription of your webhook usage.
urlStringRequiredURL endpoint for webhook to be sent to. Only one webhook endpoint is allowed per merchant.

Response:

{
    "schemaVersion": "1.0",
    "timestamp": "2024-02-08 04:38:42.938",
    "responseId": "{{$guid}}",
    "responseCode": "2001",
    "errorCode": "0",
    "responseMsg": "Webhook created successfully",
    "params": {
        "secret": "your_secret"
    }
}

Note: Store your secret securely—do not commit it to source control. We only provide it once during webhook registration. This secret is used to verify the authenticity of incoming webhook requests.


2. Get All Webhooks

Retrieve a list of all existing webhooks.

POST Request

POST /asm
{
    "schemaVersion": "1.0",
    "requestId": "{{$guid}}",
    "timestamp": "{{$timestamp}}",
    "channelName": "WEB",
    "serviceName": "WEBHOOK_LIST",
    "serviceParams": {
        "merchantUid": "{{MERCHANT_UID}}",
        "storeId": {{STORE_ID}},
        "hppKey": "{{HPP_KEY}}",
    }
}

Response

{
    "params": {
        "data": [
            {
                "id": "25",
                "url": "https://app.sample.com/webhook",
                "description": "testing",
                "merchantId": "10165",
                "partnerUid": "400394",
                "merchantName": "WaafiPay Store"
            }
        ]
    }
}

3. Update Webhook

You can update the url and description properties of an existing webhook.

POST Request

POST /asm
{
    "schemaVersion": "1.0",
    "requestId": "{{$guid}}",
    "timestamp": "{{$timestamp}}",
    "channelName": "WEB",
    "serviceName": "WEBHOOK_UPDATE",
    "serviceParams": {
        "merchantUid": "{{MERCHANT_UID}}",
        "storeId": {{STORE_ID}},
        "hppKey": "{{HPP_KEY}}",
        "url": "https://api.sample.so/webhook/v2",
        "webhookId": 10,
        "description": "another description"
    }
}

4. Delete Webhook

Remove an existing webhook.

POST Request

POST /asm
{
    "schemaVersion": "1.0",
    "requestId": "{{$guid}}",
    "timestamp": "{{$timestamp}}",
    "channelName": "WEB",
    "serviceName": "WEBHOOK_DELETE",
    "serviceParams": {
        "merchantUid": "{{MERCHANT_UID}}",
        "storeId": {{STORE_ID}},
        "hppKey": "{{HPP_KEY}}",
        "webhookId": 10,
    }
}

Receiving a Webhook on Your Server

Once registered, your webhook URL will receive transaction notifications in the following format:

Webhook Sample Payload

{
  "event": "payment_received",
  "customer_identity": "2526xxxxxxx",
  "cardholder_name": "John Doe",
  "merchant_id": "merchant-uid-123",
  "payment": {
    "transaction_id": "123456",
    "order_id": "7890",
    "transfer_code": "T-CODE-001",
    "amount": 100.50,
    "currency": "USD",
    "payment_method": "MWALLET_ACCOUNT",
    "status": "APPROVED",
    "reference_id": "INV-4567",
    "channel": "WEB",
    "description": "Payment for order 7890",
    "date": "2025-08-13 15:04:05"
  }
}

Field reference:

  • event: One of payment_received, payment_failed.
  • customer_identity: Your customer identifier (e.g., phone, card_no).
  • cardholder_name: Optional name associated with the customer.
  • merchant_id: Your merchant ID registered with us.
  • payment.transaction_id: WaafiPay transaction ID.
  • payment.order_id: WaafiPay order ID when provided.
  • payment.transfer_code: Provider transfer code when available.
  • payment.amount: Transaction amount.
  • payment.currency: ISO-like currency code, e.g. USD or DJF.
  • payment.payment_method: Payment method used (e.g., MWALLET_ACCOUNT, CREDIT_CARD).
  • payment.status: Provider status string (e.g., APPROVED, FAILED, CANCELED, TIMEOUT).
  • payment.reference_id: ReferenceId from your system provided in the initial payment request.
  • payment.channel: Channel used (e.g., WEB, APP, POS).
  • payment.description: Optional description.
  • payment.date: Local gateway time formatted as “YYYY-MM-DD HH:MM:SS”.

Events

  • payment_received: Successful payment.
  • payment_failed: Failed/declined/rejected payment.
  • payment_expired: Payment has expired. A payment is considered expired if it has not been completed within a certain time frame (e.g., 10 minutes).
  • payment_timed_out: Payment has timed out without the customer completing the transaction. This typically occurs if the customer does not complete the payment within a specified time limit (e.g., 5 minutes).
  • payment_canceled: Payment has been canceled by the user.

IMPORTANT: Always check the event and the payment.status field to determine the actual outcome of the transaction. If the payment is successful, the payment.status will be APPROVED, and the event will be payment_received. If the payment fails, the payment.status will be FAILED or DECLINED, and the event will be payment_failed.


Authentication via HMAC Signatures

When we send a webhook to your system, it'll include a cryptographic signature that lets you verify the request really came from us and hasn’t been tampered with.

We use HMAC-SHA256 with a per-merchant secret key. You'll get your secret key in the merchant dashboard. Keep it safe—anyone with it can forge webhooks.


Why This Matters

Without verification, anyone who knows your webhook URL could send fake events. This could:

  • Trigger false order confirmations
  • Cause accounting mismatches
  • Potentially create fraud opportunities

HMAC signatures protect against this by:

  • Authenticating the sender (only you and we know the secret)
  • Protecting integrity (body cannot be changed without breaking the signature)
  • Preventing replays (timestamp and unique event IDs let you reject old or duplicated requests)

How It Works

  1. We create a signing string:

    {timestamp}.{event_id}.{raw_body_bytes}

  • timestamp = Unix time in seconds.
  • event_id = Unique ID for this webhook.
  • raw_body_bytes = JSON payload bytes exactly as sent.
  1. We compute an HMAC-SHA256 digest of that string using your secret key.

  2. We include the signature and metadata in the webhook HTTP headers.

  3. You recompute the signature on your side and compare it to the value we sent.


Headers We Send

HeaderPurpose
User-Agent: WPNotifyServiceIdentifies the source of the webhook request
Content-Type: application/jsonIndicates the media type of the resource
X-Webhook-TimestampUnix timestamp (seconds) to prevent replay attacks
X-Webhook-Event-IdUnique per event; use for replay protection
X-Webhook-Signature-AlgAlways HMAC-SHA256
X-Webhook-SignatureEncoded as lowercase hex digest of HMAC-SHA256 over the signing string
Content-TypeAlways application/json

Verification Steps

  1. Read the raw HTTP body exactly as received (before JSON parsing).

  2. Extract X-Webhook-Timestamp and X-Webhook-Event-Id from headers.

  3. Build the string: {timestamp}.{event_id}.{raw_body_bytes}

  4. Compute HMAC-SHA256 with your secret.

  5. Compare your computed hex digest to X-Webhook-Signature using constant-time comparison.

  6. Acknowledge: Return HTTP 200 OK as soon as you verify the signature and process the event. We expect you to handle the event quickly and not block the response.

  7. Reject if:

    • The signature does not match.
    • The timestamp is more than 5 minutes from your server time.
    • The event ID has already been processed.

Example Webhook

Headers

X-Webhook-Timestamp: 1755045838
X-Webhook-Event-Id: 1151
X-Webhook-Signature: 19797b9fe0eaaa99e4ad509d53f3c91d1bb16d56de20b2252a21260a31913838
Content-Type: application/json

Body

{
    "event": "payment_received",
    "customer_identity": "234243",
    "merchant_id": "10917",
    "payment": {
        "transaction_id": "1303630",
        "transfer_code": "ISR0011303630",
        "amount": 60.2,
        "currency": "USD",
        "payment_method": "MWALLET_ACCOUNT",
        "status": "APPROVED",
        "reference_id": "WS_3062906406",
        "channel": "WEB",
        "date": "2025-08-12 17:59:15"
    }
}

Common Pitfalls

  • Using parsed JSON instead of raw body
  • Any whitespace changes or key reordering will break signature verification. Always capture the raw bytes.
  • Not checking timestamps: Without checking X-Webhook-Timestamp, an attacker could replay an old signed webhook.
  • Storing the secret insecurely: Keep your webhook secret outside of your code repo and in a secure vault.
  • Always use HTTPS for your webhook endpoint.