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:
Request Parameters
Parameter | Data Type | Presence | Description |
---|---|---|---|
schemaVersion | String | Constant | API schema version (e.g., "1.0"). |
requestId | UUID String | Required | Unique request identifier (e.g., UUID). |
timestamp | String | Required | Date and time of the request. |
channelName | String | Constant | The channel through which the request was made. |
serviceName | String | Constant | The name of the service being called (e.g., WEBHOOK_REGISTER). |
merchantUid | String | Required | Unique identifier provided by WaafiPay upon merchant account setup. |
storeId | String | Required | API store/user identifier for the merchant. |
hppKey | String | Required | API key for request authentication. |
partnerUid | String | Optional | WAAFI Merchant Number (PartnerUID). |
description | String | Optional | Description of your webhook usage. |
url | String | Required | URL endpoint for webhook to be sent to. Only one webhook endpoint is allowed per merchant. |
Response:
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
Response
3. Update Webhook
You can update the url
and description
properties of an existing webhook.
POST Request
4. Delete Webhook
Remove an existing webhook.
POST Request
Receiving a Webhook on Your Server
Once registered, your webhook URL will receive transaction notifications in the following format:
Webhook Sample Payload
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
orDJF
.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, thepayment.status
will beAPPROVED
, and the event will bepayment_received
. If the payment fails, thepayment.status
will beFAILED
orDECLINED
, and the event will bepayment_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
-
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.
-
We compute an HMAC-SHA256 digest of that string using your secret key.
-
We include the signature and metadata in the webhook HTTP headers.
-
You recompute the signature on your side and compare it to the value we sent.
Headers We Send
Header | Purpose |
---|---|
User-Agent: WPNotifyService | Identifies the source of the webhook request |
Content-Type: application/json | Indicates the media type of the resource |
X-Webhook-Timestamp | Unix timestamp (seconds) to prevent replay attacks |
X-Webhook-Event-Id | Unique per event; use for replay protection |
X-Webhook-Signature-Alg | Always HMAC-SHA256 |
X-Webhook-Signature | Encoded as lowercase hex digest of HMAC-SHA256 over the signing string |
Content-Type | Always application/json |
Verification Steps
-
Read the raw HTTP body exactly as received (before JSON parsing).
-
Extract
X-Webhook-Timestamp
andX-Webhook-Event-Id
from headers. -
Build the string:
{timestamp}.{event_id}.{raw_body_bytes}
-
Compute HMAC-SHA256 with your secret.
-
Compare your computed hex digest to
X-Webhook-Signature
using constant-time comparison. -
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. -
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
Body
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.