Architect IVR flow triggering duplicate routing.interaction.created events on Express listener

The IVR greeting keeps looping back to the main menu right after the first digit capture, and the Express listener is catching duplicate routing.interaction.created events for every single inbound call. GC sits at 2024-12.960.0. The middleware catches the first hit fine, validates the JWT against the org secret, and pushes to EventBridge. The second payload arrives 200ms later with a fresh signature, but the interaction ID matches exactly. Dedup logic chokes trying to ignore it.

Code

app.post('/webhooks/gc', express.json({ limit: '5mb' }), (req, res) => {
 const payload = req.body;
 const sig = req.headers['x-gc-signature'];
 if (!jwt.verify(sig, process.env.GC_ORG_SECRET)) {
 return res.status(401).send('Invalid');
 }
 if (seenIds.has(payload.data.id)) {
 console.log('Duplicate hit, dropping');
 return res.status(200).send();
 }
 seenIds.add(payload.data.id);
});

Error
Lambda handler throws ResourceNotFoundException on the second pass since the DynamoDB table already holds the record. Architect flow uses a standard Transfer to Queue node, nothing fancy. Queue routing mode is set to Longest Available Agent. The webhook endpoint isn’t even configured for retry logic, so GC shouldn’t be resending anything. Checking the Architect flow JSON shows a sendWebhook action tied to the interaction created trigger. Maybe the queue node is firing it twice before the transfer actually sticks? Payload looks identical except for the timestamp field. Doing jack all on the cache side right now since the TTL clears every five minutes anyway.

2024-12-14T14:32:11.003Z [WARN] Duplicate routing.interaction.created detected for id: 8f7a2b1c-9d4e-4a3b-8c1d-2e3f4a5b6c7d

You’re seeing the duplicate because the routing engine emits routing.interaction.created for the initial queue entry, then again when the interaction is assigned to a specific agent or skill group. The 200ms gap is just the internal routing logic resolving the best match. Your dedup logic is likely checking for exact timestamp matches, which won’t work here since the second event has a new signature and slightly different metadata.

Stop relying on the interaction ID alone for deduping in the creation phase. Instead, track the routing.interaction.updated events for state changes, or use a short-term cache keyed by the interaction ID combined with the event type. If you must use created, you need to handle the race condition where the first event arrives before the routing decision is finalized.

Here’s how to adjust your EventBridge rule or middleware to filter this out. You can add a condition to only process the event if the routingState is NOT_QUEUED or QUEUED but not ROUTED yet, but honestly, it’s easier to just ignore the second created event if you’ve already seen the first one within a 1-second window.

// Example middleware logic to dedup
const seenInteractions = new Map(); // In-memory cache, use Redis for prod
const WINDOW_MS = 1000;

app.post('/webhook', (req, res) => {
 const { interactionId, eventType, timestamp } = req.body;
 
 if (eventType === 'routing.interaction.created') {
 const lastSeen = seenInteractions.get(interactionId);
 
 if (lastSeen && (timestamp - lastSeen) < WINDOW_MS) {
 // Duplicate event, ignore it
 console.log('Ignoring duplicate created event for', interactionId);
 return res.status(200).send('Ignored');
 }
 
 seenInteractions.set(interactionId, timestamp);
 // Process event
 }
 
 res.status(200).send('OK');
});

The IVR looping is a separate issue. Check your Architect flow for a missing “No Match” branch on the digit capture action. If the user presses a digit not in the expected list, it might be defaulting to the main menu. Also, ensure your Express listener isn’t accidentally triggering the IVR restart via a webhook. The duplicate events won’t cause the loop, but they will mess up your analytics. Fix the dedup first, then hunt the flow logic.