Routing Cognigy Bot Intent Data to NICE CXone Dynamic Queues
What You Will Build
- This tutorial demonstrates how to capture intent confidence scores and entity data from a NICE Cognigy bot and use that data to dynamically route the conversation to the correct NICE CXone queue or skill group.
- It utilizes the NICE CXone REST API (specifically the Interaction and Routing APIs) and the Cognigy.AI SDK for Node.js.
- The implementation covers JavaScript/TypeScript for the Cognigy bot logic and Python for a backend middleware service that translates Cognigy payloads into CXone routing directives.
Prerequisites
- NICE CXone Account: An active tenant with access to the API.
- OAuth Credentials: A CXone OAuth client ID and secret with the
routing:queue:writeandinteraction:conversation:writescopes. - Cognigy.AI Project: An active project with a published bot.
- Node.js: Version 16 or higher for the Cognigy.AI SDK environment.
- Python: Version 3.9 or higher for the middleware service.
- Dependencies:
- Node.js:
@cognigy/sdk,axios - Python:
requests,python-dotenv
- Node.js:
Authentication Setup
NICE CXone uses OAuth 2.0 for API authentication. Because the Cognigy bot runs in a serverless environment or a separate container, it should not hold long-lived CXone tokens. Instead, a middleware service or a secure backend endpoint should handle token acquisition.
Below is a Python utility class to manage OAuth tokens. This class handles the initial grant and refreshes tokens before they expire to prevent 401 errors during high-volume routing.
import requests
import time
from typing import Optional
import os
class CxoneAuthManager:
def __init__(self, client_id: str, client_secret: str, base_url: str = "https://api.us-gov-1.niceincontact.com"):
self.client_id = client_id
self.client_secret = client_secret
self.base_url = base_url
self.token_endpoint = f"{base_url}/api/v2/oauth/token"
self.access_token: Optional[str] = None
self.token_expiry: float = 0
self.refresh_threshold = 60 # Refresh 60 seconds before expiry
def _get_token(self) -> dict:
"""Fetches a new OAuth token from CXone."""
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
"scope": "routing:queue:write interaction:conversation:write"
}
response = requests.post(self.token_endpoint, headers=headers, data=data)
response.raise_for_status()
return response.json()
def get_valid_token(self) -> str:
"""Returns a valid access token, refreshing if necessary."""
current_time = time.time()
# Check if token exists and is not expired (accounting for threshold)
if self.access_token and (current_time < self.token_expiry - self.refresh_threshold):
return self.access_token
# Fetch new token
token_data = self._get_token()
self.access_token = token_data["access_token"]
# CXone tokens typically expire in 3600 seconds (1 hour)
self.token_expiry = current_time + token_data.get("expires_in", 3600)
return self.access_token
# Example Usage
# auth = CxoneAuthManager(os.getenv("CXONE_CLIENT_ID"), os.getenv("CXONE_CLIENT_SECRET"))
# token = auth.get_valid_token()
Implementation
Step 1: Extracting Intent Data in Cognigy.AI
The first step is to structure the data leaving the Cognigy bot. When the bot determines the user’s intent (e.g., “BillingQuestion”, “TechnicalSupport”), it must pass this information to the external routing system. We do not route directly from Cognigy to CXone queues in this architecture; instead, Cognigy sends a payload to a middleware endpoint, which then interacts with CXone.
In your Cognigy.AI project, create a new Step called RouteToCXone. In the JavaScript code editor for this step, extract the intent and confidence score.
/**
* Cognigy.AI Step: RouteToCXone
* Extracts intent data and prepares payload for CXone middleware.
*/
module.exports = async ({ session, utils }) => {
try {
// 1. Retrieve the resolved intent from the session
const intent = session.getVariable("intent");
const confidence = session.getVariable("confidence");
// 2. Validate that an intent was successfully recognized
if (!intent || confidence < 0.7) {
// Fallback logic: send to general queue
session.setVariable("routingQueue", "GeneralSupport");
session.setVariable("routingReason", "LowConfidenceFallback");
return;
}
// 3. Map Cognigy Intents to CXone Queue Names/Skills
// This mapping should ideally be in a configuration file or database
const queueMapping = {
"BillingQuestion": "Billing_Tier1",
"TechnicalSupport": "Tech_Support_L2",
"SalesInquiry": "Sales_NewBusiness",
"AccountManagement": "Account_Mgmt"
};
const targetQueue = queueMapping[intent] || "GeneralSupport";
// 4. Set session variables for the webhook payload
session.setVariable("cxoneTargetQueue", targetQueue);
session.setVariable("cxoneIntent", intent);
session.setVariable("cxoneConfidence", confidence);
// 5. Prepare the payload for the HTTP request step
// We will use a subsequent HTTP Step to send this data
const payload = {
sessionId: session.id,
userId: session.user.id,
targetQueue: targetQueue,
intent: intent,
confidence: confidence,
timestamp: new Date().toISOString()
};
session.setVariable("routingPayload", JSON.stringify(payload));
} catch (error) {
console.error("Error preparing routing data:", error);
session.setVariable("cxoneTargetQueue", "ErrorHandling");
}
};
After this step, add an HTTP Request step in Cognigy.AI. Configure it to POST to your middleware endpoint (e.g., https://your-middleware.com/route). Set the body to {{routingPayload}} and Content-Type to application/json.
Step 2: Building the Middleware Router
The middleware receives the payload from Cognigy and translates it into a CXone API call. This service acts as the bridge, handling authentication and error retries.
Create a Python FastAPI application (or Flask/Express) to handle the incoming webhook.
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import requests
import logging
import os
# Import the AuthManager from Step 1
from cxone_auth import CxoneAuthManager
app = FastAPI()
auth_manager = CxoneAuthManager(
os.getenv("CXONE_CLIENT_ID"),
os.getenv("CXONE_CLIENT_SECRET")
)
class RoutingPayload(BaseModel):
sessionId: str
userId: str
targetQueue: str
intent: str
confidence: float
timestamp: str
@app.post("/route")
def route_interaction(payload: RoutingPayload):
"""
Receives intent data from Cognigy and updates the CXone interaction
to route to the specified queue.
"""
try:
# 1. Authenticate with CXone
token = auth_manager.get_valid_token()
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# 2. Identify the Interaction in CXone
# In a real scenario, you might link the Cognigy Session ID to
# a CXone Interaction ID via a database or header passed during IVR handoff.
# For this example, we assume the Cognigy bot is embedded in a CXone
# Web Chat or Voice channel where the Interaction ID is known or
# we are creating a new interaction context.
# NOTE: If this is a voice call, the Interaction ID is passed from the IVR.
# If this is Web Chat, the Interaction ID is generated by CXone.
# We will use a placeholder Interaction ID here.
# In production, retrieve this from a session store or request header.
interaction_id = payload.userId # Simplified for example
# 3. Prepare the Routing Directive
# We use the Interaction API to update the routing target.
# Endpoint: PATCH /api/v2/interactions/conversations/{interactionId}
# Construct the update payload
# We are updating the 'routing' object to specify the queue
update_payload = {
"routing": {
"queueId": payload.targetQueue,
# Note: In production, you should map Queue Name to Queue ID
# by first querying GET /api/v2/routing/queues/name/{name}
"skillRequirements": []
},
"metadata": {
"intent": payload.intent,
"confidence": payload.confidence
}
}
# 4. Execute the API Call
cxone_base = "https://api.us-gov-1.niceincontact.com" # Adjust for your region
url = f"{cxone_base}/api/v2/interactions/conversations/{interaction_id}"
response = requests.patch(url, json=update_payload, headers=headers)
if response.status_code == 200:
logging.info(f"Successfully routed session {payload.sessionId} to {payload.targetQueue}")
return {"status": "success", "queue": payload.targetQueue}
elif response.status_code == 404:
raise HTTPException(status_code=404, detail="Interaction not found in CXone")
elif response.status_code == 429:
# Handle Rate Limiting
logging.warning("CXone API Rate Limited. Retry logic should be implemented.")
raise HTTPException(status_code=503, detail="Service temporarily unavailable due to rate limits")
else:
raise HTTPException(status_code=response.status_code, detail=response.text)
except Exception as e:
logging.error(f"Routing failed for session {payload.sessionId}: {str(e)}")
raise HTTPException(status_code=500, detail="Internal Routing Error")
Step 3: Resolving Queue Names to IDs
CXone APIs generally require IDs (UUIDs) rather than names for routing directives. The previous step used a simplified queueId field. In production, you must resolve the queue name to its ID.
Add a helper function to resolve queue IDs. This should be cached to avoid excessive API calls.
from functools import lru_cache
import requests
@lru_cache(maxsize=128)
def get_queue_id_by_name(queue_name: str, auth_token: str) -> str:
"""
Resolves a CXone Queue Name to its UUID.
Uses LRU cache to prevent repeated API calls for the same queue.
"""
headers = {
"Authorization": f"Bearer {auth_token}",
"Content-Type": "application/json"
}
# CXone Endpoint: GET /api/v2/routing/queues/name/{name}
# URL encode the queue name
import urllib.parse
encoded_name = urllib.parse.quote(queue_name, safe='')
url = f"https://api.us-gov-1.niceincontact.com/api/v2/routing/queues/name/{encoded_name}"
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.json().get("id")
else:
raise Exception(f"Failed to resolve queue ID for '{queue_name}'. Status: {response.status_code}")
# Update the /route endpoint to use this helper:
# queue_id = get_queue_id_by_name(payload.targetQueue, token)
# update_payload["routing"]["queueId"] = queue_id
Complete Working Example
Below is the complete Python middleware service. It includes the auth manager, queue resolution, and the FastAPI router.
import os
import time
import requests
import logging
import urllib.parse
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from functools import lru_cache
# Configure Logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# --- Configuration ---
CXONE_CLIENT_ID = os.getenv("CXONE_CLIENT_ID")
CXONE_CLIENT_SECRET = os.getenv("CXONE_CLIENT_SECRET")
CXONE_BASE_URL = os.getenv("CXONE_BASE_URL", "https://api.us-gov-1.niceincontact.com")
app = FastAPI()
# --- Authentication Module ---
class CxoneAuthManager:
def __init__(self, client_id: str, client_secret: str, base_url: str):
self.client_id = client_id
self.client_secret = client_secret
self.base_url = base_url
self.token_endpoint = f"{base_url}/api/v2/oauth/token"
self.access_token = None
self.token_expiry = 0
self.refresh_threshold = 60
def _get_token(self) -> dict:
headers = {"Content-Type": "application/x-www-form-urlencoded"}
data = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
"scope": "routing:queue:write interaction:conversation:write routing:queue:read"
}
response = requests.post(self.token_endpoint, headers=headers, data=data)
response.raise_for_status()
return response.json()
def get_valid_token(self) -> str:
current_time = time.time()
if self.access_token and (current_time < self.token_expiry - self.refresh_threshold):
return self.access_token
token_data = self._get_token()
self.access_token = token_data["access_token"]
self.token_expiry = current_time + token_data.get("expires_in", 3600)
return self.access_token
auth_manager = CxoneAuthManager(CXONE_CLIENT_ID, CXONE_CLIENT_SECRET, CXONE_BASE_URL)
# --- Queue Resolution Module ---
@lru_cache(maxsize=128)
def get_queue_id_by_name(queue_name: str, auth_token: str) -> str:
headers = {"Authorization": f"Bearer {auth_token}"}
encoded_name = urllib.parse.quote(queue_name, safe='')
url = f"{CXONE_BASE_URL}/api/v2/routing/queues/name/{encoded_name}"
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.json().get("id")
else:
raise Exception(f"Queue '{queue_name}' not found. Status: {response.status_code}")
# --- Data Models ---
class RoutingPayload(BaseModel):
sessionId: str
userId: str
targetQueue: str
intent: str
confidence: float
timestamp: str
# --- API Endpoints ---
@app.post("/route")
def route_interaction(payload: RoutingPayload):
try:
token = auth_manager.get_valid_token()
# 1. Resolve Queue Name to ID
try:
queue_id = get_queue_id_by_name(payload.targetQueue, token)
except Exception as e:
logger.error(f"Queue resolution failed: {e}")
raise HTTPException(status_code=400, detail=f"Invalid Queue Name: {payload.targetQueue}")
# 2. Prepare Routing Update
# Note: In a real Voice scenario, you would have the Interaction ID from the IVR.
# Here we assume the userId is linked to the Interaction ID for demonstration.
interaction_id = payload.userId
update_payload = {
"routing": {
"queueId": queue_id
},
"metadata": {
"cognigyIntent": payload.intent,
"confidenceScore": payload.confidence
}
}
# 3. Send to CXone
url = f"{CXONE_BASE_URL}/api/v2/interactions/conversations/{interaction_id}"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
response = requests.patch(url, json=update_payload, headers=headers)
if response.status_code == 200:
logger.info(f"Routed {payload.sessionId} to Queue ID: {queue_id}")
return {"status": "success", "queueId": queue_id}
elif response.status_code == 404:
raise HTTPException(status_code=404, detail=f"Interaction {interaction_id} not found")
elif response.status_code == 429:
raise HTTPException(status_code=503, detail="CXone API Rate Limited")
else:
raise HTTPException(status_code=response.status_code, detail=response.text)
except HTTPException:
raise
except Exception as e:
logger.error(f"Unexpected error: {str(e)}")
raise HTTPException(status_code=500, detail="Internal Server Error")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Common Errors & Debugging
Error: 401 Unauthorized
Cause: The OAuth token has expired or the client credentials are incorrect.
Fix: Ensure the CxoneAuthManager is correctly fetching a new token. Check that the client_id and client_secret in your environment variables match the CXone Admin Console. Verify that the scope routing:queue:write is included in the token request.
Error: 404 Interaction Not Found
Cause: The interactionId passed to the PATCH endpoint does not exist in CXone.
Fix: In a Voice scenario, ensure the IVR is correctly passing the CXone Interaction ID to the Cognigy webhook. In a Web Chat scenario, ensure the Chat SDK has initialized the interaction before the bot attempts to route. Log the interactionId to verify it is a valid UUID.
Error: 403 Forbidden
Cause: The OAuth token lacks the necessary scope.
Fix: Check the token response body. Ensure the scope parameter in the OAuth request includes interaction:conversation:write. If using a restricted OAuth client, verify that the client has access to the specific routing queues.
Error: 429 Too Many Requests
Cause: The middleware is sending requests faster than CXone allows (rate limiting).
Fix: Implement exponential backoff in the CxoneAuthManager or the /route endpoint. For high-volume bots, batch routing updates or use a message queue (like RabbitMQ or AWS SQS) to throttle requests to the CXone API.