Webhooks
Webhooks are HTTP callbacks that send real-time transaction notifications to your server. When a transaction completes, WaafiPay automatically sends the transaction details to your registered endpoint, eliminating the need for polling.
Current Support: Webhooks are available for authorization and refund transactions via the Hosted Payment Page (HPP). Failed deliveries are not automatically retried.
Best Practice: Implement proper logging and error handling for webhook events to ensure you don't miss critical transaction updates.
Quick Start
How It Works
- Register a webhook URL → 2. Receive test ping → 3. Transaction occurs → 4. Receive webhook → 5. Verify signature → 6. Check
event+status→ 7. Take action → 8. Return 200 OK
Understanding the Payload
Every webhook has two key fields:
event: Transaction type (authorization,refund, orwebhook.test)payment.status: Outcome (APPROVED,FAILED,DECLINED,CANCELED,EXPIRED,TIMEOUT)
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
5. Test Your Webhook
When you register a webhook, WaafiPay automatically sends a test request to validate that your endpoint is reachable and responding correctly.
Test Webhook Payload
What to Expect
- User-Agent:
WPNotifyService-Test - Content-Type:
application/json - Timeout: 5 seconds
- Expected Response:
200 OK
Test Event Details
| Field | Description | Example |
|---|---|---|
event | Special test event type | webhook.test |
message | Descriptive message indicating this is a test | WaafiPay webhook validation ping |
merchant_uid | Your merchant ID | 10165 |
Note: Test webhooks (
webhook.test) are not signed with HMAC signatures. They are sent only during webhook registration to verify endpoint availability. Your webhook handler should recognize this special event type and respond with200 OKwithout requiring signature verification.
Example Implementation
Receiving a Webhook on Your Server
Once registered, your webhook URL will receive transaction notifications. The webhook payload structure is simple:
- The
eventfield tells you the transaction type (authorization or refund) - The
payment.statusfield tells you the transaction outcome (approved, failed, etc.)
Note: The payload structure may differ between authorization and refund events, and some fields may be optional. See the field reference table below for details on which fields are present in each event type.
Authorization Transaction Example
Refund Transaction Example
Webhook Fields Reference
| Field | Description | Example | Authorization | Refund |
|---|---|---|---|---|
event | Transaction type | authorization, refund | ✓ | ✓ |
merchant_id | Merchant ID (numeric) | 10165 | ✓ | ✓ |
merchant_uid | Merchant UID (string) | M400394 | ✓ | ✓ |
user_id | User ID | 400394 | ✓ | ✓ |
customer_identity | Customer phone/card number | 252612345678 | ✓ | - |
cardholder_name | Customer name | John Doe | ✓ (optional) | - |
payment.transaction_id | Transaction ID | 1303630 | ✓ | ✓ |
payment.order_id | Order ID | ORD-2024-001 | ✓ (optional) | - |
payment.transfer_code | Provider transfer code | ISR0011303630 | ✓ | ✓ |
payment.amount | Transaction amount | 100.50 | ✓ | ✓ |
payment.currency | Currency code | USD, DJF | ✓ | ✓ |
payment.payment_method | Payment method | MWALLET_ACCOUNT, CREDIT_CARD | ✓ | - |
payment.status | Transaction outcome | APPROVED, FAILED, etc. | ✓ | ✓ |
payment.reference_id | Your reference ID | REF-2024-4567 | ✓ | ✓ |
payment.channel | Transaction channel | WEB, APP, POS | ✓ | - |
payment.description | Transaction description | Payment for order... | ✓ (optional) | - |
payment.date | Transaction timestamp | 2025-08-13 15:04:05 | ✓ | ✓ |
Understanding Events and Status
Events (Transaction Types)
Webhooks are sent for the following events:
| Event | Description | When It's Sent | HMAC Signed |
|---|---|---|---|
webhook.test | Webhook validation test | When you register or update a webhook URL | No |
authorization | Payment authorization transaction | When a customer completes (or fails to complete) a payment authorization via HPP | Yes |
refund | Refund transaction | When a refund is processed for a previous authorization | Yes |
Status Values (Transaction Outcomes)
The payment.status field tells you what happened with the transaction:
| Status | Meaning | Applies To | What To Do |
|---|---|---|---|
APPROVED | Transaction succeeded | Authorization, Refund | Fulfill order / Update accounting |
FAILED | Transaction failed due to technical error | Authorization, Refund | Cancel order / Log error and notify support |
DECLINED | Transaction declined by payment provider/bank | Authorization, Refund | Cancel order and notify customer / Log refund failure |
CANCELED | Customer canceled the transaction | Authorization | Cancel order |
EXPIRED | Transaction link/session expired before completion | Authorization | Mark as abandoned, optionally resend payment link |
TIMEOUT | Transaction timed out without completion | Authorization, Refund | Mark as abandoned / Log and retry refund |
Handling Webhooks
Always check both
eventandpayment.statusto determine the correct action.
Security: HMAC Signature Verification
All webhooks (except webhook.test) include an HMAC-SHA256 signature for verification. You must verify this signature to prevent fake webhook attacks.
How It Works
Signing String Format: {timestamp}.{event_id}.{raw_body_bytes}
- We compute HMAC-SHA256 of the signing string using your secret key
- We send the signature in the
X-Webhook-Signatureheader - You recompute the signature and compare it
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
- Capture raw body before JSON parsing
- Build signing string:
{timestamp}.{event_id}.{raw_body_bytes} - Compute HMAC-SHA256 using your secret key
- Compare signatures using constant-time comparison
- Validate timestamp (reject if >5 minutes old)
- Check event ID (reject if already processed)
- Return 200 OK quickly (process async if needed)
Critical: Always use the raw HTTP body bytes, not parsed JSON. Any whitespace changes will break verification.
Example Webhooks
Example 1: Authorization Webhook
Headers
Body
Example 2: Refund Webhook
Headers
Body
Implementation Guide
Complete Example
Here's a production-ready webhook handler:
Best Practices
- Use HTTPS: Webhook URL must use HTTPS
- Store secret securely: Use environment variables, never commit to code
- Return 200 quickly: Respond within 5 seconds, process async if needed
- Log everything: Keep webhook logs for debugging
- Implement idempotency: Use event IDs to prevent duplicate processing
- Handle test events: Respond to
webhook.testwithout signature verification
Common Issues
| Issue | Solution |
|---|---|
| Signature verification fails | Capture raw HTTP body before parsing JSON |
| Webhooks not received | Ensure URL is publicly accessible |
| Duplicate processing | Track event IDs using X-Webhook-Event-Id |
| Timeout errors | Return 200 OK immediately, queue for async processing |
| Replay attacks | Validate timestamp (reject if >5 minutes old) |
