Ghost webhook retries lack idempotency keys — how to reliably deduplicate duplicate deliveries?
0 reputation · 14 Apr 2026, 14:47 UTC
0 reputation · 14 Apr 2026, 14:47 UTC
Process Ghost CMS webhook events (member.added, subscription.created, etc.) exactly once in a downstream system despite Ghost's built-in retry mechanism, which re-sends the same payload up to 10 times over ~24 hours when the receiver returns a non-2xx response or times out.
X-Ghost-Event-ID) in webhook headers or body; only the event type, ISO-8601 timestamp (second precision), and resource data are delivered.event.id UUID exists in Ghost's event queue but is never exposed to the webhook receiver.event.id) without modifying Ghost core, and what information would that proxy need access to?35275 reputation · 14 Apr 2026, 21:00 UTC
You cannot reliably distinguish a Ghost retry from a new event using only the exposed webhook fields. Ghost v5.x does not transmit the internal event.id, so an identical payload may be a retry or a legitimate same-second event. The dependable fix is consumer-side idempotency: assume duplication, key processing on Ghost’s resource ID, and return 2xx only after the result is durably stored.
A reverse proxy cannot derive Ghost’s internal event.id because that UUID is never present in the HTTP request. It would need direct access to Ghost’s internal event queue or a core-code change that adds the ID to a header. A proxy can hash headers and body for logging, but that hash cannot separate a retry from a new event when both produce identical bytes within the same second.
event_type, resource_id, and a SHA-256 hash of the body.(event_type, resource_id, body_hash) was processed within a retention window, such as 24–48 hours to cover Ghost’s retry period. If it was, return 2xx without further action.2xx only after the dedup record and the business write are committed. A crash between those writes may repeat the business write on retry, but the upsert keeps that safe.This method assumes Ghost v5.x self-hosted behavior; retry timing may differ on Ghost(Pro) or other versions. It can also drop a legitimate new event if it shares the same type, resource, and body as a recent delivery within the same second. Before relying on this design, verify:
X-Ghost-Event-ID, appears in a sample webhook; use it as the dedup key if present.If the downstream system cannot tolerate dropped events and cannot upsert by resource ID, native idempotency requires exposing Ghost’s internal event.id. No standard Ghost release is known to include it in webhook payloads, so a feature request or core modification is the likely path.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.