Retries, idempotency, and failures
Webhook delivery is a durable transport operation with explicit outcome, not a fire-and-forget HTTP call. Keep a stable event/delivery identity and make the receiver idempotent before performing side effects.
Receiver acknowledgement
Return a success response only after the receiver has durably accepted the verified envelope for processing. Return a retryable status for temporary unavailability and a non-retryable status for a permanently invalid contract. Do not return success for a signature, replay, scope, or schema rejection.
HTTP/1.1 202 Accepted
X-Correlation-Id: <YOUR_CORRELATION_ID>
For a retryable response, include a bounded Retry-After only when the receiver can accept work later. Qanivo’s worker policy remains authoritative for retry count, backoff, and terminal disablement.
Idempotent processing
Store eventId/deliveryId and contract version in a durable idempotency record before applying a side effect. Replaying the same verified delivery returns the prior logical result. Reusing an idempotency key with different body or scope is a conflict, not a new operation.
export async function receive(rawBody: Buffer, headers: Record<string, string | undefined>) {
const envelope = verifyRawWebhook(rawBody, headers);
const key = `${headers['x-qanivo-delivery-id']}:${envelope.eventType}`;
const prior = await idempotencyStore.get(key);
if (prior) return prior.response;
const response = await enqueueOnce(key, envelope);
await idempotencyStore.put(key, response);
return response;
}
Outcome model
Use normalized Pending, Accepted, Retrying, Delivered, TerminalFailure, Disabled, and Unsupported outcomes only where the applicable surface defines them. Preserve attempt number, HTTP class, safe reason, next retry time, event/delivery identity, and correlation ID. Dead-letter or terminal storage must be inspectable through permissioned operational evidence without exposing raw secrets or full personal payloads.
Transient transport failure must not be mistaken for business rejection. Conversely, an endpoint that consistently rejects authenticity, schema, authorization, or payload policy should not be retried indefinitely.