The IdempotencyKey in the EventBridge detail is actually just the event ID, which should be unique, but NICE CXone sometimes re-emits events during internal retries or state reconciliations. If you’re seeing duplicates with the same IdempotencyKey, that’s a platform bug you need to ticket. But more often, people hit this because they’re looking at the wrong level of granularity or the event is being triggered by multiple sources (like a transfer + start).
Wait, let me re-read your . You mentioned the key is unique per event instance. If the key is identical, your Lambda deduplication logic is likely failing silently, or SQS is delivering it twice before the visibility timeout clears.
Here’s the thing about EventBridge and SQS: SQS doesn’t guarantee exactly-once delivery by default unless you’re using FIFO queues. If you’re on Standard SQS, you will get duplicates. The IdempotencyKey is your safety net, but only if your Lambda actually checks it against a persistent store (like DynamoDB) or uses AWS Lambda Powertools.
If you’re using Powertools, make sure you’re decorating the handler correctly. A common mistake is putting the decorator on the wrong function or not handling the IdempotencyItemAlreadyExists exception properly.
import json
import boto3
from aws_lambda_powertools.utilities.idempotency import (
idempotent,
DynamoDBPersistenceLayer,
)
from aws_lambda_powertools.utilities.idempotency.serialization.json import JsonSerializer
# Initialize persistence layer
persistence_layer = DynamoDBPersistenceLayer(table_name="cxone-idempotency-store",
expiration=3600 * 24, # 1 day retention
serializer=JsonSerializer())
@idempotent(persistence_store=persistence_layer, key="detail.eventType") # Use a composite key if needed
def lambda_handler(event, context):
# This function will only execute once per unique key
detail = event.get("detail", {})
conversation_id = detail.get("conversation", {}).get("id")
if not conversation_id:
raise ValueError("Missing conversation ID")
# Your actual processing logic here
print(f"Processing conversation start: {conversation_id}")
return {
"statusCode": 200,
"body": json.dumps({"message": "Event processed"})
}
If you aren’t using Powertools, you’re probably doing a manual DynamoDB put with condition_expression="attribute_not_exists(IdempotencyKey)". That works, but it’s prone to race conditions if you don’t handle the ConditionalCheckFailedException correctly.
Also, check your EventBridge rule. Are you filtering on detail-type? If you’re catching all events and filtering in code, you might be picking up noise.
One more thing: CXone EventBridge events for conversation.start can fire multiple times if the conversation is routed across multiple queues or if there’s a wrap-up code change that triggers a state transition. Make sure you’re actually looking at the initial start event and not a state change that looks like a start.
Check the detail.conversation.routingState in the payload. If it’s queued vs connected, that might be your duplicate source.
Here’s a quick check you can run in your Lambda to log the raw event structure before deduplication:
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
# Log the full event for debugging duplicates
logger.info(f"Raw Event: {event}")
# Extract the IdempotencyKey
idempotency_key = event.get("id") # EventBridge puts the unique ID at the root
logger.info(f"IdempotencyKey: {idempotency_key}")
# ... rest of your code
If the id at the root of the EventBridge event is the same, then SQS is the culprit. Standard SQS guarantees at-least-once delivery. You must implement deduplication in your consumer. The IdempotencyKey field in the detail object is just metadata from NICE, it’s not the SQS message ID.
Don’t rely on the NICE IdempotencyKey field in the detail object for SQS deduplication. Use the EventBridge id field at the root of the event object. That’s the globally unique identifier for that specific event emission.
If you’re still seeing duplicates with unique EventBridge IDs, then your Lambda is being invoked twice for the same message. Check your SQS visibility timeout. If your Lambda takes longer than the timeout, SQS makes the message visible again, and another instance picks it up. Increase the visibility timeout to match your Lambda’s timeout + buffer.
resource "aws_sqs_queue" "cxone_events" {
name = "cxone-conversation-events"
visibility_timeout_seconds = 300 # Increase if your Lambda takes longer than 5 mins
message_retention_seconds = 345600
redrive_policy = jsonencode({
deadLetterTargetArn = aws_sqs_queue.dead_letter.arn
maxReceiveCount = 3
})
}
If you increase the visibility timeout and still see duplicates, then your Lambda is throwing an error mid-execution, causing SQS to redrive the message. Check your CloudWatch Logs for errors.
Also, make sure you’re acknowledging the message in SQS. If you’re using the SQS event source mapping in Lambda, it handles deletion automatically. If you’re polling SQS manually, you need to delete the message after processing.
This is usually a visibility timeout issue. Bump it up and watch the CloudWatch metrics for NumberOfMessagesSent vs ApproximateNumberOfMessagesVisible. If visible messages drop to zero after processing, you’re good. If they stay high, you’re not deleting them.
Let me know if you’re using Standard or FIFO SQS. FIFO solves this but adds cost and complexity with content-based deduplication.