duplicate_event
hooksentinel error duplicate_event — the event ID was already marked processed by the idempotency store. This is expected behavior, not a failure.
Summary
| Field | Value |
|---|---|
| Code | duplicate_event |
| HTTP status | 200 |
| Retryable | No |
What caused it
The idempotency store's has() check found this event ID already marked processed, so hooksentinel skipped onEvent and returned a 200 immediately. This is the deduplication feature working as intended, not an error condition — it's included in the error code list because it's still a distinct outcome your onError handler can observe, not because something went wrong.
Providers deliver at least once by design. A checkout.session.completed event can arrive twice because:
- Your server didn't respond within the provider's timeout window on the first delivery (even if
onEventactually succeeded — the ack just arrived too late). - The provider's own retry logic redelivers after a transient failure on their end.
- You manually resent a test event from a provider's dashboard.
Returning 200 for a duplicate (rather than an error status) matters: if hooksentinel returned a 4xx or 5xx for a duplicate, the provider would interpret that as "still failing" and keep retrying indefinitely, when the event was in fact already handled successfully the first time.
When to actually investigate
A steady low rate of duplicates is normal and doesn't need action. Investigate if you see:
- A high duplicate rate for one specific event type. This usually means
onEventfor that type is slow enough to regularly miss the provider's ack timeout — see the BullMQ fast-ack pattern to move slow work off the request path. - Duplicates appearing immediately after a deploy. If you're using
memoryStore(), a process restart clears all dedup state — every in-flight or recently-processed event looks "new" again after a redeploy. Switch toredisStore()orprismaStore()for anything beyond local development; see Idempotency.
Observing duplicates
onError: async (error, ctx) => {
if (error.code === 'duplicate_event') {
metrics.increment('webhook.duplicate', { provider: ctx.provider });
}
},Nothing else needs to be done here by default — hooksentinel already returns the correct 200 response.
Last updated on
parse_error
hooksentinel error parse_error — the request body passed signature verification but isn't valid JSON, or doesn't match the provider's expected event shape.
idempotency_store_error
hooksentinel error idempotency_store_error — the configured idempotency store (Redis, Prisma, etc.) was unreachable or errored while checking or marking an event.