Queue & Delivery

Nexus Broker provides a FIFO queue with explicit acknowledgment for reliable webhook delivery. This page explains how messages flow through the queue, how locking works, and how to handle failures.

Queue Model

Each webhook has its own independent FIFO queue. Messages are assigned a monotonically increasing sequence number when ingested, guaranteeing strict ordering within a webhook. There is no deduplication — every POST to the ingestion endpoint creates a new message, even if the payload is identical.

  • FIFO ordering — Messages are dequeued in the order they were ingested, determined by sequence number
  • Explicit acknowledgment — Messages must be explicitly committed or errored. There is no auto-acknowledgment.
  • No duplicates — Each message has a unique ID. Dequeue returns at most one message per request. The same message cannot be dequeued concurrently.
  • Per-webhook isolation — Each webhook's queue is independent. Activity on one webhook does not affect another.

Message Lifecycle

Every message moves through a well-defined state machine:

queued
dequeue
locked
commit
committed
terminal
error
queued
retry
max retries
dead
terminal
lock expires
queued
retry
TTL expires
purged
terminal

States

StateDescriptionTransitions
queuedMessage is encrypted and waiting to be consumed→ locked (dequeue)
lockedMessage has been dequeued and is being processed→ committed, queued, dead
committedSuccessfully processed (terminal)None
deadFailed after max retries (terminal, in DLQ)→ queued (retry from DLQ)
purgedExpired TTL, auto-deleted (terminal)None

Dequeue & Locking

When you call the dequeue endpoint, the system atomically finds the earliest queued message, sets its status to "locked", assigns a lock expiration timestamp, and returns it. This operation is serialized per webhook to prevent two consumers from receiving the same message.

  • Lock timeout — Configurable per webhook (default: 300 seconds). If the consumer does not commit or error the message within this window, the lock expires and the message returns to "queued" state.
  • Lock expiration is automatic — A scheduled job runs every 60 seconds to find expired locks and release them back to the queue.
  • Empty queue — If no messages are available, dequeue returns HTTP 204 with no body. Your consumer should wait briefly before polling again.
  • Visibility timeout — The lock timeout effectively acts as a visibility timeout. While a message is locked, it is invisible to other dequeue requests.

Commit (ACK)

After successfully processing a message, call the commit endpoint to acknowledge it. The message transitions to "committed" state and is no longer available for dequeue. Committed messages are retained for audit purposes until their TTL expires or they are garbage-collected.

curl -X POST https://nexus-broker.dev/api/webhooks/wh_a1b2c3/messages/msg_x9y8z7/commit \
  -H "x-api-key: nxb_Kj3m9x...Rq7w"

Committing a message that is not in "locked" state returns a 409 Conflict error. Each message can only be committed once.

Error (NACK)

If processing fails, call the error endpoint to release the message back to the queue. Include an optional reason string for debugging. The message's retry counter is incremented.

curl -X POST https://nexus-broker.dev/api/webhooks/wh_a1b2c3/messages/msg_x9y8z7/error \
  -H "x-api-key: nxb_Kj3m9x...Rq7w" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Downstream service returned 500"}'

The error response tells you whether the message was requeued or sent to the dead letter queue:

{
  "id": "msg_x9y8z7",
  "status": "queued",
  "retryCount": 3,
  "maxRetries": 10
}

Dead Letter Queue

When a message's retry count reaches the maximum (configurable per webhook, default depends on plan), it is moved to the dead letter queue. Dead letters are not returned by dequeue and must be handled explicitly.

Inspection

Use the errors endpoint to list dead letters. The response includes the last error reason, retry count, and timestamps:

curl https://nexus-broker.dev/api/webhooks/wh_a1b2c3/errors \
  -H "x-api-key: nxb_Kj3m9x...Rq7w"

Retry from DLQ

Retry a dead letter to move it back to the active queue with a reset retry counter:

curl -X POST https://nexus-broker.dev/api/webhooks/wh_a1b2c3/errors/msg_p3q4r5/retry \
  -H "x-api-key: nxb_Kj3m9x...Rq7w"

Discard from DLQ

Permanently remove a dead letter message when it is no longer needed:

curl -X DELETE https://nexus-broker.dev/api/webhooks/wh_a1b2c3/errors/msg_p3q4r5 \
  -H "x-api-key: nxb_Kj3m9x...Rq7w"

Message TTL

Every message has a time-to-live (TTL) that determines how long it remains in the system. The TTL is configurable per webhook and defaults to the plan limit. When a message's TTL expires, a scheduled job automatically purges it regardless of its state (queued, locked, or dead).

PlanDefault TTLConfigurable
Free7 daysNo
Pro30 days1–30 days
Enterprise90 days1–90 days

The purge job runs every 15 minutes. Purged messages are permanently deleted and cannot be recovered.

Ordering Guarantees

Nexus Broker provides FIFO ordering within a single webhook:

  • Messages are dequeued in sequence number order within a webhook
  • If a message is errored and returns to the queue, it is requeued at its original sequence position, not at the end
  • A locked message blocks dequeue of subsequent messages — if message 5 is locked, dequeue will not return message 6 until message 5 is committed or its lock expires
  • There is no global ordering across webhooks — each webhook's queue is independent
  • If you need to process messages from multiple webhooks in a specific order, coordinate on the consumer side using timestamps

Rate Limits

Rate limits are applied to queue operations to prevent abuse and ensure fair resource allocation:

OperationFreeProEnterprise
Ingestion (per webhook)10 req/s100 req/s1,000 req/s
Dequeue (per webhook)5 req/s50 req/s500 req/s
Commit/Error (per webhook)10 req/s100 req/s1,000 req/s

Best Practices

Design for Idempotency

Messages may be delivered more than once in edge cases: lock expiration, network failures after commit, or DLQ retries. Design your consumer to handle duplicate messages gracefully. Use the message ID or a payload field as an idempotency key.

Tune Lock Timeout

Set the lock timeout slightly longer than your maximum expected processing time. If processing typically takes 30 seconds, set the timeout to 60 seconds. Setting it too low causes unnecessary retries; setting it too high delays redelivery when a consumer crashes.

Implement Retry with Backoff

When a message fails, consider whether it is a transient error (network timeout, rate limit) or a permanent error (invalid data, missing resource). For transient errors, error the message and let the queue retry. For permanent errors, commit the message and log the issue to avoid filling the DLQ.

Monitor the Dead Letter Queue

Regularly inspect the DLQ to catch systemic issues. A growing DLQ often indicates a downstream service problem. Set up alerting on DLQ depth — if it exceeds a threshold, investigate the root cause before retrying messages.

Use Separate Webhooks for Separate Concerns

Each webhook has an independent FIFO queue. If you need to process different event types with different consumers or at different priorities, create separate webhooks for each. This prevents one slow consumer from blocking others.