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.
Summary
| Field | Value |
|---|---|
| Code | idempotency_store_error |
| HTTP status | 500 |
| Retryable | Yes |
What caused it
has() or markProcessed() on your configured IdempotencyStore threw — most commonly because the backing store (Redis, your Prisma-managed database) was unreachable, timed out, or returned an error hooksentinel didn't expect. Since hooksentinel can't safely determine whether this event has already been processed, it fails the request rather than guessing — guessing "not a duplicate" risks double-processing, and guessing "is a duplicate" risks silently dropping a real event.
This code is retryable (500) specifically because the failure is about your infrastructure, not the request — the same event redelivered after the store recovers should succeed normally, and the provider's default retry behavior is exactly what you want here.
Common causes:
- Redis connection issues. Network partition, Redis instance restarted or exhausted its connection limit, wrong
REDIS_URL, or TLS misconfiguration against a managed Redis provider. - Prisma / database issues. Connection pool exhaustion, database restart, migration not yet applied (the
processedWebhookEventtable/model doesn't exist yet in the current environment). - Serverless cold-start races. In Lambda or edge environments, a store client created per-invocation may attempt to connect before its underlying network path (e.g. a VPC ENI) is ready.
The fix
Check store connectivity and credentials first — this resolves the large majority of occurrences:
redis-cli -u "$REDIS_URL" ping # expect PONG// confirm the Prisma model referenced actually exists and migrations are applied
npx prisma migrate statusAdd explicit logging in onError so store outages are visible in your monitoring rather than only showing up as elevated 500s from your webhook endpoint:
onError: async (error, ctx) => {
if (error.code === 'idempotency_store_error') {
logger.error('idempotency store unreachable', {
provider: ctx.provider,
eventId: ctx.eventId,
cause: error.cause,
});
alerting.page('webhook-idempotency-store-down');
}
},For Redis specifically, prefer passing an existing, already-managed ioredis client (with its own reconnection/backoff strategy) over letting redisStore() open a fresh connection per handler instance:
import { redisStore } from '@hooksentinel/core/stores';
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!, {
maxRetriesPerRequest: 3,
retryStrategy: (times) => Math.min(times * 200, 2000),
});
redisStore({ client: redis });If store outages are rare but your application can tolerate occasional double-processing more than it can tolerate rejecting legitimate webhooks during a brief outage, you can catch this specific code in onError and choose to proceed without deduplication as a deliberate fallback — but do this consciously, since it trades correctness for availability.
Last updated on
duplicate_event
hooksentinel error duplicate_event — the event ID was already marked processed by the idempotency store. This is expected behavior, not a failure.
handler_error
hooksentinel error handler_error — your onEvent callback threw an uncaught error. hooksentinel responds 500 so the provider retries delivery.