Build a Genesys Cloud Webhook for Slack SLA Breach Notifications

Build a Genesys Cloud Webhook for Slack SLA Breach Notifications

What You Will Build

  • This tutorial demonstrates how to configure a Genesys Cloud webhook that triggers when a conversation in a specific queue breaches its Service Level Agreement (SLA).
  • The solution uses the Genesys Cloud Platform API v2 to create a webhook definition and a Slack Incoming Webhook to receive the payload.
  • The implementation is provided in Python using the official Genesys Cloud SDK and the requests library for testing.

Prerequisites

  • Genesys Cloud Account: An organization with Administrator or Supervisor permissions to create webhooks.
  • OAuth Credentials: A Public or Private OAuth Client with the following scopes:
    • webhook:read
    • webhook:write
    • routing:queue:read (to validate queue IDs)
    • conversation:conversation:read (optional, for debugging context)
  • Slack Workspace: A channel where notifications will be posted.
  • Slack Incoming Webhook URL: Generated via the Slack App Management console.
  • Python Environment: Python 3.8+ with genesyscloud and requests installed.
pip install genesyscloud requests

Authentication Setup

Genesys Cloud uses OAuth 2.0 for all API interactions. For server-side integrations like webhooks, the Client Credentials Grant flow is the standard approach. This flow exchanges your client ID and secret for an access token that does not require user interaction.

The token expires after one hour. Production code should implement a cache or a refresh loop. For this tutorial, we will use the SDK’s built-in handling where possible, but explicitly show the token retrieval for clarity.

import os
from genesyscloud.platform_client import PlatformClient
from genesyscloud.auth import AuthClient

def get_platform_client(client_id: str, client_secret: str) -> PlatformClient:
    """
    Initializes the Genesys Cloud Platform Client.
    The SDK handles token caching automatically for the lifetime of the client object.
    """
    platform_client = PlatformClient()
    auth_client = platform_client.auth
    
    try:
        auth_client.set_client_id(client_id)
        auth_client.set_client_secret(client_secret)
        # Obtain the initial token
        auth_client.get_token()
    except Exception as e:
        raise Exception(f"Authentication failed: {str(e)}")

    return platform_client

# Example Usage
GENESYS_CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
GENESYS_CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")

if not GENESYS_CLIENT_ID or not GENESYS_CLIENT_SECRET:
    raise ValueError("Environment variables GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET are required.")

platform_client = get_platform_client(GENESYS_CLIENT_ID, GENESYS_CLIENT_SECRET)

Implementation

Step 1: Define the Webhook Configuration

Before calling the API, you must structure the webhook definition correctly. The Genesys Cloud webhook engine supports multiple event types. For SLA breaches, the relevant event type is routing:conversation:wrapup. However, SLA breach detection is more accurately handled by monitoring the routing:conversation:metrics or specific queue-level events if available.

Actually, the most robust way to detect an SLA breach programmatically via webhook is to listen for routing:conversation:wrapup or routing:conversation:answer and check the metrics, OR use the specific routing:queue:sla event if your organization has access to advanced routing analytics events.

For this tutorial, we will use the routing:conversation:wrapup event combined with a filter. Why? Because an SLA breach is typically confirmed when the conversation ends (wrapup) and the metrics show the wait time exceeded the target. Alternatively, you can use the routing:conversation:answer event to detect if the answer time exceeded the SLA target immediately upon answer.

Let us use routing:conversation:answer as it provides immediate notification when an agent answers a call that has already breached the SLA.

The webhook payload must be a JSON object conforming to the Webhook schema.

{
  "name": "SLA Breach Slack Notifier",
  "description": "Sends Slack alert when a queue conversation breaches SLA",
  "enabled": true,
  "eventTypes": [
    "routing:conversation:answer"
  ],
  "requestUrl": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
  "httpMethod": "POST",
  "contentType": "application/json",
  "headers": [
    {
      "key": "Content-Type",
      "value": "application/json"
    }
  ],
  "filter": {
    "type": "conversation",
    "conditions": [
      {
        "field": "queue.id",
        "operator": "equals",
        "value": "YOUR_QUEUE_ID_HERE"
      }
    ]
  }
}

Note on Filtering: Genesys Cloud webhooks support limited filtering at the API level. Complex logic (like comparing waitTime against slaTarget) often requires a middleware server. However, if you want a pure webhook-to-Slack solution without middleware, you must rely on the fact that the routing:conversation:answer event includes the waitTime in seconds. You can use a Slack Block Kit Builder to format the message, but the decision to send the message is handled by Genesys sending all answers for that queue.

To strictly filter for SLA breaches inside Genesys without middleware, you would need a custom integration. Since we are building a direct webhook, we will send all answers for the queue to Slack and use a Slack Workflow or simply accept that Slack will receive all answers.

