Receiving Webhook Events
Set up and verify webhook endpoints, including payload structure and signature verification.
Receive webhook POST requests, validate the request headers and payload, and return the correct HTTP status so Amwal can deliver events reliably.
Webhook Endpoint Requirements
Your webhook endpoint must:
- Accept HTTP POST requests with JSON payloads.
- Return HTTP 200 for successful processing within 30 seconds.
- Return HTTP 4xx for permanent failures (no retries).
- Return HTTP 5xx for temporary failures (will retry).
- Use HTTPS - HTTP is not supported.
- Validate signatures before processing (recommended).
Receive and Acknowledge a Webhook
Create a webhook endpoint that accepts POST requests, checks the required headers, validates the payload shape, and returns the correct status code:
const express = require('express');
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 3000;
const expectedApiKey = process.env.AMWAL_API_KEY_FINGERPRINT;
app.use(express.json());
function processEvent(webhook) {
switch (webhook.event_type) {
case 'order.created':
case 'order.success':
case 'order.failed':
case 'order.updated':
return {
accepted: true,
eventType: webhook.event_type,
orderId: webhook?.data?.id || webhook?.id || null
};
default:
return {
accepted: false,
reason: `Unknown event type: ${webhook.event_type}`
};
}
}
app.post('/webhooks/amwal', async (req, res) => {
try {
const apiKey = req.header('X-API-Key');
const signature = req.header('X-Signature');
const contentType = req.header('Content-Type') || '';
if (!contentType.includes('application/json')) {
return res.status(400).json({
error: 'Invalid Content-Type. Use application/json.'
});
}
if (!apiKey || apiKey !== expectedApiKey) {
return res.status(401).json({
error: 'Invalid X-API-Key header.'
});
}
if (!signature) {
return res.status(401).json({
error: 'Missing X-Signature header.'
});
}
const webhook = req.body;
if (!webhook.event_type || !webhook.data) {
return res.status(400).json({
error: 'Invalid payload structure.'
});
}
const result = processEvent(webhook);
if (!result.accepted) {
return res.status(400).json({
error: result.reason
});
}
console.log(`Received ${result.eventType}`, {
order_id: result.orderId,
webhook_id: webhook.id || null
});
return res.status(200).json({
status: 'received',
event_type: result.eventType,
webhook_id: webhook.id || null
});
} catch (error) {
console.error('Webhook processing failed:', error.message);
return res.status(500).json({
error: 'Temporary processing failure.'
});
}
});
app.listen(PORT, () => {
console.log(`Webhook listener running on port ${PORT}`);
});Expected success response:
{
"status": "received",
"event_type": "order.success",
"webhook_id": "amwal_order_12345"
}Expected error responses:
{
"error": "Invalid X-API-Key header."
}{
"error": "Invalid payload structure."
}{
"error": "Temporary processing failure."
}Use the signature verification flow in Verify Signature before you process the webhook payload.
Payload Structure
Webhook payloads follow this structure:
Order.Success Event
{
"event_type": "order.success",
"id": "amwal_order_12345",
"timestamp": "2025-12-15T10:30:00Z",
"data": {
"id": "amwal_order_12345",
"ref_id": "ORD-2025-001",
"merchant_order_id": "ORD-2025-001",
"amount": 99.99,
"currency": "SAR",
"status": "completed",
"customer": {
"id": "customer_123",
"email": "[email protected]",
"phone": "+966501234567",
"first_name": "John",
"last_name": "Doe"
},
"payment": {
"method": "wallet",
"reference": "pay_ref_12345",
"transaction_id": "txn_67890",
"gateway": "amwal"
},
"shipping": {
"address": {
"street": "123 Main St",
"city": "Riyadh",
"state": "Riyadh Province",
"country": "SA",
"postal_code": "11564"
},
"method": "standard"
},
"metadata": {
"custom_field": "value",
"integration_version": "1.0.0"
}
}
}Order.updated Event
Refund Payload Example
{
"event_type": "order.updated",
"data": {
"address_details": {
"id": "3e637800-bede-4f50-82ea-d0c996d8c2cf",
"city": "الصوارى",
"state": "جدة",
"country": "SA",
"street1": "Icon55 jeddah",
"postcode": "37732"
},
"refunded_amount": "1.30",
"amount": "0.87000",
"amwal_fee": "1.03",
"amwal_tax_vat": "0.15",
"app_notified": false,
"approval_type": null,
"brand_percentage": "2.50",
"brand_static_fee": "1.00",
"card_last_4_digits": "",
"card_holder": "",
"card_payment_brand": "",
"client_email": "[email protected]",
"client_first_name": "M",
"client_last_name": "Z",
"client_phone_number": "+966542170857",
"client_registered": true,
"created_at": "2025-12-14T08:37:32.640759+03:00",
"discount": "0.00000",
"fees": "0.00000",
"has_new_registration": false,
"hyperpay_checkout_id": null,
"hypersplit_id": null,
"id": "48744b9b-ecf6-4ed4-a511-d85f7ec3c15c",
"is_approved_by_client": null,
"is_refundable": false,
"merchant_business_name": "Amwal Shop",
"merchant_country_code": "SA",
"merchant_english_business_name": "Amwal Shop",
"merchant_key": "prod-amwal-5563d261-8545-418b-b122-8767a2e59931",
"merchant_payout": "0.11",
"order_details": {
"order_id": "5276",
"order_url": "https://shop.amwal.tech/checkout/order-received/5276/?key=wc_order_7zIS8V0Fm5Ced"
},
"paymentBrand": "VISA",
"payment_method": "Apple Pay",
"payment_option": "Pay In Full",
"ref_id": "amwalwc-payload693e4d1932fb2",
"shipping": "0.30000",
"shipping_details": {
"id": "flat_rate:12",
"tax": 0,
"label": "Flat rate",
"price": 0.3
},
"status": "refunded",
"store_domain": "https://shop.amwal.tech/",
"store_logo": "https://fra1.digitaloceanspaces.com/media-amwal/media/store_logo/481a2629-ca3.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=DO801HVVA4FFJDYGV84V%2F20260209%2Ffra1%2Fs3%2Faws4_request&X-Amz-Date=20260209T095947Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=e533a51291c4fc02b4a7daccc54ef49325901fda62777d8e55ee53c481f788d2",
"taxes": "0.13000",
"total_amount": "1.30",
"convenience_fee": "0.00",
"payment_amount": "1.30",
"failure_reason": null,
"type": "PRODUCTION",
"gateway": 11,
"gateway_type": 4,
"payment_link_id": "",
"payment_link_callback_url": "",
"payment_link_description": "",
"billing_address_required": false,
"supports_stc_pay": false,
"refund_tracker": [
{
"id": "3392",
"status": "R",
"amount": "1.30",
"gateway_transaction_id": "2026000179",
"gateway_key_type": "RefundReference",
"rrn": "603613330708"
}
],
"merchant_domain": "https://shop.amwal.tech/",
"card_type": "",
"card_bank_issuer": "",
"installment_fee": "0.00",
"installment_fee_vat": "0.00",
"murabha_fee": "0.00",
"murabha_fee_vat": "0.00",
"installment_duration": null,
"branch_name": "-",
"payment_link_lang": "",
"payment_link_only_show_bank_installments": true,
"payment_link_only_show_pay_in_full": true,
"payment_link_show_shipping": false,
"payment_link_passkey_enabled": true,
"address_required": false,
"funding_method": "credit",
"issuer": "Celtic Bank",
"number": "427106xxxxxx7643",
"bank_name": "",
"trx_lang": "en"
}
}Optional: Troubleshoot Request Handling
- Return
200only after your application has accepted the event for processing. - Return
400when the payload is invalid or required fields are missing. - Return
401whenX-API-KeyorX-Signatureis missing or invalid. - Return
500when your system cannot process the event and Amwal should retry. - Keep processing under 30 seconds. If your business logic takes longer, queue the job and acknowledge the webhook first.
Updated 1 day ago