Delivery semantics

Automatic retries with exponential backoff, plus manual redelivery from the dashboard.

The platform delivers every webhook at least once: an immediate dispatch on each invoice state transition, then automatic retries with exponential backoff while your endpoint keeps failing.

What happens on dispatch#

  1. The platform POSTs the signed JSON body to your webhook URL.
  2. Before connecting, the destination is re-validated: the hostname must resolve to a public address (see URL requirements).
  3. The HTTP response is captured:
    • status code — stored and visible in the dashboard.
    • error string — captured if the request failed before a response (DNS, TLS, timeout, or a rejected destination).
  4. A 2xx status marks the event delivered. Anything else marks it failed and schedules the next automatic retry.

The body of your server's response is not shown in the dashboard and is not returned by the API — only the status code and error string are.

Automatic retry schedule#

Failed deliveries are retried on an exponential backoff (with ±20% jitter):

AttemptDelay after previous failure
2~30 seconds
3~2 minutes
4~10 minutes
5~30 minutes
6~2 hours
7~6 hours
8~24 hours

After 8 total attempts the event becomes exhausted: automatic retries stop and platform operators are alerted. You can still redeliver an exhausted event manually — see Retry failed webhooks.

Webhook URL requirements#

Your webhook URL must:

  • use http or https (https strongly recommended);
  • contain no embedded credentials (user:pass@);
  • resolve to a publicly routable address. Private, loopback, link-local and carrier-grade-NAT ranges are rejected — both when you save the URL and again at every delivery.

Redirects are not followed — point the URL at the final destination. For local development use a public tunnel (ngrok, cloudflared).

How to build against this contract#

  • Dedupe with X-Webhook-ID. Every attempt of the same event — automatic or manual — carries the same ID; first successful processing wins.
  • Return 2xx fast. Do slow work async on your side. A handler that takes seconds will occasionally time out and burn a retry attempt.
  • Don't assume arrival order. Retries mean a later transition can arrive before an earlier one. The status field in the payload is authoritative.
  • Monitor the dashboard's Webhook events tab for failed / exhausted rows — they mean your endpoint has been failing for a while.