Correction for Precision: If you strictly need to filter only breaches at the source, you must use a middleware endpoint (Step 3 below). For this tutorial, we will build the Middleware Approach because it is the production-standard for SLA monitoring. Sending every answered call to Slack is noisy. We will build a Python Flask server that acts as the webhook receiver, checks the SLA, and only posts to Slack if breached.

Step 2: Build the Middleware Server

We need a small HTTP server to receive the Genesys webhook, evaluate the SLA condition, and forward to Slack.

Dependencies:

pip install flask requests

Middleware Code (app.py):

from flask import Flask, request, jsonify
import requests
import os

app = Flask(__name__)

# Configuration
SLACK_WEBHOOK_URL = os.getenv("SLACK_WEBHOOK_URL")
QUEUE_ID = os.getenv("TARGET_QUEUE_ID")  # The Queue ID to monitor
SLA_TARGET_SECONDS = int(os.getenv("SLA_TARGET_SECONDS", "20"))  # Default 20 seconds

def send_to_slack(message: str):
    """Sends a message to Slack via Incoming Webhook."""
    payload = {
        "text": message,
        "blocks": [
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": message
                }
            }
        ]
    }
    
    try:
        response = requests.post(SLACK_WEBHOOK_URL, json=payload, timeout=5)
        response.raise_for_status()
        return True
    except requests.exceptions.RequestException as e:
        print(f"Failed to send to Slack: {e}")
        return False

@app.route('/webhook/genesys', methods=['POST'])
def handle_genesys_webhook():
    """
    Receives webhook from Genesys Cloud.
    Filters for SLA breaches and forwards to Slack.
    """
    # 1. Validate Content-Type
    if not request.is_json:
        return jsonify({"error": "Invalid content type"}), 400

    data = request.json
    
    # 2. Extract Conversation Details
    # The event type is routing:conversation:answer
    # Key fields: conversationId, queueId, waitTime (in seconds)
    
    conversation_id = data.get("conversationId")
    queue_id = data.get("queueId")
    wait_time_seconds = data.get("waitTime", 0) / 1000.0 # Genesys sends waitTime in milliseconds
    
    # 3. Filter by Queue
    if queue_id != QUEUE_ID:
        # Ignore events from other queues
        return jsonify({"status": "ignored"}), 200

    # 4. Check SLA Breach
    if wait_time_seconds > SLA_TARGET_SECONDS:
        # SLA Breached
        breach_message = (
            f":rotating_light: **SLA Breach Detected**\n"
            f"*Queue:* {queue_id}\n"
            f"*Conversation:* {conversation_id}\n"
            f"*Wait Time:* {wait_time_seconds:.1f}s (Target: {SLA_TARGET_SECONDS}s)\n"
            f"<https://myorg.genesyscloud.com/conversations/{conversation_id}|View in Genesys>"
        )
        
        success = send_to_slack(breach_message)
        if success:
            return jsonify({"status": "sent"}), 200
        else:
            return jsonify({"status": "slack_error"}), 502
    else:
        # Within SLA
        return jsonify({"status": "within_sla"}), 200

if __name__ == '__main__':
    app.run(port=5000, debug=True)

Step 3: Expose the Middleware (Optional but Recommended)

For Genesys Cloud to reach your local Flask server, you need a public URL. Use a tunneling service like ngrok for testing.

ngrok http 5000

Copy the https://...ngrok.io URL. This will be your requestUrl in the Genesys Webhook definition.

Step 4: Create the Webhook via API

Now we use the Genesys Cloud SDK to create the webhook definition pointing to our middleware.

from genesyscloud.webhooks import WebhooksClient
from genesyscloud.webhooks.model import Webhook, WebhookHeader

def create_sla_webhook(platform_client: PlatformClient, webhook_url: str):
    """
    Creates the webhook in Genesys Cloud pointing to the middleware.
    """
    webhooks_client = WebhooksClient(platform_client)

    # Define Headers
    headers = [
        WebhookHeader(key="Content-Type", value="application/json")
    ]

    # Define Webhook Object
    webhook = Webhook(
        name="SLA Breach Middleware Trigger",
        description="Triggers middleware to check SLA and notify Slack",
        enabled=True,
        event_types=["routing:conversation:answer"],
        request_url=webhook_url,
        http_method="POST",
        content_type="application/json",
        headers=headers,
        retry_config={
            "max_retries": 3,
            "retry_interval_seconds": 5
        }
    )

    try:
        response = webhooks_client.post_webhook(webhook=webhook)
        print(f"Webhook created successfully. ID: {response.id}")
        return response
    except Exception as e:
        print(f"Error creating webhook: {e}")
        return None

# Usage
WEBHOOK_URL = os.getenv("WEBHOOK_URL", "https://your-ngrok-url.ngrok.io/webhook/genesys")
create_sla_webhook(platform_client, WEBHOOK_URL)

Complete Working Example

Below is the consolidated Python script that sets up the client and creates the webhook. You will still need to run the Flask middleware separately.

