Endpoints

Guide to managing webhook endpoints, API keys, and event handling.

Base URL

https://backend.sa.amwal.tech/api/

Webhook Management

List Webhook Endpoints

async function listWebhookEndpoints() {
  const response = await fetch('https://backend.sa.amwal.tech/api/webhook-endpoints/', {
    headers: {
      'X-API-Key': 'YOUR_API_KEY_FINGERPRINT'
    }
  });

  return await response.json();
}

Update Webhook Endpoint

async function updateWebhookEndpoint(webhookId, updates) {
  const response = await fetch(
    `https://backend.sa.amwal.tech/api/webhook-endpoints/${webhookId}/`,
    {
      method: 'PUT',
      headers: {
        'X-API-Key': 'YOUR_API_KEY_FINGERPRINT',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(updates)
    }
  );

  return await response.json();
}

Delete Webhook Endpoint

async function deleteWebhookEndpoint(webhookId) {
  const response = await fetch(
    `https://backend.sa.amwal.tech/api/webhook-endpoints/${webhookId}/`,
    {
      method: 'DELETE',
      headers: {
        'X-API-Key': 'YOUR_API_KEY_FINGERPRINT'
      }
    }
  );

  return response.ok;
}

API Key Management

List API Keys

async function listApiKeys() {
  const response = await fetch('https://backend.sa.amwal.tech/api/api-keys/', {
    headers: {
      'X-API-Key': 'YOUR_API_KEY_FINGERPRINT'
    }
  });

  return await response.json();
}

Update API Key Status

async function updateApiKeyStatus(apiKeyId, isActive) {
  const response = await fetch(
    `https://backend.sa.amwal.tech/api/api-keys/${apiKeyId}/`,
    {
      method: 'PATCH',
      headers: {
        'X-API-Key': 'YOUR_API_KEY_FINGERPRINT',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ is_active: isActive })
    }
  );

  return await response.json();
}

Event Management

Trigger Test Event

async function triggerTestEvent(eventType, payload) {
  const response = await fetch('https://backend.sa.amwal.tech/api/trigger-event/', {
    method: 'POST',
    headers: {
      'X-API-Key': 'YOUR_API_KEY_FINGERPRINT',
      'X-Signature': 'GENERATED_SIGNATURE',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      event_type: eventType,
      payload: payload,
      merchant_id: 'YOUR_MERCHANT_ID'
    })
  });

  return await response.json();
}

// Usage
await triggerTestEvent('test.event', {
  order_id: 'test_12345',
  amount: 1.00,
  currency: 'SAR'
});

Monitoring & Debugging

View Delivery Attempts

async function getDeliveryAttempts(filters = {}) {
  const params = new URLSearchParams(filters);
  const response = await fetch(
    `https://backend.sa.amwal.tech/api/delivery-attempts/?${params}`,
    {
      headers: {
        'X-API-Key': 'YOUR_API_KEY_FINGERPRINT'
      }
    }
  );

  const data = await response.json();
  
  return data.results.map(attempt => ({
    id: attempt.id,
    webhook_url: attempt.webhook_url,
    event_type: attempt.event_type,
    status: attempt.status,
    attempts: attempt.attempts,
    last_attempt: attempt.last_attempt
  }));
}

// Usage
const recentAttempts = await getDeliveryAttempts({ limit: 50 });
console.log('Recent delivery attempts:', recentAttempts);

Get Delivery Logs

async function getDeliveryLogs(webhookId, limit = 50) {
  const response = await fetch(
    `https://backend.sa.amwal.tech/api/delivery-attempts/?webhook=${webhookId}&limit=${limit}`,
    {
      headers: {
        'X-API-Key': 'YOUR_API_KEY_FINGERPRINT'
      }
    }
  );

  return await response.json();
}

Error Handling & Retry Logic

Retry Mechanism

Amwal retries failed webhooks with exponential backoff:

  • Max attempts: 5
  • Backoff: 2^attempt seconds
  • Max delay: 1 hour
  • Retry on: HTTP 5xx, timeouts, network errors
  • No retry on: HTTP 4xx

Response Guidelines

HTTP StatusMeaningRetry?
200-299SuccessNo
400Bad RequestNo
401UnauthorizedNo
403ForbiddenNo
404Not FoundNo
409ConflictNo
422UnprocessableNo
429Rate LimitedYes
500-599Server ErrorYes

Rate Limiting

  • Limit: 60 requests/minute
  • Window: 60 seconds
  • Headers: Rate limit info included

Idempotency

Handle duplicate deliveries:

const processedWebhooks = new Set();

async function checkAndMarkProcessed(webhookId, eventType) {
  const key = `${webhookId}_${eventType}`;
  
  const response = await fetch(`https://your-api.com/webhooks/processed/${webhookId}`);
  
  if (response.ok) {
    const data = await response.json();
    if (data.exists) {
      console.log('Webhook already processed, skipping');
      return true;
    }
  }
  
  return false;
}

async function markWebhookProcessed(webhookId, eventType) {
  await fetch('https://your-api.com/webhooks/processed', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      webhook_id: webhookId,
      event_type: eventType,
      processed_at: new Date().toISOString()
    })
  });
}

// Usage
app.post('/webhooks/amwal', async (req, res) => {
  const webhook = JSON.parse(req.body.toString('utf8'));
  const webhookId = webhook.id;
  const eventType = webhook.event_type;

  if (await checkAndMarkProcessed(webhookId, eventType)) {
    return res.status(200).json({ status: 'already_processed' });
  }

  try {
    await processWebhookEvent(webhook);
    await markWebhookProcessed(webhookId, eventType);
    res.status(200).json({ status: 'success' });
  } catch (error) {
    console.error('Webhook processing error:', error);
    res.status(500).json({ error: 'Processing failed' });
  }
});