How-to guide

Reliable webhook delivery

Networks fail, receivers restart, endpoints rate-limit. Queuey turns that into a delivery you can trust: retries with backoff, idempotency so a retry is never a duplicate, a safe Hold-on-timeout default, and a dead-letter queue for whatever truly can't be delivered.

Retries & backoff

Each queue resolves a retry policy. What gets retried is decided by the failure class, not by a counter: a receiver that rejected the event goes straight to the dead-letter queue, while a target that is down is held and probed until it recovers. dlq.afterAttempts is the one attempt budget you set. Retries use exponential backoff with jitter, and what counts as retryable is driven by failure classes (server errors, unavailable, rate-limited) with per-receiver HTTP-status overrides:

retry policy
Retry policy (resolved system → tenant → queue)

  backoff:          exponential, base 1s, max 5m, jitter: full
  retryOn:          failure classes (server error, unavailable, rate-limited)
                    + httpStatus fallback / excluded
                    + network errors, timeouts
  timeoutBehavior:  Hold        # Retry | Hold (default) | Dlq
  dlq:              enabled, afterAttempts: 8

Idempotency

Send a producer Idempotency-Key and a duplicate retry within the dedup window short-circuits to the already-accepted event — you never double-enqueue:

ingress with Idempotency-Key
curl -X POST "$INGRESS/events/ten_yourTenant/orders" \
  -H "X-Api-Key: $KEY" \
  -H "Idempotency-Key: order-10042-created" \
  -H "Content-Type: application/json" \
  -d '{ "eventType": "order.created", "payload": { "id": "10042" } }'

On the way out, Queuey threads the same key (and an X-Queuey-Path for loop prevention) so an idempotent receiver can dedupe across delivery retries too:

outbound headers
POST /your/webhook HTTP/1.1
Idempotency-Key: order-10042-created
X-Queuey-Path: ten_yourTenant/orders
Content-Type: application/json

Safe-by-default on timeout

Hold, don't blindly retry
When a delivery times out with no response, Queuey doesn't know if the receiver applied the side effect. The default timeoutBehavior is Hold: pause the queue and open an issue rather than risk a duplicate. Switch to Retry only once you've asserted the endpoint is idempotent — or route timeouts straight to Dlq.

Dead-letter queue

When retries are exhausted (or after a configured attempt count), the event lands in the DLQ instead of disappearing. From the console you can inspect the failure, fix the target, and replay — single events or in bulk.

Related