How to Set Up a Genesys Cloud Webhook for Slack Notifications on Queue SLA Breach
What You Will Build
- One sentence: This tutorial builds a Python integration that subscribes to Genesys Cloud queue statistics, detects when a queue breaches its Service Level Agreement (SLA) threshold, and posts an alert to a Slack channel via a Genesys Cloud Webhook integration.
- One sentence: This uses the Genesys Cloud Platform API v2, specifically the Queue Statistics and Integrations Webhooks endpoints.
- One sentence: The implementation is covered in Python using the
genesys-cloud-sdkandrequestslibrary.
Prerequisites
- OAuth Client Type: Client Credentials Grant (Service Account).
- Required Scopes:
queue:statistics:view(to read queue performance metrics)integration:webhook:manage(to create and update webhook definitions)integration:webhook:execute(to trigger webhooks manually if needed)
- SDK Version: Genesys Cloud Python SDK v2.0+ (
genesys-cloud-sdk). - Language/Runtime: Python 3.9+.
- External Dependencies:
genesys-cloud-sdkrequestspyyaml(for configuration management)
- Slack Requirement: A Slack Incoming Webhook URL generated from the Slack App configuration.
Authentication Setup
Genesys Cloud uses OAuth 2.0 for API authentication. For server-to-server integrations, the Client Credentials Grant is the standard approach. You must obtain a Client ID and Client Secret from the Genesys Cloud Admin Console under Developers > Apps.
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.api_exception import ApiException
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def get_genesys_client(client_id: str, client_secret: str, environment: str = "my.genesys.cloud") -> PureCloudPlatformClientV2:
"""
Initializes and authenticates the Genesys Cloud Platform Client.
Args:
client_id: The OAuth Client ID.
client_secret: The OAuth Client Secret.
environment: The Genesys Cloud environment (e.g., 'usw2.platform.dev').
Returns:
PureCloudPlatformClientV2: The authenticated API client.
"""
try:
# Initialize the client with the provided credentials
client = PureCloudPlatformClientV2(client_id, client_secret, environment=environment)
# Verify authentication by fetching the user info (optional but recommended for validation)
# Note: In a production service account context, this might return limited info depending on scopes
client.login()
logger.info("Successfully authenticated with Genesys Cloud.")
return client
except ApiError as e:
logger.error(f"Authentication failed: {e.status_code} - {e.reason}")
raise
except Exception as e:
logger.error(f"Unexpected error during authentication: {str(e)}")
raise
# Configuration
GENESYS_CLIENT_ID = os.getenv("GENESYS_CLIENT_ID")
GENESYS_CLIENT_SECRET = os.getenv("GENESYS_CLIENT_SECRET")
GENESYS_ENVIRONMENT = os.getenv("GENESYS_ENVIRONMENT", "my.genesys.cloud")
if not GENESYS_CLIENT_ID or not GENESYS_CLIENT_SECRET:
raise ValueError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables must be set.")
genesys_client = get_genesys_client(GENESYS_CLIENT_ID, GENESYS_CLIENT_SECRET, GENESYS_ENVIRONMENT)
Implementation
Step 1: Define the Slack Webhook Integration
Before configuring the trigger, you must define the target integration in Genesys Cloud. This creates the “endpoint” in Genesys that knows how to talk to Slack. We will use the IntegrationsApi to create a webhook integration.
OAuth Scope Required: integration:webhook:manage
from genesyscloud.integrations.rest import IntegrationsApi
from genesyscloud.integrations.model import WebhookIntegrationRequest
from genesyscloud.integrations.model import WebhookIntegrationRequestBody
from genesyscloud.integrations.model import WebhookIntegrationHeader
def create_slack_webhook_integration(client: PureCloudPlatformClientV2, slack_webhook_url: str, integration_name: str = "Slack-Queue-Alerts") -> str:
"""
Creates a Genesys Cloud Webhook Integration pointing to a Slack Incoming Webhook URL.
Args:
client: The authenticated Genesys Cloud client.
slack_webhook_url: The full URL from Slack's Incoming Webhook app.
integration_name: A unique name for the integration within Genesys Cloud.
Returns:
str: The ID of the created integration.
"""
integrations_api = IntegrationsApi(client)
# Construct the header object. Slack expects application/json content type.
headers = [
WebhookIntegrationHeader(
key="Content-Type",
value="application/json"
)
]
# Construct the request body
body = WebhookIntegrationRequest(
name=integration_name,
description="Webhook integration for sending Slack notifications on queue SLA breaches",
enabled=True,
type="webhook",
url=slack_webhook_url,
headers=headers
)
try:
# Post the integration
response = integrations_api.post_integrationwebhook(body=body)
if response.id:
logger.info(f"Successfully created integration with ID: {response.id}")
return response.id
else:
raise ValueError("Integration creation succeeded but no ID was returned.")
except ApiError as e:
if e.status_code == 409:
logger.warning(f"Integration '{integration_name}' already exists. You may need to fetch the existing ID.")
# In a production app, you would query existing integrations here to find the ID
raise
elif e.status_code == 401 or e.status_code == 403:
logger.error("Insufficient permissions to create integration. Check scopes.")
raise
else:
logger.error(f"API Error creating integration: {e.status_code} - {e.reason}")
raise
except Exception as e:
logger.error(f"Unexpected error creating integration: {str(e)}")
raise
# Example Usage
SLACK_WEBHOOK_URL = os.getenv("SLACK_WEBHOOK_URL")
INTEGRATION_ID = create_slack_webhook_integration(genesys_client, SLACK_WEBHOOK_URL)
Step 2: Identify Queues and Define SLA Thresholds
To detect SLA breaches, you need to monitor specific queues. We will fetch all queues and allow the user to specify which ones to monitor. The SLA threshold (e.g., 80% of calls answered within 20 seconds) is defined in the Genesys Cloud Queue configuration, but for this tutorial, we will define a static threshold in our code for simplicity. In a production system, you might read the slaPercent and slaTime directly from the Queue object.
OAuth Scope Required: queue:view
from genesyscloud.routing.rest import RoutingApi
from genesyscloud.routing.model import QueueEntityListing
def get_monitored_queues(client: PureCloudPlatformClientV2, queue_ids: list = None) -> list:
"""
Fetches queue details for monitoring.
Args:
client: The authenticated Genesys Cloud client.
queue_ids: Optional list of specific queue IDs to fetch. If None, fetches the first page of all queues.
Returns:
list: A list of Queue objects.
"""
routing_api = RoutingApi(client)
queues = []
try:
if queue_ids:
# Fetch specific queues
for q_id in queue_ids:
queue = routing_api.get_routingqueue(queue_id=q_id)
queues.append(queue)
else:
# Fetch all queues (handle pagination if more than 250)
result = routing_api.get_routingqueues(page_size=250)
queues = result.entities if result.entities else []
# Handle pagination if total > page_size
while result.page_size < result.total:
result = routing_api.get_routingqueues(page_size=250, page_token=result.next_page_token)
if result.entities:
queues.extend(result.entities)
else:
break
logger.info(f"Fetched {len(queues)} queues for monitoring.")
return queues
except ApiError as e:
logger.error(f"Failed to fetch queues: {e.status_code} - {e.reason}")
raise
except Exception as e:
logger.error(f"Unexpected error fetching queues: {str(e)}")
raise
# Example Usage
ALL_QUEUES = get_monitored_queues(genesys_client)
Step 3: Check Queue Statistics and Detect SLA Breach
This is the core logic. We will use the QueueStatisticsApi to get real-time or interval-based statistics for the queues. We will check the percent field in the sla object against a threshold.
OAuth Scope Required: queue:statistics:view
from genesyscloud.routing.rest import RoutingApi
from genesyscloud.routing.model import QueueStatisticsQuery
from datetime import datetime, timedelta
def check_queue_sla(client: PureCloudPlatformClientV2, queue_id: str, sla_threshold_percent: float = 80.0) -> dict:
"""
Checks the current SLA performance for a specific queue.
Args:
client: The authenticated Genesys Cloud client.
queue_id: The ID of the queue to check.
sla_threshold_percent: The minimum SLA percentage considered healthy.
Returns:
dict: A dictionary containing the queue name, current SLA percent, and a boolean indicating if it breached.
"""
routing_api = RoutingApi(client)
# Define the time window for statistics (last 15 minutes)
end_time = datetime.utcnow()
start_time = end_time - timedelta(minutes=15)
# Format times for the API (ISO 8601)
start_time_str = start_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
end_time_str = end_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
# Construct the query
query = QueueStatisticsQuery(
queue_ids=[queue_id],
interval="interval", # Use 'interval' for aggregated stats over the time period
start_time=start_time_str,
end_time=end_time_str,
group_by="queue",
include_empty=True
)
try:
# Post the statistics query
response = routing_api.post_routingqueuestatsdetails(query=query)
if not response.entities:
logger.warning(f"No statistics found for queue {queue_id} in the specified time window.")
return {"queue_id": queue_id, "breached": False, "current_sla": 0, "queue_name": "Unknown"}
queue_stat = response.entities[0]
queue_name = queue_stat.queue_name
current_sla_percent = queue_stat.sla.percent if queue_stat.sla else 0
# Determine if SLA is breached
is_breached = current_sla_percent < sla_threshold_percent
logger.info(f"Queue '{queue_name}' (ID: {queue_id}) SLA: {current_sla_percent:.2f}% (Threshold: {sla_threshold_percent}%)")
return {
"queue_id": queue_id,
"queue_name": queue_name,
"current_sla": current_sla_percent,
"threshold": sla_threshold_percent,
"breached": is_breached
}
except ApiError as e:
logger.error(f"Failed to fetch statistics for queue {queue_id}: {e.status_code} - {e.reason}")
raise
except Exception as e:
logger.error(f"Unexpected error checking SLA for queue {queue_id}: {str(e)}")
raise
Step 4: Trigger the Webhook to Send Slack Notification
When a breach is detected, we use the IntegrationsApi to execute the webhook. We will format the payload as a JSON object that Slack can render nicely using its block kit or simple text format.
OAuth Scope Required: integration:webhook:execute
from genesyscloud.integrations.rest import IntegrationsApi
from genesyscloud.integrations.model import IntegrationExecutionRequest
from genesyscloud.integrations.model import IntegrationExecutionRequestBody
import json
def send_slack_alert(client: PureCloudPlatformClientV2, integration_id: str, queue_name: str, current_sla: float, threshold: float) -> bool:
"""
Sends a Slack notification via the Genesys Cloud Webhook Integration.
Args:
client: The authenticated Genesys Cloud client.
integration_id: The ID of the Slack webhook integration.
queue_name: The name of the queue that breached SLA.
current_sla: The current SLA percentage.
threshold: The SLA threshold percentage.
Returns:
bool: True if the webhook was triggered successfully.
"""
integrations_api = IntegrationsApi(client)
# Construct the Slack message payload
# Slack Incoming Webhooks accept a JSON body. We will use a simple text format for reliability.
message_text = (
f":rotating_light: *SLA Breach Alert*\n"
f"*Queue:* {queue_name}\n"
f"*Current SLA:* {current_sla:.2f}%\n"
f"*Threshold:* {threshold}%\n"
f"*Time:* {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC')}"
)
payload = {
"text": message_text,
"channel": "#alerts" # You can make this dynamic if needed
}
# Construct the execution request
execution_body = IntegrationExecutionRequestBody(
payload=payload
)
execution_request = IntegrationExecutionRequest(
body=execution_body
)
try:
# Trigger the webhook
response = integrations_api.post_integrationexecution(integration_id=integration_id, body=execution_request)
if response.status_code == 200 or response.status_code == 202:
logger.info(f"Successfully triggered Slack alert for queue '{queue_name}'.")
return True
else:
logger.warning(f"Webhook triggered but status code was {response.status_code}.")
return False
except ApiError as e:
logger.error(f"Failed to trigger webhook: {e.status_code} - {e.reason}")
raise
except Exception as e:
logger.error(f"Unexpected error triggering webhook: {str(e)}")
raise
Complete Working Example
The following script combines all steps into a single runnable module. It assumes you have set the environment variables GENESYS_CLIENT_ID, GENESYS_CLIENT_SECRET, SLACK_WEBHOOK_URL, and optionally QUEUE_IDS (comma-separated).
import os
import time
import logging
from genesyscloud import PureCloudPlatformClientV2
from genesyscloud.api_exception import ApiError
from genesyscloud.integrations.rest import IntegrationsApi
from genesyscloud.integrations.model import WebhookIntegrationRequest, WebhookIntegrationHeader, IntegrationExecutionRequest, IntegrationExecutionRequestBody
from genesyscloud.routing.rest import RoutingApi
from genesyscloud.routing.model import QueueStatisticsQuery
from datetime import datetime, timedelta
# Configure Logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def get_genesys_client(client_id: str, client_secret: str, environment: str = "my.genesys.cloud") -> PureCloudPlatformClientV2:
try:
client = PureCloudPlatformClientV2(client_id, client_secret, environment=environment)
client.login()
logger.info("Successfully authenticated with Genesys Cloud.")
return client
except ApiError as e:
logger.error(f"Authentication failed: {e.status_code} - {e.reason}")
raise
except Exception as e:
logger.error(f"Unexpected error during authentication: {str(e)}")
raise
def create_or_get_slack_integration(client: PureCloudPlatformClientV2, slack_webhook_url: str, integration_name: str = "Slack-Queue-Alerts") -> str:
integrations_api = IntegrationsApi(client)
headers = [WebhookIntegrationHeader(key="Content-Type", value="application/json")]
body = WebhookIntegrationRequest(
name=integration_name,
description="Webhook for Slack Queue SLA Alerts",
enabled=True,
type="webhook",
url=slack_webhook_url,
headers=headers
)
try:
response = integrations_api.post_integrationwebhook(body=body)
if response.id:
logger.info(f"Created integration ID: {response.id}")
return response.id
except ApiError as e:
if e.status_code == 409:
# Fetch existing integration by name
existing = integrations_api.get_integrationwebhooks(page_size=200)
for i in existing.entities:
if i.name == integration_name:
logger.info(f"Found existing integration ID: {i.id}")
return i.id
raise ValueError(f"Integration {integration_name} exists but could not be fetched.")
else:
raise
except Exception as e:
logger.error(f"Error creating integration: {str(e)}")
raise
def get_queue_ids(client: PureCloudPlatformClientV2, specific_ids: str = None) -> list:
routing_api = RoutingApi(client)
queue_ids = []
if specific_ids:
queue_ids = [id.strip() for id in specific_ids.split(",")]
else:
result = routing_api.get_routingqueues(page_size=250)
if result.entities:
queue_ids = [q.id for q in result.entities]
return queue_ids
def check_and_alert(client: PureCloudPlatformClientV2, integration_id: str, queue_id: str, sla_threshold: float = 80.0):
routing_api = RoutingApi(client)
end_time = datetime.utcnow()
start_time = end_time - timedelta(minutes=15)
query = QueueStatisticsQuery(
queue_ids=[queue_id],
interval="interval",
start_time=start_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
end_time=end_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
group_by="queue"
)
try:
response = routing_api.post_routingqueuestatsdetails(query=query)
if not response.entities:
return
stat = response.entities[0]
current_sla = stat.sla.percent if stat.sla else 0
queue_name = stat.queue_name
if current_sla < sla_threshold:
logger.warning(f"SLA Breach Detected: {queue_name} ({current_sla:.2f}% < {sla_threshold}%)")
# Send Alert
integrations_api = IntegrationsApi(client)
message = (
f":rotating_light: *SLA Breach*\n"
f"*Queue:* {queue_name}\n"
f"*SLA:* {current_sla:.2f}%\n"
f"*Threshold:* {sla_threshold}%\n"
f"*Time:* {end_time.strftime('%H:%M:%S UTC')}"
)
payload = {"text": message}
body = IntegrationExecutionRequestBody(payload=payload)
request = IntegrationExecutionRequest(body=body)
integrations_api.post_integrationexecution(integration_id=integration_id, body=request)
logger.info(f"Alert sent to Slack for {queue_name}")
except ApiError as e:
logger.error(f"Error checking stats for {queue_id}: {e.reason}")
except Exception as e:
logger.error(f"Unexpected error: {str(e)}")
def main():
# Configuration
client_id = os.getenv("GENESYS_CLIENT_ID")
client_secret = os.getenv("GENESYS_CLIENT_SECRET")
slack_url = os.getenv("SLACK_WEBHOOK_URL")
queue_ids_str = os.getenv("QUEUE_IDS") # Comma separated IDs, or empty for all
sla_threshold = float(os.getenv("SLA_THRESHOLD", "80.0"))
poll_interval = int(os.getenv("POLL_INTERVAL_SECONDS", "60"))
if not client_id or not client_secret or not slack_url:
raise ValueError("Missing required environment variables: GENESYS_CLIENT_ID, GENESYS_CLIENT_SECRET, SLACK_WEBHOOK_URL")
# Initialize
client = get_genesys_client(client_id, client_secret)
integration_id = create_or_get_slack_integration(client, slack_url)
queue_ids = get_queue_ids(client, queue_ids_str)
logger.info(f"Monitoring {len(queue_ids)} queues with SLA threshold {sla_threshold}%")
# Main Loop
try:
while True:
for q_id in queue_ids:
check_and_alert(client, integration_id, q_id, sla_threshold)
logger.info(f"Next check in {poll_interval} seconds...")
time.sleep(poll_interval)
except KeyboardInterrupt:
logger.info("Shutting down...")
except Exception as e:
logger.error(f"Fatal error: {str(e)}")
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 401 Unauthorized
- What causes it: The OAuth token is expired, invalid, or the Client ID/Secret is incorrect.
- How to fix it: Ensure your environment variables are set correctly. The Genesys Cloud SDK handles token refresh automatically, but if the initial
login()fails, check the credentials. - Code Fix: Verify the
get_genesys_clientfunction logs the specific error reason.
Error: 403 Forbidden
- What causes it: The OAuth client lacks the required scopes.
- How to fix it: Go to the Genesys Cloud Admin Console, navigate to Developers > Apps, select your application, and ensure
queue:statistics:view,integration:webhook:manage, andintegration:webhook:executeare added to the scopes. Save and restart your application.
Error: 429 Too Many Requests
- What causes it: You are polling queue statistics too frequently. Genesys Cloud has rate limits on API calls.
- How to fix it: Increase the
POLL_INTERVAL_SECONDSenvironment variable. A minimum of 30-60 seconds is recommended for queue statistics to avoid rate limiting. - Code Fix: Implement exponential backoff in the
check_and_alertloop if a 429 is received.
Error: Slack Message Not Formatting Correctly
- What causes it: The JSON payload sent to the webhook does not conform to Slack’s Incoming Webhook expectations.
- How to fix it: Ensure the
Content-Typeheader is set toapplication/jsonin the integration definition. Verify the payload structure using Slack’s Webhook Tester.