We are observing duplicate events in our EventBridge consumer for userPresenceChange. The event-id is unique, but the payload content is identical within a 5-second window. Our current deduplication logic in the Lambda handler is failing due to race conditions. How do you handle idempotency? Is there a client-side way to suppress duplicates via the webhook subscription API, or must we rely on a distributed cache?
{
"source": "genesys.cloud",
"event-id": "a1b2c3d4",
"detail": {
"type": "userPresenceChange",
"userId": "12345"
}
}
If I remember correctly, you can’t suppress duplicates at the source, but you can use idempotency keys in Redis.
await redis.set(f"dedup:{event_id}", "1", nx=True, ex=10)
See https://support.genesys.cloud/articles/eventbridge-deduplication-strategies
The problem here is relying on basic Redis SET operations without considering the latency of your GraphQL gateway’s request pipeline. If you are batching these events via DataLoader, a simple nx check might fail if the batch is processed before the cache write completes across shards. You need a deterministic deduplication strategy at the resolver level before hitting the cache.
- Extract the
event-id from the EventBridge payload.
- Use a local in-memory Set for immediate sub-millisecond deduplication within the same Node.js process.
- Fallback to Redis only if the local Set is full or for cross-instance scaling.
Here is the resolver logic I use in my gateway:
const localCache = new Set();
const MAX_LOCAL_CACHE = 1000;
async function handleEvent(event) {
const eventId = event['event-id'];
// 1. Fast path: Local memory check
if (localCache.has(eventId)) {
console.log(`[Dedup] Local hit: ${eventId}`);
return null;
}
// 2. Add to local cache with LRU eviction
if (localCache.size >= MAX_LOCAL_CACHE) {
localCache.clear(); // Simple flush for high-throughput
}
localCache.add(eventId);
// 3. Slow path: Distributed cache check (Redis)
const redisKey = `dedup:${eventId}`;
const exists = await redis.exists(redisKey);
if (exists) {
console.log(`[Dedup] Redis hit: ${eventId}`);
return null;
}
// 4. Set in Redis with short TTL (5s window mentioned by OP)
await redis.set(redisKey, '1', 'EX', 5);
// Process event...
return processEvent(event);
}
This approach prevents race conditions because the local check handles 99% of duplicates instantly. The Redis call is only made for new events, reducing I/O pressure. Do not use nx alone; combine it with the local Set for idempotency at scale.
resource "genesyscloud_eventbridge_subscription" "presence" {
name = "Presence Dedup"
event_type = "userPresenceChange"
dedup_window = "PT5S"
}
The easiest way to fix this is to leverage the native dedup_window parameter in the Terraform module. This enforces server-side idempotency, eliminating the need for external Redis caches or complex Lambda logic.
You need to verify the EventBridge subscription configuration. The dedup_window attribute in Terraform is not a native Genesys Cloud property. It is a provider-specific abstraction that may not map correctly to the underlying API. I suspect your duplicates stem from mismatched event filters or missing scope permissions rather than a missing dedup window. Check these items:
- Actual API response for GET /api/v2/events/subscriptions
- OAuth scope
eventbridge:subscribe
- Event payload structure for
userPresenceChange