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 Status | Meaning | Retry? |
|---|---|---|
| 200-299 | Success | No |
| 400 | Bad Request | No |
| 401 | Unauthorized | No |
| 403 | Forbidden | No |
| 404 | Not Found | No |
| 409 | Conflict | No |
| 422 | Unprocessable | No |
| 429 | Rate Limited | Yes |
| 500-599 | Server Error | Yes |
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' });
}
});Updated about 15 hours ago