How to set up a Genesys Cloud webhook that sends Slack notifications on queue SLA breach

How to set up a Genesys Cloud webhook that sends Slack notifications on queue SLA breach

What You Will Build

  • You will create a Genesys Cloud Webhook that triggers when a conversation in a specific queue exceeds its Service Level Agreement (SLA) target.
  • This tutorial uses the Genesys Cloud Platform APIs and the Python PureCloudPlatformClientV2 SDK.
  • The implementation covers Python for the webhook configuration and a Node.js serverless function (AWS Lambda style) to receive the payload and post to Slack.

Prerequisites

  • Genesys Cloud Account: An admin user with webhooks:write permissions.
  • OAuth Credentials: A Genesys Cloud OAuth client with webhooks:write and routing:queue:read scopes.
  • Slack Bot Token: A xoxb- token with chat:write scope for the target channel.
  • Python Environment: Python 3.8+ with pip.
  • Node.js Environment: Node.js 14+ for the webhook receiver service.
  • External Dependencies:
    • Python: purecloudplatformclientv2, requests
    • Node.js: axios, dotenv

Authentication Setup

Genesys Cloud uses OAuth 2.0 for API access. You must obtain an access token using the Client Credentials Grant flow. This token expires after 30 minutes, so production code must implement refresh logic.

Python OAuth Helper

import os
from purecloudplatformclientv2 import ApiClient, Configuration, WebhooksApi, Webhook
from purecloudplatformclientv2.rest import ApiException
import requests

# Configuration
CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
ENVIRONMENT = os.getenv("GENESYS_ENVIRONMENT", "mypurecloud.com")

def get_access_token() -> str:
    """
    Retrieves an OAuth2 access token from Genesys Cloud.
    In production, cache this token and refresh before expiration.
    """
    url = f"https://api.{ENVIRONMENT}/oauth/token"
    data = {
        'grant_type': 'client_credentials'
    }
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    
    # Basic Auth header for client credentials
    import base64
    credentials = f"{CLIENT_ID}:{CLIENT_SECRET}"
    encoded_credentials = base64.b64encode(credentials.encode()).decode()
    headers['Authorization'] = f"Basic {encoded_credentials}"

    response = requests.post(url, data=data, headers=headers)
    
    if response.status_code != 200:
        raise Exception(f"Failed to get token: {response.text}")
        
    return response.json().get('access_token')

def get_genesys_api_client():
    """
    Configures and returns the Genesys Cloud API Client.
    """
    configuration = Configuration(
        host=f"https://api.{ENVIRONMENT}",
        access_token=get_access_token()
    )
    return ApiClient(configuration)

Implementation

Step 1: Identify the Trigger and Queue

Before creating the webhook, you must identify the Queue ID you wish to monitor. Genesys Cloud webhooks for routing events require the Queue ID to filter triggers effectively.

  1. Get Queue ID: Use the RoutingApi to list queues.
  2. Define SLA Breach Logic: The webhook trigger event for SLA breaches is routing:conversation:queue:sla:breach. This event fires when a conversation has been in the queue longer than the defined service level target (e.g., 80% of conversations answered within 20 seconds).

Note: The routing:conversation:queue:sla:breach event is a standard platform event. It does not require custom scripting on the Genesys side.

Step 2: Create the Webhook via Python SDK

You will create a webhook that points to your external service (e.g., AWS Lambda, Azure Function, or a custom Node.js server).

def create_sla_breach_webhook(queue_id: str, target_url: str):
    """
    Creates a Genesys Cloud webhook for SLA breaches.
    
    Args:
        queue_id: The ID of the queue to monitor.
        target_url: The HTTPS URL of your receiver service.
    """
    api_client = get_genesys_api_client()
    webhooks_api = WebhooksApi(api_client)

    # Define the webhook configuration
    # The 'events' array specifies which triggers activate this webhook.
    # We use 'routing:conversation:queue:sla:breach'.
    webhook_body = Webhook(
        name="Queue SLA Breach Alert",
        description="Sends Slack notification when queue SLA is breached",
        target_uri=target_url,
        events=["routing:conversation:queue:sla:breach"],
        # Optional: Filter by specific queue to avoid noise
        # Note: Filtering by queue ID is done in the event payload processing 
        # or via Webhook Filters if supported by your specific license/region.
        # For this example, we assume the receiver filters by queue ID.
        enabled=True,
        # Security: Use a secret to verify the request signature
        security_token=os.getenv("WEBHOOK_SECRET_TOKEN")
    )

    try:
        response = webhooks_api.post_webhook(body=webhook_body)
        print(f"Webhook created successfully with ID: {response.id}")
        return response.id
    except ApiException as e:
        print(f"Exception when calling WebhooksApi->post_webhook: {e}")
        raise

Step 3: Build the Receiver Service (Node.js)

Genesys Cloud sends a POST request to your target_uri. You must implement a server that:

  1. Validates the request signature (using security_token).
  2. Parses the payload.
  3. Posts a formatted message to Slack.

Project Setup

mkdir genesys-slack-bot
cd genesys-slack-bot
npm init -y
npm install axios dotenv

Receiver Code (index.js)

require('dotenv').config();
const express = require('express');
const crypto = require('crypto');
const axios = require('axios');

const app = express();
app.use(express.json());

// Configuration
const SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN;
const SLACK_CHANNEL_ID = process.env.SLACK_CHANNEL_ID;
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET_TOKEN;

// Verify Genesys Cloud Webhook Signature
function verifySignature(req, res, buf) {
    if (!WEBHOOK_SECRET) return true; // Skip if no secret configured
    
    const signature = req.headers['x-genesys-signature'];
    const timestamp = req.headers['x-genesys-timestamp'];
    
    if (!signature || !timestamp) {
        return false;
    }

    const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
    hmac.update(timestamp + buf.toString());
    const digest = hmac.digest('hex');
    
    // Simple comparison; in production, use a timing-safe comparison
    return signature === `sha256=${digest}`;
}

// Post to Slack
async function postToSlack(message) {
    try {
        const response = await axios.post('https://slack.com/api/chat.postMessage', {
            channel: SLACK_CHANNEL_ID,
            text: message
        }, {
            headers: {
                'Authorization': `Bearer ${SLACK_BOT_TOKEN}`,
                'Content-Type': 'application/json'
            }
        });
        
        if (!response.data.ok) {
            console.error('Slack API Error:', response.data.error);
        }
    } catch (error) {
        console.error('Error posting to Slack:', error.message);
    }
}

// Webhook Endpoint
app.post('/webhook/genesys', (req, res) => {
    // Verify signature
    if (!verifySignature(req, res, JSON.stringify(req.body))) {
        return res.status(401).send('Unauthorized');
    }

    const payload = req.body;
    const event = payload.event;

    // Check if this is an SLA breach event
    if (event === 'routing:conversation:queue:sla:breach') {
        const queueName = payload.data.queue.name;
        const waitTime = payload.data.waitTimeSeconds;
        const slaTarget = payload.data.slaTargetSeconds;
        const conversationId = payload.data.conversationId;

        // Format Slack Message
        const slackMessage = `
*🚨 SLA Breach Alert*
*Queue:* ${queueName}
*Wait Time:* ${waitTime}s (Target: ${slaTarget}s)
*Conversation ID:* ${conversationId}
        `.trim();

        // Post to Slack
        postToSlack(slackMessage);
        
        // Acknowledge receipt to Genesys Cloud
        res.status(200).send('OK');
    } else {
        // Ignore other events
        res.status(200).send('Ignored');
    }
});

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

Step 4: Deploy and Connect

  1. Deploy the Node.js Service: Use a platform like AWS Lambda, Heroku, or a simple EC2 instance. Ensure the endpoint is publicly accessible via HTTPS (Genesys Cloud requires HTTPS for webhooks).
  2. Note the Public URL: For example, https://my-service.herokuapp.com/webhook/genesys.
  3. Run the Python Script: Execute the Python script to create the webhook, passing the Queue ID and the Public URL.
if __name__ == "__main__":
    QUEUE_ID = "your-queue-id-here"
    TARGET_URL = "https://my-service.herokuapp.com/webhook/genesys"
    
    create_sla_breach_webhook(QUEUE_ID, TARGET_URL)

Complete Working Example

Python (create_webhook.py)

import os
import sys
from purecloudplatformclientv2 import ApiClient, Configuration, WebhooksApi, Webhook
from purecloudplatformclientv2.rest import ApiException
import requests
import base64

def get_access_token() -> str:
    CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
    CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
    ENVIRONMENT = os.getenv("GENESYS_ENVIRONMENT", "mypurecloud.com")
    
    url = f"https://api.{ENVIRONMENT}/oauth/token"
    data = {'grant_type': 'client_credentials'}
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    
    credentials = f"{CLIENT_ID}:{CLIENT_SECRET}"
    encoded_credentials = base64.b64encode(credentials.encode()).decode()
    headers['Authorization'] = f"Basic {encoded_credentials}"

    response = requests.post(url, data=data, headers=headers)
    
    if response.status_code != 200:
        raise Exception(f"Failed to get token: {response.text}")
        
    return response.json().get('access_token')