File: setup_slb_webhook.py

import os
import sys
from genesyscloud.platform_client import PlatformClient
from genesyscloud.auth import AuthClient
from genesyscloud.webhooks import WebhooksClient
from genesyscloud.webhooks.model import Webhook, WebhookHeader

def init_platform_client(client_id: str, client_secret: str) -> PlatformClient:
    platform_client = PlatformClient()
    auth_client = platform_client.auth
    try:
        auth_client.set_client_id(client_id)
        auth_client.set_client_secret(client_secret)
        auth_client.get_token()
    except Exception as e:
        raise RuntimeError(f"Authentication failed: {e}")
    return platform_client

def create_webhook(platform_client: PlatformClient, webhook_url: str) -> None:
    webhooks_client = WebhooksClient(platform_client)
    
    headers = [
        WebhookHeader(key="Content-Type", value="application/json")
    ]

    webhook = Webhook(
        name="SLA Breach Slack Notifier",
        description="Sends event to middleware for SLA check",
        enabled=True,
        event_types=["routing:conversation:answer"],
        request_url=webhook_url,
        http_method="POST",
        content_type="application/json",
        headers=headers,
        retry_config={
            "max_retries": 3,
            "retry_interval_seconds": 10
        }
    )

    try:
        response = webhooks_client.post_webhook(webhook=webhook)
        print(f"Success: Webhook created with ID {response.id}")
        print(f"Webhook URL: {response.request_url}")
    except Exception as e:
        print(f"Error: {e}")
        sys.exit(1)

if __name__ == "__main__":
    client_id = os.getenv("GENESYS_CLIENT_ID")
    client_secret = os.getenv("GENESYS_CLIENT_SECRET")
    webhook_url = os.getenv("WEBHOOK_URL")

    if not all([client_id, client_secret, webhook_url]):
        print("Missing environment variables: GENESYS_CLIENT_ID, GENESYS_CLIENT_SECRET, WEBHOOK_URL")
        sys.exit(1)

    try:
        client = init_platform_client(client_id, client_secret)
        create_webhook(client, webhook_url)
    except Exception as e:
        print(f"Fatal error: {e}")
        sys.exit(1)

File: middleware.py (Run this separately)

from flask import Flask, request, jsonify
import requests
import os

app = Flask(__name__)

SLACK_WEBHOOK_URL = os.getenv("SLACK_WEBHOOK_URL")
TARGET_QUEUE_ID = os.getenv("TARGET_QUEUE_ID")
SLA_TARGET_SECONDS = int(os.getenv("SLA_TARGET_SECONDS", "20"))

@app.route('/webhook/genesys', methods=['POST'])
def handle_webhook():
    if not request.is_json:
        return jsonify({"error": "Not JSON"}), 400
    
    data = request.json
    queue_id = data.get("queueId")
    wait_time_ms = data.get("waitTime", 0)
    conv_id = data.get("conversationId")
    
    if queue_id != TARGET_QUEUE_ID:
        return jsonify({"status": "skip"}), 200
        
    wait_time_s = wait_time_ms / 1000.0
    
    if wait_time_s > SLA_TARGET_SECONDS:
        msg = f":fire: **SLA Breach**\nQueue: {queue_id}\nConv: {conv_id}\nWait: {wait_time_s:.1f}s"
        requests.post(SLACK_WEBHOOK_URL, json={"text": msg})
        
    return jsonify({"status": "ok"}), 200

if __name__ == '__main__':
    app.run(port=5000)

Common Errors & Debugging

Error: 401 Unauthorized

Cause: The OAuth token is expired or invalid.
Fix: Ensure your GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET are correct. The AuthClient.get_token() call must succeed before making API calls. If using the SDK, it caches the token. If you restart the script, it will fetch a new one.

Error: 403 Forbidden

Cause: The OAuth Client lacks the webhook:write scope.
Fix: Go to Genesys Cloud Admin > Security > OAuth Clients. Edit your client and ensure webhook:write is checked. Save and regenerate the secret if necessary.

Error: 400 Bad Request (Webhook Creation)

Cause: The requestUrl is invalid or not HTTPS.
Fix: Genesys Cloud requires webhooks to use HTTPS. Ensure your ngrok URL or production endpoint uses https://. Also, verify the JSON structure of the Webhook object matches the SDK model strictly.

Error: Slack Returns 403 or 404

Cause: The Slack Incoming Webhook URL is incorrect or the channel ID has changed.
Fix: Regenerate the webhook URL in your Slack App settings. Ensure the app is added to the specific channel you want to post to.

Error: Webhook Not Triggering

Cause: The event type routing:conversation:answer only fires when an agent answers. If the call is abandoned, it will not fire.
Fix: If you need to track abandoned calls that breached SLA, you must also add routing:conversation:abandon to the event_types list and adjust the middleware logic to check waitTime for abandoned conversations as well.

Official References