Understand how Baanx delivers webhook events, what HTTP responses are expected from your endpoint, and how automatic retries work.
Delivery Guarantees
| Guarantee | Detail |
|---|
| At-least-once | Events may be delivered more than once due to retries or network conditions |
| No guaranteed order | Events are not guaranteed to arrive in the same order they occurred |
| 30-second timeout | Requests that do not receive a response within 30 seconds are treated as failed |
| Idempotency | The same event_id is used across all retry attempts |
Because delivery is at-least-once, always use event_id as an idempotency key to prevent processing the same event twice. See Event Types & Payload Structure for an example.
Responding to Webhooks
Your endpoint must return an HTTP response within 30 seconds. To meet this reliably:
- Acknowledge immediately — return a
2xx response as soon as you’ve received and validated the request
- Process asynchronously — push the event to an internal queue and process it in the background
- Verify the signature first — return
401 if the signature is invalid (this will not be retried)
Success Responses
Any 2xx status code is treated as a successful delivery:
| Status | Description |
|---|
200 OK | Event received and processed |
201 Created | Event received and resource created |
204 No Content | Event received (no response body needed) |
Client Error Responses — Not Retried
4xx responses are treated as permanent failures and will not be retried. Use these to signal that the request itself is invalid.
| Status | Description |
|---|
400 Bad Request | Invalid payload format |
401 Unauthorized | Invalid or missing signature |
403 Forbidden | Endpoint not authorised |
404 Not Found | Endpoint not found |
Server Error Responses — Will Retry
5xx responses and network failures trigger automatic retries:
| Status | Description |
|---|
500 Internal Server Error | Server error |
502 Bad Gateway | Gateway error |
503 Service Unavailable | Service unavailable |
504 Gateway Timeout | Request timed out |
Retry Policy
When delivery fails with a 5xx response or a network error, Baanx automatically retries with exponential backoff:
| Attempt | Delay After Failure |
|---|
| 1st retry | 1 second |
| 2nd retry | 2 seconds |
| 3rd retry | 4 seconds |
| 4th retry | 8 seconds |
| 5th retry | 16 seconds |
Retry rules:
- Maximum 5 retry attempts after the initial delivery (6 total attempts)
- Only
5xx status codes and network failures trigger retries
4xx errors are not retried — they indicate a client-side problem
- The same
event_id is used across all attempts
- After all retries are exhausted, the event is marked
failed and can be manually retried
Handling Duplicates
Since retries use the same event_id, your endpoint should store processed event IDs and skip re-processing:
async function handleWebhook(event) {
// Check if already processed
const existingEvent = await db.findEvent(event.event_id);
if (existingEvent) {
// Already handled — return success without reprocessing
return { status: 204 };
}
// Process the event
await processEvent(event);
await db.markEventProcessed(event.event_id);
return { status: 204 };
}
async def handle_webhook(event: dict) -> dict:
# Check if already processed
if await db.find_event(event["event_id"]):
return {"status": 204}
# Process the event
await process_event(event)
await db.mark_event_processed(event["event_id"])
return {"status": 204}
Monitoring Deliveries
Use the API to inspect delivery history and troubleshoot failures: