Webhook to Slack on Queue SLA Breach returning 400 Bad Request

I’m trying to set up a webhook that fires when a queue breaches its SLA target. The idea is to get a notification in Slack so the team knows immediately. I’ve created a webhook in the admin portal and pointed it to our Slack Incoming Webhook URL. The payload I’m sending is pretty simple:

{
 "text": "Queue {{queue.name}} has breached SLA. Current wait: {{queue.waitTime}}"
}

When I test the webhook using the ‘Test’ button, I get a 400 Bad Request error. The response body says invalid_form. I know the Slack URL is correct because I can curl it manually and it works fine. I’m not sure if Genesys Cloud expects a different content type or if the JSON structure needs to be wrapped in something else. I’ve tried changing the content type to application/json explicitly in the webhook settings, but that didn’t help. Is there a specific format Slack expects from Genesys webhooks? I’ve been staring at this for an hour and can’t figure out what’s wrong with the payload. I’m using the default template provided by Genesys but modified the text field. Any pointers?

The docs state: “Webhooks must include a valid Content-Type header and a properly formatted JSON body.” You’re likely hitting a 400 because Slack expects application/json but your test payload might be missing the header or using incorrect variable syntax. Try this curl to verify the endpoint accepts standard JSON: curl -X POST -H "Content-Type: application/json" -d '{"text": "Test"}' https://hooks.slack.com/services/.... If that works, the issue is in how the webhook service constructs the payload.

Make sure your webhook configuration isn’t wrapping the JSON in extra quotes. Sometimes the platform auto-escapes strings. Check the raw request logs in the admin console. If the body looks like "{\"text\": \"...\"} instead of {"text": "..."}, you’ll need to adjust the payload template. The Slack Incoming Webhook API is strict about structure. Also, ensure your webhook has the correct scope to access queue metrics if you’re pulling live data.

Are you hitting the Slack webhook directly from Genesys Cloud or through an intermediate service?

The issue is almost certainly the variable syntax. Genesys Cloud webhooks don’t resolve {{queue.name}} inside the JSON body string like a template engine. They send the raw JSON structure with the actual data nested under data. Slack’s incoming webhook expects a flat text key. If you send that template string literally, Slack receives invalid JSON or a string it can’t parse as expected.

I ran into this exact 400 error last week while building a similar alert system in C#. The fix is to send the raw object and handle the formatting on the receiving end, or use a very specific payload structure if you’re using an intermediary like Azure Functions.

If you are hitting Slack directly, you can’t use template variables in the body. You have to send the full event payload and parse it. But since you can’t write code inside the Genesys webhook config, the best approach is to point the webhook to a lightweight API endpoint (like an Azure Function or a simple Express server) that transforms the data.

Here’s how the Genesys Cloud webhook payload actually looks when it hits your endpoint. Note that queue is nested inside data:

{
 "type": "queue",
 "id": "12345",
 "data": {
 "name": "Support Team",
 "waitTime": 120000
 }
}

Your C# Azure Function handler should look something like this to transform it for Slack:

public static async Task<IActionResult> Run(
 [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
 ILogger log)
{
 var body = await new StreamReader(req.Body).ReadToEndAsync();
 var payload = JsonConvert.DeserializeObject<GenesysEvent>(body);

 // Transform to Slack format
 var slackPayload = new {
 text = $"Queue {payload.Data.Name} has breached SLA. Current wait: {payload.Data.WaitTime / 1000}s"
 };

 var client = new HttpClient();
 var content = new StringContent(JsonConvert.SerializeObject(slackPayload), Encoding.UTF8, "application/json");
 
 // Post to Slack
 var response = await client.PostAsync(SLACK_WEBHOOK_URL, content);
 
 if (!response.IsSuccessStatusCode)
 {
 return new StatusCodeResult(500);
 }

 return new OkObjectResult("Posted to Slack");
}

public class GenesysEvent
{
 public string Type { get; set; }
 public string Id { get; set; }
 public QueueData Data { get; set; }
}

public class QueueData
{
 public string Name { get; set; }
 public long WaitTime { get; set; } // in milliseconds
}

This way, Genesys sends valid JSON, your function validates it, and you send valid JSON to Slack. Direct templating in the webhook body just doesn’t work as expected.