Genesys Cloud Webhook to Slack: Mapping `slaBreach` event payload to Slack blocks in Kotlin

I’m trying to wire up a webhook in Genesys Cloud that triggers when a queue SLA is breached, specifically targeting the routing.queue.sla.breach event. The goal is to push a formatted card to a Slack channel. I’ve got the webhook configured in GC to hit my Kotlin Spring Boot service, and the initial handshake works fine. I’m receiving the POST request with the event payload.

The problem is mapping the GC event structure to the Slack Block Kit format. The GC payload looks like this:

{
 "eventType": "routing.queue.sla.breach",
 "data": {
 "queueId": "abc-123",
 "queueName": "Support Tier 2",
 "slaMetric": "responseTime",
 "value": 45.2,
 "target": 30.0
 }
}

I need to transform this into Slack’s blocks array. Here is my Kotlin data class for the Slack payload:

data class SlackPayload(
 val channel: String,
 val blocks: List<Block>
)

data class Block(
 val type: String, // e.g., "section"
 val text: TextObject
)

When I send the resulting JSON to Slack’s chat.postMessage API, I get a 400 Bad Request with invalid_blocks. I suspect I’m missing the mrkdwn flag or nesting the text object incorrectly. Slack’s docs are dense, and I can’t find a clear example of mapping a simple metric breach to a section block.

Here is what I’ve tried so far:

  • Verified the webhook is firing by logging the raw request body in my Kotlin service. The eventType matches.
  • Hardcoded a Slack payload in Postman to ensure the Slack API endpoint is correct. That works.
  • Used Jackson to serialize my Kotlin data classes into JSON. Checked the output string. It looks valid JSON.
  • Tried setting text.value directly to the queue name. Still gets rejected.

Am I structuring the Block object wrong? Should I be using section type with a text field of type mrkdwn? I’ve been staring at the Slack API docs for an hour. The error message isn’t specific enough. Just need to know the correct JSON structure for a single section block containing plain text.

The issue is usually just how you’re mapping the nested objects. Genesys sends the queue details inside the routing object, not at the root. You need to extract routing.queue.name and routing.sla.breachThreshold specifically.

Here’s a quick Kotlin data class structure that maps cleanly to the Slack Block Kit. I use this pattern for most alerting webhooks. It keeps the JSON payload small and avoids serialization errors when Slack parses the blocks.

data class SlackAttachment(
 val type: String = "section",
 val text: SlackText
)

data class SlackText(
 val type: String = "mrkdwn",
 val text: String
)

fun buildBreachBlock(queueName: String, threshold: Int): List<SlackAttachment> {
 return listOf(
 SlackAttachment(
 text = SlackText(text = "*SLA Breach*: $queueName exceeded ${threshold} seconds")
 )
 )
}

Make sure your controller returns 200 OK immediately. Slack won’t retry if you hang the connection. Also, check the webhook signature header if you’re enabling verification. It’s easy to miss that step during setup.