Routing NICE Cognigy Bot Intents to NICE CXone Queues via Webhooks

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:write and user:read scopes.
  • Runtime: Node.js 18+ installed locally for testing, or a configured Cognigy Cloud Action environment.
  • Dependencies: axios for HTTP requests, dotenv for 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:

  1. Create a Webhook Action.
  2. Set the URL to your local ngrok URL or deployed endpoint (e.g., https://abc123.ngrok.io/webhook/cognigy-route).
  3. 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}}
  4. In the Response Mapping, save the returned cxoneConversationId to 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 .env variables. Ensure your CXone OAuth client has the webchat:write scope. Check the auth.js logs 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:write and conversation:write are selected.

Error: 404 Not Found (Conversation)

  • Cause: You attempted to route a conversation ID that does not exist in CXone.
  • Fix: Ensure that createWebChatConversation is called before routeConversationToQueue if the session is new. Verify that the cxoneConversationId is 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 getAccessToken function caches the token until expires_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 queueId passed to routeConversationToQueue does not exist or is invalid.
  • Fix: Verify the Queue IDs in config.js. You can list queues using GET /api/v2/routing/queues to confirm the exact UUIDs.

Official References