Retries and failures

If the receiver doesn't reply 2xx, the delivery is retried on a fixed schedule. If it stays silent for almost a day, the webhook is disabled and its owner gets an e-mail.

Retry schedule

Attempt When
1 immediately after the event
2 +5 seconds
3 +5 minutes
4 +30 minutes
5 +2 hours
6 +5 hours
7 +10 hours
8 +10 hours

That's 8 attempts over ~28 hours. Every pause carries a random jitter of ±10 %: without it, a thousand events from one transaction would hit your server in lockstep — exactly when it is already struggling.

The dispatcher wakes up once a minute. A single change arrives within a minute; a bulk operation (applying a draft, permanently deleting an employee) goes out as a batch within about 15 minutes: no more than 50 deliveries per endpoint are taken on a single pass over the queue, so one noisy receiver can't take over the whole batch. A minute-long tick makes several passes — batch after batch, for as long as its 50-second budget lasts.

The current attempt number is visible in the Smengo-Delivery-Id header, after the colon.

Response classification

Receiver response What we do
2xx success, the response body is ignored
3xx failure redirect_not_allowed: we don't follow redirects — the URL must be served by the final server
410 Gone terminal: the delivery is marked dead and the endpoint is disabled immediately
429, 5xx failure with a retry; the Retry-After header is honoured
other 4xx failure with a retry
no response within 15 seconds failure timeout, with a retry
network, TLS, DNS failure with a retry (network_…, dns_failed)
the URL resolves to a private network failure private_address — the request is not sent at all

Retry-After

The header is understood in both RFC 9110 forms — seconds and HTTP date — and applied as max(schedule, Retry-After) capped at 1 hour. Two consequences:

  • a server can postpone the next attempt but cannot speed it up: Retry-After: 0 won't turn retries into a hot loop;
  • it cannot postpone beyond an hour either — otherwise burning through the attempts would take weeks.

Auto-disabling

The trigger is the first delivery that exhausts all 8 attempts (or a 410 Gone response). Then:

  1. The endpoint is switched to the disabled state — deliveries stop.
  2. Everything queued for that endpoint is marked dead and will not be sent after you switch it back on: otherwise a repaired server would be hit by a 28-hour backlog.
  3. An e-mail goes to the owner (the organization's billing address, otherwise the endpoint creator's address).
  4. The webhook card shows a banner with the reason — visible even without the e-mail.

To bring delivery back: fix the receiver → Send test event → make sure you got a 2xx → switch the webhook back on. What you missed while it was off is read through the v1 API — v1 has no manual redelivery.

Pause is not the same as disabled

The Pause button differs from auto-disabling:

  • while a webhook is paused, new events are not queued at all — after you resume, you get only what happens from that point on;
  • deliveries already queued when you paused go out after you resume;
  • a test event can be sent while paused — that's the intended way to verify a fix.

Inactive subscription

If the organization's trial has ended and the subscription is inactive, push stops along with the rest of the paid functionality: the delivery is postponed by an hour and retried, the attempt is not consumed, and the endpoint is not disabled. Once payment goes through, deliveries resume on their own within the hour. Such rows are marked subscription_inactive in the log.

Delivery log

  • Kept for 30 days, together with the event body — exactly what was sent to your URL.
  • Below the cards, the Recent deliveries block shows the 25 most recent deliveries across all of the organization's webhooks: time, event type, response code, attempt count, body and attempt history. The limit is shared: with three endpoints that is 25 rows in total, not 25 per endpoint.
  • The first 512 bytes of the response are stored — enough to see the error text your server returned.

Codes in the "error" column

Code Meaning
http_<code> the server replied non-2xx (for example http_500)
redirect_not_allowed a 3xx response; redirects are not supported
gone a 410 response; the endpoint was disabled
timeout no response within 15 seconds
network_<code> a Node network error (network_ECONNREFUSED, network_EPROTO, …)
dns_failed the hostname did not resolve within the allotted time
private_address the URL resolves to a private or reserved range
not_https, port_not_allowed, userinfo_not_allowed, fragment_not_allowed, invalid_url, url_too_long, url_too_short the URL failed the shape checks before sending
subscription_inactive the organization's subscription is inactive, the delivery is postponed by an hour
endpoint_disabled the delivery was dropped when the endpoint was auto-disabled
lease_expired the delivery got stuck (an invocation failure) and was picked up by a housekeeping process

How to avoid retries

  • Reply 2xx before doing heavy work: accept the event, verify the signature, enqueue it, reply 200.
  • Don't reply 3xx: a load balancer redirecting httphttps or /hook/hook/ breaks delivery. Register the final URL.
  • Return 410 only when the URL is dead for good: that code disables the webhook on the first hit.
  • Under temporary load, reply 429 or 503 with Retry-After — that way you control the pace without losing events.

Next

Was this article helpful?