Skip to main content
Understand how Baanx delivers webhook events, what HTTP responses are expected from your endpoint, and how automatic retries work.

Delivery Guarantees

GuaranteeDetail
At-least-onceEvents may be delivered more than once due to retries or network conditions
No guaranteed orderEvents are not guaranteed to arrive in the same order they occurred
30-second timeoutRequests that do not receive a response within 30 seconds are treated as failed
IdempotencyThe 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:
  1. Acknowledge immediately — return a 2xx response as soon as you’ve received and validated the request
  2. Process asynchronously — push the event to an internal queue and process it in the background
  3. 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:
StatusDescription
200 OKEvent received and processed
201 CreatedEvent received and resource created
204 No ContentEvent 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.
StatusDescription
400 Bad RequestInvalid payload format
401 UnauthorizedInvalid or missing signature
403 ForbiddenEndpoint not authorised
404 Not FoundEndpoint not found

Server Error Responses — Will Retry

5xx responses and network failures trigger automatic retries:
StatusDescription
500 Internal Server ErrorServer error
502 Bad GatewayGateway error
503 Service UnavailableService unavailable
504 Gateway TimeoutRequest timed out

Retry Policy

When delivery fails with a 5xx response or a network error, Baanx automatically retries with exponential backoff:
AttemptDelay After Failure
1st retry1 second
2nd retry2 seconds
3rd retry4 seconds
4th retry8 seconds
5th retry16 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: