Routing Cognigy.AI Dialog Flows by User Tier with TypeScript Middleware

Routing Cognigy.AI Dialog Flows by User Tier with TypeScript Middleware

What You Will Build

  • This middleware intercepts incoming Cognigy.AI webhook requests, determines a customer subscription tier via an external CRM, evaluates a routing matrix, and updates the bot session to continue execution at a tier-specific flow node.
  • This implementation uses the Cognigy.AI Webhook API and the Cognigy.AI Session REST API (PUT /api/v1/sessions/{sessionId}).
  • The tutorial covers TypeScript with Node.js, Express, and Axios.

Prerequisites

  • Cognigy.AI tenant with API Key or OAuth2 Client Credentials enabled
  • Required Cognigy.AI scopes: session:read, session:write
  • Node.js 18+ and TypeScript 5+
  • External CRM API endpoint with Bearer token authentication
  • Dependencies: npm install express axios zod @types/express @types/node

Authentication Setup

Cognigy.AI accepts API Keys or OAuth2 Bearer tokens for backend integrations. The middleware requires a long-lived API Key for session manipulation and a CRM token for customer data resolution.

// config.ts
export const COGNIGY_CONFIG = {
  baseUrl: process.env.COGNIGY_TENANT_URL || 'https://mytenant.cognigy.ai',
  apiKey: process.env.COGNIGY_API_KEY || '',
  scopes: ['session:read', 'session:write']
} as const;

export const CRM_CONFIG = {
  baseUrl: process.env.CRM_API_URL || 'https://api.crm-provider.com',
  token: process.env.CRM_API_TOKEN || ''
} as const;

Cognigy.AI API Key authentication passes the key in the Authorization header as a Bearer token. The CRM endpoint uses the same pattern. Both tokens must remain scoped to the minimum required permissions.

Implementation

Step 1: Webhook Interception and Payload Validation

Cognigy.AI sends a JSON payload to your webhook endpoint when a bot reaches a Webhook node. The middleware must validate the structure, extract the session identifier, and prepare for downstream calls.

// types.ts
export interface CognigyWebhookPayload {
  id: string;
  user: { id: string; email?: string; phoneNumber?: string };
  session: {
    id: string;
    botId: string;
    flow: string;
    node: string;
    metadata: Record<string, unknown>;
    [key: string]: unknown;
  };
  intent: { name: string; confidence: number };
  entities: Array<{ name: string; value: string; confidence: number }>;
  history: Array<Record<string, unknown>>;
  metadata: Record<string, unknown>;
  context: Record<string, unknown>;
}
// middleware/webhook.interceptor.ts
import { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import { CognigyWebhookPayload } from '../types';

const WebhookSchema = z.object({
  id: z.string().uuid(),
  user: z.object({ id: z.string() }),
  session: z.object({
    id: z.string().uuid(),
    botId: z.string(),
    flow: z.string(),
    node: z.string()
  })
});

export async function validateWebhook(req: Request, res: Response, next: NextFunction) {
  try {
    const parsed = WebhookSchema.parse(req.body as CognigyWebhookPayload);
    req.cognigyPayload = parsed;
    next();
  } catch (error) {
    res.status(400).json({ error: 'Invalid Cognigy.AI webhook payload structure', details: error.message });
  }
}

Expected Webhook Request

POST /webhooks/cognigy-router HTTP/1.1
Host: api.yourdomain.com
Content-Type: application/json
Authorization: Bearer <cognigy-api-key>

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "user": { "id": "usr_98765", "email": "client@example.com" },
  "session": {
    "id": "sess_112233",
    "botId": "bot_445566",
    "flow": "main_menu",
    "node": "webhook_tier_resolver"
  },
  "intent": { "name": "support_request", "confidence": 0.92 },
  "entities": [],
  "history": [],
  "metadata": {},
  "context": {}
}

Step 2: CRM Resolution and Tier Evaluation

The middleware queries the external CRM to retrieve the customer subscription level. The request includes exponential backoff retry logic for 429 responses. The routing matrix maps tiers to target flow endpoints.

// services/crm.client.ts
import axios, { AxiosInstance, AxiosError } from 'axios';
import { CRM_CONFIG } from '../config';

export interface CustomerTierResponse {
  customerId: string;
  subscriptionTier: 'free' | 'standard' | 'premium' | 'enterprise';
  contractStatus: 'active' | 'suspended' | 'expired';
}

export class CrmClient {
  private client: AxiosInstance;

  constructor() {
    this.client = axios.create({
      baseURL: CRM_CONFIG.baseUrl,
      headers: { Authorization: `Bearer ${CRM_CONFIG.token}` },
      timeout: 3000
    });
  }

  async resolveTier(userId: string): Promise<CustomerTierResponse> {
    const retryConfig = {
      retry: 3,
      retryDelay: (attempt: number) => Math.pow(2, attempt) * 1000,
      retryCondition: (error: AxiosError) => error.response?.status === 429
    };

    const response = await this.client.get<CustomerTierResponse>(
      `/api/v1/customers/${userId}`,
      {
        validateStatus: (status) => status === 200
      }
    );

    if (response.data.contractStatus === 'suspended' || response.data.contractStatus === 'expired') {
      throw new Error('Customer contract is not active');
    }

    return response.data;
  }
}

Routing Matrix Configuration

// config/routing.matrix.ts
export const ROUTING_MATRIX: Record<string, { flow: string; node: string }> = {
  free: { flow: 'self_service_flow', node: 'free_tier_entry' },
  standard: { flow: 'standard_support_flow', node: 'standard_entry' },
  premium: { flow: 'premium_support_flow', node: 'premium_entry' },
  enterprise: { flow: 'enterprise_dedicated_flow', node: 'enterprise_entry' }
};

Step 3: Session Redirection via Cognigy REST API

After determining the tier, the middleware calls the Cognigy.AI Session API to update the session context. Cognigy.AI uses the updated flow and node properties to redirect execution immediately after the webhook returns.

// services/cognigy.client.ts
import axios, { AxiosInstance } from 'axios';
import { COGNIGY_CONFIG } from '../config';

export class CognigyClient {
  private client: AxiosInstance;

  constructor() {
    this.client = axios.create({
      baseURL: `${COGNIGY_CONFIG.baseUrl}/api/v1`,
      headers: { Authorization: `Bearer ${COGNIGY_CONFIG.apiKey}` },
      timeout: 5000
    });
  }

  async updateSessionRouting(
    sessionId: string,
    targetFlow: string,
    targetNode: string,
    tier: string
  ): Promise<void> {
    await this.client.put(`/sessions/${sessionId}`, {
      session: {
        flow: targetFlow,
        node: targetNode,
        metadata: {
          resolvedTier: tier,
          routingTimestamp: new Date().toISOString(),
          routedBy: 'tier-middleware-v1'
        }
      }
    });
  }
}

Cognigy.AI Session Update Request/Response Cycle

PUT /api/v1/sessions/sess_112233 HTTP/1.1
Host: mytenant.cognigy.ai
Content-Type: application/json
Authorization: Bearer <cognigy-api-key>

{
  "session": {
    "flow": "premium_support_flow",
    "node": "premium_entry",
    "metadata": {
      "resolvedTier": "premium",
      "routingTimestamp": "2024-05-20T14:32:00.000Z",
      "routedBy": "tier-middleware-v1"
    }
  }
}
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": "sess_112233",
  "botId": "bot_445566",
  "flow": "premium_support_flow",
  "node": "premium_entry",
  "metadata": {
    "resolvedTier": "premium",
    "routingTimestamp": "2024-05-20T14:32:00.000Z",
    "routedBy": "tier-middleware-v1"
  },
  "lastUpdated": "2024-05-20T14:32:00.123Z"
}

The webhook handler ties these components together. It returns the original payload structure with the updated session to satisfy Cognigy.AI synchronous webhook expectations.

