Receiving Webhook Events
Set up and verify webhook endpoints, including payload structure and signature verification.
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).
Payload Structure
Webhook payloads follow this structure:
{
"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"
}
}
}Signature Verification
Steps
- Extract signature from
X-Signatureheader. - Get raw payload - Use the exact request body.
- Decode signature - Base64 decode the signature.
- Load private key - Retrieve your stored private key.
- Extract public key - Derive from the private key.
- Verify signature - Use RSA-PSS with SHA-256.
Verification Using Node.js Crypto Module
const crypto = require('crypto');
const fs = require('fs');
function verifyWebhookSignature(payload, signature, privateKeyPath) {
try {
const privateKey = fs.readFileSync(privateKeyPath, 'utf8');
const publicKey = crypto.createPublicKey(privateKey);
const signatureBuffer = Buffer.from(signature, 'base64');
const verifier = crypto.createVerify('SHA256');
verifier.update(payload);
return verifier.verify({
key: publicKey,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
}, signatureBuffer);
} catch (error) {
console.error('Signature verification error:', error);
return false;
}
}
const express = require('express');
const app = express();
app.use('/webhooks/amwal', express.raw({ type: 'application/json' }));
app.post('/webhooks/amwal', (req, res) => {
const signature = req.headers['x-signature'];
const apiKey = req.headers['x-api-key'];
const rawPayload = req.body.toString('utf8');
if (apiKey !== process.env.EXPECTED_API_KEY_FINGERPRINT) {
return res.status(401).json({ error: 'Invalid API key' });
}
if (!verifyWebhookSignature(rawPayload, signature, process.env.PRIVATE_KEY_PATH)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const webhook = JSON.parse(rawPayload);
console.log('Valid webhook received:', webhook.event_type);
res.status(200).json({ status: 'success' });
});Key Configuration
- Store private keys securely.
- Never expose private keys in logs or code.
- Use the exact private key provided during webhook creation.
Updated about 15 hours ago