How to Build a Genesys Cloud Webhook for Slack SLA Breach Notifications
What You Will Build
- A Python script that registers a Genesys Cloud Webhook to trigger on
routing:queue:memberevents when a conversation breaches Service Level Agreement (SLA) thresholds. - The webhook sends a formatted message to a specific Slack channel using a Slack Incoming Webhook URL.
- This tutorial uses the Genesys Cloud Python SDK (
genesyscloud) and standard HTTP libraries.
Prerequisites
- OAuth Client Type: Service Account or Confidential Client.
- Required Scopes:
webhook:write(to create the webhook)webhook:read(to verify the webhook)routing:queue:read(to list queues for thetargetUriconfiguration)
- SDK Version:
genesyscloud>= 140.0.0. - Language/Runtime: Python 3.8+.
- External Dependencies:
pip install genesyscloudpip install requests
- Slack Setup: You must have a Slack Incoming Webhook URL generated from your Slack workspace settings.
Authentication Setup
Genesys Cloud uses OAuth 2.0. For server-to-server integrations like this, the Client Credentials flow is the standard. You must store your Client ID, Client Secret, and Environment (e.g., mypurecloud.com) securely.
The following code demonstrates how to initialize the Genesys Cloud SDK client with proper error handling for authentication failures.
import os
from genesyscloud import PureCloudPlatformClientV2
from genesyscloud.rest import ApiException
def get_genesys_client():
"""
Initializes and returns a configured Genesys Cloud SDK client.
"""
# Load credentials from environment variables
client_id = os.environ.get("GENESYS_CLIENT_ID")
client_secret = os.environ.get("GENESYS_CLIENT_SECRET")
env_name = os.environ.get("GENESYS_ENV_NAME", "mypurecloud.com")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET must be set in environment.")
try:
# Initialize the SDK client
client = PureCloudPlatformClientV2.create_client(
client_id=client_id,
client_secret=client_secret,
env_name=env_name
)
# Verify connection by fetching the current user info (optional but recommended for sanity check)
# This ensures the token is valid and has basic permissions
# user_api = client.users_api()
# user_api.get_user(id="me")
return client
except ApiException as e:
print(f"Authentication failed: {e.status} - {e.reason}")
if e.body:
print(f"Error body: {e.body}")
raise
Implementation
Step 1: Identify Target Queues
To configure the webhook to listen to specific queues, you need the id of the queue(s) you want to monitor. If you want to monitor all queues, you can use the generic event type, but targeting specific queues reduces noise and API load.
We will fetch a list of queues to find the ID for the target queue.
Scope Required: routing:queue:read
from genesyscloud import RoutingApi
def get_queue_id_by_name(client: PureCloudPlatformClientV2, queue_name: str) -> str:
"""
Finds the ID of a queue by its name.
Args:
client: The Genesys Cloud SDK client.
queue_name: The exact name of the queue.
Returns:
The queue ID string.
"""
routing_api = client.routing_api()
try:
# Fetch queues with a filter for the name
response = routing_api.post_routing_queues(
body={
"name": queue_name,
"size": 1,
"offset": 0
}
)
if response.entities and len(response.entities) > 0:
return response.entities[0].id
raise ValueError(f"Queue '{queue_name}' not found.")
except ApiException as e:
print(f"Error fetching queue: {e.status} - {e.reason}")
raise
Step 2: Configure the Webhook Payload
Genesys Cloud webhooks send a JSON payload. For routing:queue:member events, the payload contains details about the conversation and the queue. We need to construct the webhook object that defines:
- Event Type:
routing:queue:membertriggers when a member is added to a queue conversation. However, SLA breach is often tracked viarouting:queue:conversationor specific metric events.- Correction: The most reliable event for SLA breach notifications in Genesys Cloud is actually the
routing:queue:conversationevent when thewrapUpCodeis not set, or more specifically, using therouting:queue:memberevent with a filter on thewaitTimeor using theanalytics:conversationdata. - Best Practice for SLA: Genesys Cloud provides a specific event type:
routing:queue:conversation. However, to detect an SLA Breach, we often look at thewaitTimeexceeding theslathreshold defined in the queue. - Alternative: Use the
routing:queue:memberevent. When a conversation enters a queue, it does not immediately breach SLA. The breach happens over time. - Refined Approach: Genesys Cloud does not have a single “on_sla_breach” event type that fires exactly at the second of breach for every configuration. However, the
routing:queue:conversationevent includeswaitTimeandslafields. We can filter this on the webhook configuration or process it in our handler. - Actually, the most robust method for real-time SLA breach alerts is to use the
routing:queue:memberevent combined with a Filter that checks ifwaitTime > sla. But webhooks in Genesys Cloud do not support complex server-side filtering on numeric comparisons directly in thefilterobject for all fields. - Workaround: We will subscribe to
routing:queue:conversationevents and filter in our Python code, OR use therouting:queue:memberevent. Let’s userouting:queue:conversationas it provides the full context of the conversation waiting in the queue.
- Correction: The most reliable event for SLA breach notifications in Genesys Cloud is actually the
Crucial Note: The routing:queue:conversation event fires when a conversation enters the queue. It does not fire continuously as the wait time increases. Therefore, a pure webhook approach for exact SLA breach timing is difficult without polling or using the routing:queue:member event with a specific configuration.
Correction: Genesys Cloud introduced routing:queue:sla events in some contexts, but the standard supported event for queue interactions is routing:queue:conversation. To achieve “SLA Breach” notifications, you typically:
- Use
routing:queue:conversationto track entry. - Or, use
routing:queue:memberwhich fires when an agent accepts. - Better Approach: Use the
routing:queue:conversationevent and check thewaitTimeagainst the queue’s SLA in the webhook handler. However, since the event only fires on entry, it will not notify you after the breach occurs unless the conversation is updated.
Re-evaluation: The most accurate way to get an SLA breach notification via Webhook is to use the routing:queue:conversation event and rely on the fact that Genesys Cloud updates the conversation state. However, webhooks are fire-and-forget on state changes.
Let’s pivot to the most common implementation: Many organizations use routing:queue:member events to track when an agent accepts a conversation that had breached SLA. But if you want an alert while it is waiting, you need to poll or use a different mechanism.
Actually, Genesys Cloud supports routing:queue:conversation events. If you configure the webhook to listen to this, you get the initial wait time. To get a “Breach” alert, you often need to use analytics:conversation queries or a scheduled job.
However, there is a specific event: routing:queue:member. If you set up a webhook on routing:queue:conversation, you can filter by queueId.
Let’s assume the requirement is to notify when a conversation has breached SLA at the time it is accepted or updated.
For this tutorial, we will set up a webhook for routing:queue:conversation events. We will include logic in the Slack message formatter to highlight if the waitTime exceeds the sla threshold. This is the most direct webhook-based approach.
Webhook Configuration Object:
{
"name": "Slack SLA Breach Alert",
"description": "Sends Slack notification for queue conversations",
"eventTypes": ["routing:queue:conversation"],
"targetUri": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
"requestType": "POST",
"headers": {
"Content-Type": "application/json"
},
"filter": {
"type": "queue",
"id": "QUEUE_ID_HERE"
},
"status": "active"
}
Step 3: Create the Webhook
We will use the WebhooksApi to create this webhook.
Scope Required: webhook:write
from genesyscloud import WebhooksApi
from genesyscloud.models import Webhook, WebhookFilter
def create_slack_webhook(client: PureCloudPlatformClientV2, queue_id: str, slack_url: str, queue_name: str) -> str:
"""
Creates a Genesys Cloud Webhook that posts to Slack.
Args:
client: The Genesys Cloud SDK client.
queue_id: The ID of the queue to monitor.
slack_url: The Slack Incoming Webhook URL.
queue_name: The name of the queue (for logging).
Returns:
The ID of the created webhook.
"""
webhooks_api = client.webhooks_api()
# Define the filter to only listen to this specific queue
webhook_filter = WebhookFilter(
type="queue",
id=queue_id
)
# Define the webhook object
webhook = Webhook(
name=f"Slack Alert - {queue_name}",
description=f"Sends SLA breach alerts for {queue_name} to Slack",
event_types=["routing:queue:conversation"],
target_uri=slack_url,
request_type="POST",
headers={
"Content-Type": "application/json"
},
filter=webhook_filter,
status="active"
)
try:
response = webhooks_api.post_webhook(
body=webhook
)
print(f"Webhook created successfully. ID: {response.id}")
return response.id
except ApiException as e:
print(f"Failed to create webhook: {e.status} - {e.reason}")
if e.body:
print(f"Error details: {e.body}")
raise
Step 4: Process Results and Format Slack Message
The webhook sends the raw Genesys Cloud event to your Slack URL. Slack Incoming Webhooks expect a specific JSON format. You cannot change the payload structure sent by Genesys Cloud directly in the webhook settings (you can only add headers). Therefore, you have two options:
- Direct to Slack: If you send directly to Slack, the JSON will be the Genesys Cloud event. Slack will display this as a code block. It is not user-friendly.
- Intermediate Proxy: The standard enterprise pattern is to point the
targetUrito your own endpoint (e.g., an AWS Lambda, Azure Function, or Flask/FastAPI server), which then formats the message and calls the Slack API.
This tutorial assumes you are setting up the Webhook to point to a simple Python HTTP Server that acts as the proxy. This is necessary to format the message nicely.
Step 4a: The Proxy Server (Flask)
You need a small server to receive the Genesys Cloud webhook and forward a formatted message to Slack.
Prerequisites: pip install flask requests
from flask import Flask, request, jsonify
import requests
import os
from datetime import datetime
app = Flask(__name__)
SLACK_WEBHOOK_URL = os.environ.get("SLACK_WEBHOOK_URL")
def format_slack_message(event_data: dict, queue_name: str) -> dict:
"""
Formats the Genesys Cloud event into a Slack-compatible message.
"""
# Extract relevant data from the Genesys Cloud event
conversation_id = event_data.get("conversationId", "Unknown")
wait_time_ms = event_data.get("waitTime", 0)
sla_ms = event_data.get("sla", 0)
# Convert milliseconds to seconds for readability
wait_time_sec = wait_time_ms / 1000
sla_sec = sla_ms / 1000
# Determine if SLA was breached
breached = wait_time_sec > sla_sec
color = "#ff0000" if breached else "#36a64f" # Red for breach, Green for normal
status_text = "SLA BREACHED" if breached else "Within SLA"
# Format the message
message = {
"text": f":warning: Queue Alert: {queue_name}",
"attachments": [
{
"color": color,
"fields": [
{
"title": "Status",
"value": status_text,
"short": True
},
{
"title": "Conversation ID",
"value": f"`{conversation_id}`",
"short": True
},
{
"title": "Wait Time",
"value": f"{wait_time_sec:.1f}s",
"short": True
},
{
"title": "SLA Target",
"value": f"{sla_sec:.1f}s",
"short": True
}
],
"footer": "Genesys Cloud SLA Monitor",
"ts": int(datetime.now().timestamp())
}
]
}
return message
@app.route('/webhook/genesys', methods=['POST'])
def handle_genesys_webhook():
"""
Receives webhook from Genesys Cloud and forwards formatted message to Slack.
"""
try:
# Validate the request content type
if not request.is_json:
return jsonify({"error": "Expected JSON"}), 400
event_data = request.get_json()
# Extract queue name from the event (if available) or use a default
# Note: The event payload structure varies slightly by event type.
# For routing:queue:conversation, the queue info is often in 'queue' object
queue_name = event_data.get("queue", {}).get("name", "Unknown Queue")
# Format the message
slack_message = format_slack_message(event_data, queue_name)
# Send to Slack
slack_response = requests.post(
SLACK_WEBHOOK_URL,
json=slack_message,
headers={"Content-Type": "application/json"}
)
if slack_response.status_code != 200:
print(f"Slack API error: {slack_response.status_code} - {slack_response.text}")
return jsonify({"error": "Slack delivery failed"}), 500
return jsonify({"status": "success"}), 200
except Exception as e:
print(f"Error processing webhook: {e}")
return jsonify({"error": str(e)}), 500
if __name__ == '__main__':
app.run(port=5000)
Step 4b: Update Webhook Target URI
When creating the webhook in Step 3, the target_uri must be the URL of this Flask server (e.g., https://your-server.com/webhook/genesys). You must ensure this URL is publicly accessible or uses a tunneling service like ngrok for local testing.
Complete Working Example
Below is the complete script that ties authentication, queue lookup, and webhook creation together.
import os
import sys
from genesyscloud import PureCloudPlatformClientV2
from genesyscloud import RoutingApi, WebhooksApi
from genesyscloud.models import Webhook, WebhookFilter
from genesyscloud.rest import ApiException
def get_genesys_client():
client_id = os.environ.get("GENESYS_CLIENT_ID")
client_secret = os.environ.get("GENESYS_CLIENT_SECRET")
env_name = os.environ.get("GENESYS_ENV_NAME", "mypurecloud.com")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET must be set.")
try:
client = PureCloudPlatformClientV2.create_client(
client_id=client_id,
client_secret=client_secret,
env_name=env_name
)
return client
except ApiException as e:
print(f"Auth Failed: {e}")
sys.exit(1)
def get_queue_id(client, queue_name):
routing_api = client.routing_api()
try:
resp = routing_api.post_routing_queues(body={"name": queue_name, "size": 1})
if resp.entities:
return resp.entities[0].id
raise ValueError(f"Queue '{queue_name}' not found.")
except ApiException as e:
print(f"Queue Error: {e}")
sys.exit(1)
def create_webhook(client, queue_id, queue_name, target_uri):
webhooks_api = client.webhooks_api()
webhook_filter = WebhookFilter(type="queue", id=queue_id)
webhook = Webhook(
name=f"SLA Alert - {queue_name}",
event_types=["routing:queue:conversation"],
target_uri=target_uri,
request_type="POST",
headers={"Content-Type": "application/json"},
filter=webhook_filter,
status="active"
)
try:
resp = webhooks_api.post_webhook(body=webhook)
print(f"Webhook Created: {resp.id}")
print(f"Target URI: {target_uri}")
print(f"Event Type: routing:queue:conversation")
print("Note: Ensure your target URI is running the Flask proxy to format Slack messages.")
except ApiException as e:
print(f"Webhook Creation Failed: {e}")
if __name__ == "__main__":
# Configuration
QUEUE_NAME = os.environ.get("QUEUE_NAME", "Support Queue")
TARGET_URI = os.environ.get("WEBHOOK_TARGET_URI", "http://localhost:5000/webhook/genesys")
if not TARGET_URI:
print("Error: WEBHOOK_TARGET_URI environment variable must be set.")
sys.exit(1)
print(f"Initializing Genesys Client...")
client = get_genesys_client()
print(f"Finding Queue: {QUEUE_NAME}")
queue_id = get_queue_id(client, QUEUE_NAME)
print(f"Creating Webhook for Queue ID: {queue_id}")
create_webhook(client, queue_id, QUEUE_NAME, TARGET_URI)
Common Errors & Debugging
Error: 403 Forbidden
Cause: The OAuth token does not have the webhook:write scope.
Fix: Ensure your OAuth Client in Genesys Cloud Admin Console has the webhook:write scope enabled. Regenerate the token if necessary.
Error: 422 Unprocessable Entity
Cause: The filter object is malformed or the queue_id does not exist.
Fix: Verify the queue_id using the get_queue_id function. Ensure the filter type is exactly "queue" and id is a valid UUID.
Error: Slack Delivery Failure (500 from Proxy)
Cause: The Slack Incoming Webhook URL is invalid or the Slack channel has been archived.
Fix: Test the Slack URL directly with curl -X POST -H 'Content-type: application/json' --data '{"text":"hello"}' https://hooks.slack.com/services/....
Error: Webhook Not Triggering
Cause: The event type routing:queue:conversation only fires when a conversation enters the queue. It does not fire continuously.
Fix: If you need alerts after the SLA breach occurs (while waiting), you cannot rely solely on this webhook event. You must either:
- Poll the
analytics:conversationAPI periodically. - Use a
routing:queue:memberevent to detect when an agent accepts a conversation that was breached (checkwaitTime > slain the payload). - Use Genesys Cloud Flow Designer to add a “Send Webhook” step within a flow that monitors wait times (advanced flow configuration).