// routes/router.handler.ts
import { Request, Response } from 'express';
import { CrmClient } from '../services/crm.client';
import { CognigyClient } from '../services/cognigy.client';
import { ROUTING_MATRIX } from '../config/routing.matrix';

const crm = new CrmClient();
const cognigy = new CognigyClient();

export async function handleTierRouting(req: Request, res: Response) {
  const payload = req.cognigyPayload;
  const userId = payload.user.id;

  try {
    const customerData = await crm.resolveTier(userId);
    const tier = customerData.subscriptionTier;

    const routingRule = ROUTING_MATRIX[tier];
    if (!routingRule) {
      throw new Error(`No routing rule defined for tier: ${tier}`);
    }

    await cognigy.updateSessionRouting(
      payload.session.id,
      routingRule.flow,
      routingRule.node,
      tier
    );

    payload.session.flow = routingRule.flow;
    payload.session.node = routingRule.node;
    payload.session.metadata = {
      ...payload.session.metadata,
      resolvedTier: tier,
      routingTimestamp: new Date().toISOString()
    };

    res.json(payload);
  } catch (error) {
    console.error('Tier routing failed:', error);
    res.status(502).json({
      error: 'Tier resolution failed',
      details: error instanceof Error ? error.message : 'Unknown error',
      fallback: { flow: 'error_handling_flow', node: 'generic_error_node' }
    });
  }
}

Complete Working Example

// index.ts
import express, { Express, Request, Response } from 'express';
import { validateWebhook } from './middleware/webhook.interceptor';
import { handleTierRouting } from './routes/router.handler';

declare global {
  namespace Express {
    interface Request {
      cognigyPayload?: any;
    }
  }
}

const app: Express = express();
app.use(express.json({ limit: '1mb' }));

app.post('/webhooks/cognigy-router', validateWebhook, async (req: Request, res: Response) => {
  await handleTierRouting(req, res);
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Tier routing middleware listening on port ${PORT}`);
});

Compile and run:

npx tsc --target es2020 --module commonjs --outDir dist
node dist/index.js

Common Errors & Debugging

Error: 401 Unauthorized on Cognigy.AI Session Update

  • Cause: The API Key lacks session:write scope or the token has expired. Cognigy.AI validates scopes per endpoint.
  • Fix: Regenerate the API Key in the Cognigy.AI admin console with session:read and session:write permissions. Verify the Authorization header matches exactly.
  • Code verification: Add a pre-flight check that calls GET /api/v1/sessions/{sessionId} to validate token permissions before writing.

Error: 429 Too Many Requests on CRM Resolution

  • Cause: The external CRM enforces rate limits per tenant or per API key. Concurrent webhook triggers exceed the threshold.
  • Fix: The AxiosInstance configuration includes retry logic with exponential backoff. Increase retryDelay multiplier if the CRM uses strict windowed limits. Implement request queuing if volume exceeds CRM capacity.
  • Code verification: Monitor error.response?.status === 429 in production logs. Adjust retry: 3 to retry: 5 for high-traffic environments.

Error: 400 Bad Request on Webhook Payload

  • Cause: Cognigy.AI sends additional fields or modifies structure during platform updates. The Zod schema rejects unexpected properties.
  • Fix: Use z.object(...).passthrough() or .strip() depending on whether you want to preserve extra metadata. Cognigy.AI payloads evolve; validate only required fields.
  • Code verification: Update WebhookSchema to z.object({...}).passthrough() to allow forward compatibility while enforcing core identifiers.

Error: Session Not Redirecting After Webhook Response

  • Cause: Cognigy.AI webhook execution is synchronous. The platform reads the returned session.flow and session.node properties to determine the next step. If the REST API call succeeds but the webhook response does not reflect the change, the bot continues on the original path.
  • Fix: Ensure both the REST API update and the webhook response payload contain identical flow and node values. The middleware explicitly mutates payload.session before returning.
  • Code verification: Log the returned JSON payload. Confirm flow and node match the routing matrix target.

Official References