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:

📘

Map payload identifiers to your records

The payload id maps to the Amwal transaction_id. Use order_details.order_id to match the event to the order ID in your system.

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.




Did this page help you?