Routing NICE Cognigy Bot Intents to NICE CXone Queues via Webhooks
What You Will Build
- You will build a serverless function that receives a webhook payload from NICE Cognigy and routes the conversation to a specific NICE CXone queue based on the detected intent.
- This tutorial uses the NICE CXone REST API (
/api/v2/conversations/webchat) and the NICE Cognigy webhook integration mechanism. - The programming language covered is Node.js (JavaScript), which is the standard runtime for Cognigy Cloud Actions and compatible with AWS Lambda or Azure Functions for standalone hosting.
Prerequisites
- NICE CXone Account: An active account with permissions to create Queues and Web Chat integrations.
- NICE Cognigy Account: An active account with access to the Cognigy Studio and Cloud Actions.
- OAuth Client Credentials: A NICE CXone OAuth client configured with
webchat:writeanduser:readscopes. - Runtime: Node.js 18+ installed locally for testing, or a configured Cognigy Cloud Action environment.
- Dependencies:
axiosfor HTTP requests,dotenvfor environment variable management.
Authentication Setup
NICE CXone uses OAuth 2.0 Client Credentials flow for server-to-server API calls. You must obtain an access token before making any routing decisions. In a production webhook handler, you should cache this token to avoid rate-limiting the authorization server.
Install the required dependencies:
npm install axios dotenv
Create a .env file in your project root:
CXONE_ENVIRONMENT=us-east-1
CXONE_CLIENT_ID=your_client_id_here
CXONE_CLIENT_SECRET=your_client_secret_here
CXONE_DOMAIN=your_domain.nice-in接触.com
The following module handles token acquisition and caching.
// auth.js
const axios = require('axios');
const { CXONE_ENVIRONMENT, CXONE_CLIENT_ID, CXONE_CLIENT_SECRET, CXONE_DOMAIN } = process.env;
let cachedToken = null;
let tokenExpiry = 0;
const CXONE_API_BASE = `https://${CXONE_ENVIRONMENT}.platform.nice-in接触.com`;
/**
* Fetches a new OAuth access token from NICE CXone.
* @returns {Promise<string>} The access token.
*/
async function getAccessToken() {
const now = Date.now();
// Return cached token if it has not expired (subtract 60s for buffer)
if (cachedToken && now < tokenExpiry - 60000) {
return cachedToken;
}
try {
const response = await axios.post(`${CXONE_API_BASE}/api/v2/oauth/token`, null, {
params: {
grant_type: 'client_credentials',
client_id: CXONE_CLIENT_ID,
client_secret: CXONE_CLIENT_SECRET,
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
cachedToken = response.data.access_token;
// expires_in is in seconds, convert to milliseconds
tokenExpiry = now + (response.data.expires_in * 1000);
return cachedToken;
} catch (error) {
console.error('Failed to fetch CXone access token:', error.response ? error.response.data : error.message);
throw new Error('Authentication failed');
}
}
module.exports = { getAccessToken };
Implementation
Step 1: Define the Cognigy Webhook Payload Structure
NICE Cognigy sends a JSON payload to your webhook URL when a specific event triggers (e.g., end of a dialogue step). To route dynamically, you must capture the intent. In Cognigy Studio, you configure the Webhook Action to send the intent and confidence from the NLU engine.
The expected payload structure from Cognigy looks like this:
{
"id": "cognigy-session-12345",
"intent": "billing_inquiry",
"confidence": 0.92,
"user": {
"id": "user-xyz",
"name": "John Doe"
},
"attributes": {
"previousQueue": null
}
}
You must map these Cognigy intents to NICE CXone Queue IDs. Hardcoding this mapping in your code is acceptable for small scales, but for production, consider storing this mapping in a database or configuration service.
// config.js
/**
* Maps Cognigy intent names to NICE CXone Queue IDs.
* You must retrieve these Queue IDs from your CXone Admin Console.
*/
const INTENT_TO_QUEUE_MAP = {
'billing_inquiry': 'queue-id-billing-123',
'technical_support': 'queue-id-tech-456',
'sales_general': 'queue-id-sales-789',
'default': 'queue-id-general-000'
};
module.exports = { INTENT_TO_QUEUE_MAP };
Step 2: Initialize the Web Chat Conversation
Before routing to a queue, you must ensure a Web Chat session exists in NICE CXone. If Cognigy initiated the chat, you might already have a cxoneConversationId. If not, you need to create one.
The endpoint is POST /api/v2/conversations/webchat.
// cxoneClient.js
const axios = require('axios');
const { getAccessToken } = require('./auth');
const { CXONE_API_BASE } = require('./auth');
/**
* Creates a new Web Chat conversation in NICE CXone.
* @param {string} userId - The unique identifier for the user.
* @param {string} userName - The display name for the user.
* @returns {Promise<object>} The conversation object containing the conversationId.
*/
async function createWebChatConversation(userId, userName) {
const token = await getAccessToken();
const payload = {
userId: userId,
userName: userName,
// Optional: Add custom attributes for analytics
attributes: {
source: 'cognigy_bot'
}
};
try {
const response = await axios.post(
`${CXONE_API_BASE}/api/v2/conversations/webchat`,
payload,
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}
);
return response.data;
} catch (error) {
console.error('Failed to create CXone conversation:', error.response ? error.response.data : error.message);
throw error;
}
}
module.exports = { createWebChatConversation };
Step 3: Route the Conversation to a Queue
Once the conversation exists, you must update its state to route it to the target queue. This is done via PATCH /api/v2/conversations/{conversationId}/states.
You need to send a routing object that specifies the target queue.
// cxoneClient.js (continued)
/**
* Routes an existing Web Chat conversation to a specific queue.
* @param {string} conversationId - The CXone conversation ID.
* @param {string} queueId - The target CXone Queue ID.
* @returns {Promise<void>}
*/
async function routeConversationToQueue(conversationId, queueId) {
const token = await getAccessToken();
const payload = {
routing: {
queueId: queueId,
// Optional: Priority (1-1000). Higher numbers are higher priority.
priority: 500
}
};
try {
await axios.patch(
`${CXONE_API_BASE}/api/v2/conversations/${conversationId}/states`,
payload,
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}
);
} catch (error) {
console.error(`Failed to route conversation ${conversationId} to queue ${queueId}:`,
error.response ? error.response.data : error.message);
throw error;
}
}
module.exports = { createWebChatConversation, routeConversationToQueue };
Step 4: The Main Webhook Handler
This is the entry point for your Cognigy Cloud Action. It receives the webhook payload, determines the queue, ensures a CXone session exists, and executes the routing.
// index.js
const { createWebChatConversation, routeConversationToQueue } = require('./cxoneClient');
const { INTENT_TO_QUEUE_MAP } = require('./config');
/**
* Main handler for the Cognigy Webhook.
* @param {object} req - The HTTP request object.
* @param {object} res - The HTTP response object.
*/
async function handleCognigyWebhook(req, res) {
try {
const body = req.body;
// 1. Validate Payload
if (!body.intent || !body.id) {
return res.status(400).json({ error: 'Missing intent or session ID in payload' });
}
const cognigySessionId = body.id;
const intent = body.intent;
const userId = body.user?.id || 'anonymous-user';
const userName = body.user?.name || 'Anonymous';
// 2. Determine Target Queue
const targetQueueId = INTENT_TO_QUEUE_MAP[intent] || INTENT_TO_QUEUE_MAP['default'];
console.log(`Routing intent '${intent}' to queue '${targetQueueId}' for session '${cognigySessionId}'`);
// 3. Check for existing CXone Conversation ID
// In a real scenario, you might store the cxoneConversationId in Cognigy Session Attributes
// during the initial handshake. For this tutorial, we assume we need to create one
// or retrieve it from a session store. Here we simulate creation.
let cxoneConversationId = body.attributes?.cxoneConversationId;
if (!cxoneConversationId) {
// Create new conversation
const newConversation = await createWebChatConversation(userId, userName);
cxoneConversationId = newConversation.conversationId;
// Important: Return this ID to Cognigy so it can be stored in Session Attributes
// for subsequent messages in the same session.
console.log(`Created new CXone conversation: ${cxoneConversationId}`);
}
// 4. Route to Queue
await routeConversationToQueue(cxoneConversationId, targetQueueId);
// 5. Respond to Cognigy
// Return the conversation ID so Cognigy can store it for future turns
res.status(200).json({
success: true,
cxoneConversationId: cxoneConversationId,
routedToQueue: targetQueueId
});
} catch (error) {
console.error('Webhook Handler Error:', error);
res.status(500).json({
error: 'Internal Server Error',
message: error.message
});
}
}
module.exports = { handleCognigyWebhook };
Complete Working Example
This example assumes you are deploying this as a standalone Express server for local testing, or adapting it for a Cognigy Cloud Action.
// server.js (For Local Testing)
const express = require('express');
const bodyParser = require('body-parser');
const { handleCognigyWebhook } = require('./index');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(bodyParser.json());
// Endpoint exposed to Cognigy Webhook Action
app.post('/webhook/cognigy-route', handleCognigyWebhook);
app.listen(PORT, () => {
console.log(`Webhook listener running on port ${PORT}`);
});
To run locally:
node server.js
In Cognigy Studio:
- Create a Webhook Action.
- Set the URL to your local ngrok URL or deployed endpoint (e.g.,
https://abc123.ngrok.io/webhook/cognigy-route). - In the Request Body, map the fields:
id:{{session.id}}intent:{{nlu.intent}}confidence:{{nlu.confidence}}user.id:{{user.id}}user.name:{{user.name}}attributes.cxoneConversationId:{{session.cxoneConversationId}}
- In the Response Mapping, save the returned
cxoneConversationIdto a session attribute:session.cxoneConversationId:{{response.cxoneConversationId}}
Common Errors & Debugging
Error: 401 Unauthorized
- Cause: The OAuth token is invalid, expired, or the Client ID/Secret is incorrect.
- Fix: Verify your
.envvariables. Ensure your CXone OAuth client has thewebchat:writescope. Check theauth.jslogs for specific token error messages.
Error: 403 Forbidden
- Cause: The OAuth client lacks the specific permission to write to Web Chat conversations or update states.
- Fix: Go to CXone Admin Console > Users & Security > OAuth Clients. Edit your client and ensure
webchat:writeandconversation:writeare selected.
Error: 404 Not Found (Conversation)
- Cause: You attempted to route a conversation ID that does not exist in CXone.
- Fix: Ensure that
createWebChatConversationis called beforerouteConversationToQueueif the session is new. Verify that thecxoneConversationIdis being correctly persisted in Cognigy Session Attributes.
Error: 429 Too Many Requests
- Cause: You are hitting the CXone API rate limits. This often happens if you do not cache the OAuth token and request a new one for every message.
- Fix: Ensure the
getAccessTokenfunction caches the token untilexpires_in - 60s. Implement exponential backoff in your axios interceptor for 429 responses.
// Add to auth.js or a central axios interceptor
const axios = require('axios');
axios.interceptors.response.use(
response => response,
error => {
if (error.response && error.response.status === 429) {
console.warn('Rate limited by CXone. Retrying in 1 second...');
return new Promise(resolve => setTimeout(() => resolve(error.response.config), 1000));
}
return Promise.reject(error);
}
);
Error: 400 Bad Request (Invalid Queue ID)
- Cause: The
queueIdpassed torouteConversationToQueuedoes not exist or is invalid. - Fix: Verify the Queue IDs in
config.js. You can list queues usingGET /api/v2/routing/queuesto confirm the exact UUIDs.