def create_sla_breach_webhook(queue_id: str, target_url: str):
    configuration = Configuration(
        host=f"https://api.{os.getenv('GENESYS_ENVIRONMENT', 'mypurecloud.com')}",
        access_token=get_access_token()
    )
    api_client = ApiClient(configuration)
    webhooks_api = WebhooksApi(api_client)

    webhook_body = Webhook(
        name="Queue SLA Breach Alert",
        description="Sends Slack notification when queue SLA is breached",
        target_uri=target_url,
        events=["routing:conversation:queue:sla:breach"],
        enabled=True,
        security_token=os.getenv("WEBHOOK_SECRET_TOKEN")
    )

    try:
        response = webhooks_api.post_webhook(body=webhook_body)
        print(f"Webhook created successfully with ID: {response.id}")
        return response.id
    except ApiException as e:
        print(f"Exception when calling WebhooksApi->post_webhook: {e}")
        raise

if __name__ == "__main__":
    if not os.getenv("GENESYS_CLIENT_ID") or not os.getenv("GENESYS_CLIENT_SECRET"):
        print("Error: GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables are required.")
        sys.exit(1)
        
    QUEUE_ID = os.getenv("QUEUE_ID", "default-queue-id")
    TARGET_URL = os.getenv("TARGET_URL", "https://example.com/webhook")
    
    create_sla_breach_webhook(QUEUE_ID, TARGET_URL)

Node.js (index.js)

require('dotenv').config();
const express = require('express');
const crypto = require('crypto');
const axios = require('axios');

const app = express();
app.use(express.json());

const SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN;
const SLACK_CHANNEL_ID = process.env.SLACK_CHANNEL_ID;
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET_TOKEN;

function verifySignature(req, buf) {
    if (!WEBHOOK_SECRET) return true;
    const signature = req.headers['x-genesys-signature'];
    const timestamp = req.headers['x-genesys-timestamp'];
    if (!signature || !timestamp) return false;

    const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
    hmac.update(timestamp + buf.toString());
    const digest = hmac.digest('hex');
    return signature === `sha256=${digest}`;
}

async function postToSlack(message) {
    try {
        const response = await axios.post('https://slack.com/api/chat.postMessage', {
            channel: SLACK_CHANNEL_ID,
            text: message
        }, {
            headers: {
                'Authorization': `Bearer ${SLACK_BOT_TOKEN}`,
                'Content-Type': 'application/json'
            }
        });
        if (!response.data.ok) console.error('Slack Error:', response.data.error);
    } catch (error) {
        console.error('Slack Post Error:', error.message);
    }
}

app.post('/webhook/genesys', (req, res) => {
    if (!verifySignature(req, JSON.stringify(req.body))) {
        return res.status(401).send('Unauthorized');
    }

    const payload = req.body;
    if (payload.event === 'routing:conversation:queue:sla:breach') {
        const data = payload.data;
        const slackMessage = `
*🚨 SLA Breach Alert*
*Queue:* ${data.queue.name}
*Wait:* ${data.waitTimeSeconds}s / *Target:* ${data.slaTargetSeconds}s
*ID:* ${data.conversationId}
        `.trim();
        postToSlack(slackMessage);
    }
    res.status(200).send('OK');
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Listening on ${PORT}`));

Common Errors & Debugging

Error: 401 Unauthorized

  • Cause: The OAuth token is invalid, expired, or the client credentials are incorrect.
  • Fix: Verify GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET. Ensure the client has webhooks:write scope. Check that the token is being refreshed before it expires.

Error: 403 Forbidden

  • Cause: The OAuth client does not have the necessary permissions.
  • Fix: Ensure the OAuth client is associated with an Admin user who has webhooks:write and routing:queue:read permissions in the Genesys Cloud Admin Console.

Error: 405 Method Not Allowed

  • Cause: The target URL does not accept POST requests.
  • Fix: Ensure your Node.js endpoint is configured to handle POST requests (app.post('/webhook/genesys', ...)).

Error: Webhook Not Firing

  • Cause: The queue does not have an SLA target configured, or no conversations are breaching the SLA.
  • Fix: Verify the queue has an SLA target set (e.g., 80% in 20 seconds). Generate test traffic to the queue that exceeds the wait time. Check the Webhooks section in the Genesys Cloud Admin Console to view delivery logs.

Error: Slack Post Failure

  • Cause: Invalid Slack Bot Token or Channel ID.
  • Fix: Ensure the Slack Bot Token (xoxb-) has chat:write scope. Ensure the Channel ID is correct (starts with C). Verify the bot is added to the target channel.

Official References