Add acknowledgment timeout guidance for webhook deliveries
States that the wait for a 2xx is server-defined rather than a protocol parameter, with ~5 seconds noted as a common choice, and that endpoints should durably accept the event, ack quickly, and process asynchronously instead of holding the delivery request open.
This commit is contained in:
parent
905ade36a2
commit
b506e34788
1 changed files with 1 additions and 1 deletions
|
|
@ -427,7 +427,7 @@ X-MCP-Subscription-Id: sub_a3f1c8e2b0d49f7e
|
|||
- `eventId` in the body enables idempotent processing. The receiver SHOULD deduplicate by this value.
|
||||
- `cursor` in the body is a **safe-to-persist watermark**: it represents a position such that every event at or before it has been acknowledged by the endpoint or abandoned by the server. The server computes this from its in-memory retry queue — it does not include `cursor_N` in event N's payload until events at positions `< N` have been acked or given up on. Computing this requires the server to hold, per active subscription, the upstream position and ack status of in-flight events; this is in-memory TTL-scoped state (lost on restart, reconstructed by client refresh), but it is per-subscription bookkeeping the SDK must implement. The client persists every cursor it receives; the most recently received value is always safe to supply on refresh. The server MAY send `cursor: null` for event types that do not support replay (see *Cursor Lifecycle*); in that case there is no recovery point to persist. The endpoint MUST make `cursor` and `eventId` available to the consuming client by whatever channel it uses to forward events; cursor-based recovery on resubscribe depends on the client receiving and persisting this value.
|
||||
- **Delivery model.** The server retries each event independently with exponential backoff on non-`2xx` responses. This matches the dominant webhook convention (Stripe, GitHub, Shopify, the Standard Webhooks spec). Concurrent deliveries and retries may therefore arrive out of order; the receiver uses `eventId` for deduplication and `timestamp` for ordering if needed. Because the payload `cursor` is a watermark (not the event's own position), out-of-order arrival does not cause the client to persist an unsafe cursor. Retries are bounded: servers SHOULD cap both the attempt count and the elapsed retry window (for example, 3–5 attempts spread over no more than 10–15 minutes) rather than retrying indefinitely; an event whose retries are exhausted is abandoned for watermark purposes (see the `cursor` note above) and, where the upstream is durable, recoverable via cursor replay. A receiver that intentionally rejects a delivery and does not want it retried — for example because it recognises the event as stale or already processed — responds `410 Gone`; the server MUST treat `410` as a non-retryable failure for that delivery (like `413`, see *Delivery profile*), without affecting the subscription itself.
|
||||
- **Acknowledgement semantics.** A `2xx` response from the webhook endpoint signals that the event has been accepted and the server need not retry it. The endpoint SHOULD NOT return `2xx` until the event has been durably persisted or forwarded — an endpoint that ACKs and then loses the event leaves recovery dependent on the client's last-persisted cursor, which may predate the lost event. At-least-once delivery in webhook mode holds between server and endpoint; end-to-end delivery to the agent depends on the endpoint honouring this contract.
|
||||
- **Acknowledgement semantics.** A `2xx` response from the webhook endpoint signals that the event has been accepted and the server need not retry it. The endpoint SHOULD NOT return `2xx` until the event has been durably persisted or forwarded — an endpoint that ACKs and then loses the event leaves recovery dependent on the client's last-persisted cursor, which may predate the lost event. At-least-once delivery in webhook mode holds between server and endpoint; end-to-end delivery to the agent depends on the endpoint honouring this contract. How long the server waits for that response before treating the attempt as failed (`lastError: "timeout"`) and retrying is a server implementation detail, not a protocol parameter — a timeout on the order of 5 seconds is common. Endpoints SHOULD therefore respond quickly: durably accept the event (persist or enqueue it), return `2xx`, and do any heavier processing asynchronously rather than holding the delivery request open.
|
||||
- **Subscribe/delivery race.** Because the secret is client-supplied, the receiver can verify the very first delivery — there is no window where a delivery arrives before the receiver knows the secret. A receiver that gets a delivery for an `id` it has not yet been told to route (e.g., the subscribe response has not propagated to the gateway) SHOULD return a retryable status (`503` or `425 Too Early`); the server's retry/backoff redelivers once the receiver is ready. `eventId` deduplication and cursor replay make this safe.
|
||||
- After repeated failures (server-defined threshold), the server MAY suspend delivery (`deliveryStatus.active: false`). A subsequent successful refresh reactivates it (sets `active: true`) and the server resumes retrying pending events; if the client never refreshes, the subscription expires naturally at TTL. A no-expiry subscription has no such backstop: the server MAY drop it after sustained delivery failure (server-defined window) and SHOULD attempt a `terminated` envelope when it does (see *Subscription TTL*).
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue