Skip to content

Interactive Controls

Add buttons and other interactive components to messages. When a user clicks a control, your bot receives a ControlInteraction event.

How Controls Work #

Controls are interactive UI elements attached to messages. They're sent as an optional controls array alongside the message text and entities. Controls are organized into rows — up to 5 rows with up to 5 controls each (25 total).

Two button variants: Callback buttons fire a ControlInteraction event with the button's id. Link buttons open a URL in the browser — no event is fired.

Control Schema #

Field Type Description
type "button" Control type. Currently only button.
variant "callback" | "link" What happens on click.
label string Button text. Max 80 chars.
id string? Developer-defined ID. Required for callback, forbidden for link. Max 100 chars.
url string? URL for link buttons. Must be HTTPS. Required for link, forbidden for callback.
colour OklchColor? Accent colour in OKLCH colour space.
labelLocalizations Record<string, string>? Localized labels keyed by BCP-47 tag (e.g. "ru", "en-US").
disabled boolean? If true, button is visible but non-interactive.

OKLCH Colours #

Buttons use OKLCH colours for theme-adaptive rendering. The client adjusts lightness to match the active theme while preserving hue and chroma.

Component Range Description
l 0.40 — 0.80 Perceived lightness
c 0.00 — 0.37 Chroma (saturation)
h 0 — 360 Hue angle (degrees, exclusive 360)
// Common colours
{ "l": 0.72, "c": 0.19, "h": 142 }  // green
{ "l": 0.55, "c": 0.25, "h": 29  }  // red
{ "l": 0.65, "c": 0.20, "h": 260 }  // blue
{ "l": 0.75, "c": 0.18, "h": 85  }  // yellow

Colour Visualizer

oklch(0.65 0.20 260)
{ "l": 0.65, "c": 0.20, "h": 260 }
0.65
0.20
260
Presets:
Button preview

Sending Controls #

Pass controls in your IMessages/Send or IInteractions/Reply request.

curl — send a message with buttons

curl -X POST /IMessages/v1/Send \
  -H "Authorization: Bot YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channelId": "...",
    "text": "Deploy to production?",
    "randomId": 42,
    "controls": [
      {
        "controls": [
          {
            "type": 0,
            "variant": 0,
            "label": "✅ Deploy",
            "id": "deploy_confirm",
            "colour": { "l": 0.72, "c": 0.19, "h": 142 }
          },
          {
            "type": 0,
            "variant": 0,
            "label": "❌ Cancel",
            "id": "deploy_cancel",
            "colour": { "l": 0.55, "c": 0.25, "h": 29 }
          },
          {
            "type": 0,
            "variant": 1,
            "label": "📚 Runbook",
            "url": "https://wiki.example.com/deploy"
          }
        ]
      }
    ]
  }'

C# — MessageBuilder

// Fluent builder for messages with entities + controls
var (text, entities, controls) = new MessageBuilder()
    .Text("Deploy to production?")
    .Row(row => row
        .CallbackButton("deploy_confirm", "✅ Deploy",
            colour: new OklchColor(0.72f, 0.19f, 142))
        .CallbackButton("deploy_cancel", "❌ Cancel",
            colour: new OklchColor(0.55f, 0.25f, 29))
        .LinkButton("📚 Runbook", "https://wiki.example.com/deploy"))
    .Build();

await channel.SendMessage(text, entities, randomId, replyTo: null, controls);

C# — Rich text with entities + controls

var (text, entities, controls) = new MessageBuilder()
    .Bold("Build #847")
    .Text(" passed on ")
    .Italic("main")
    .Text(" — ")
    .Url("ci.example.com", "/builds/847")
    .Row(row => row
        .CallbackButton("deploy", "Deploy",
            colour: new OklchColor(0.72f, 0.19f, 142))
        .CallbackButton("logs", "View Logs"))
    .Build();

Handling Interactions #

When a user clicks a Callback button, your SSE stream receives a ControlInteraction event. Subscribe to the ControlInteractions intent (bit 13, value 8192).

Event payload

event: ControlInteraction
data: {
  "interactionId": "a1b2c3d4-...",
  "controlType": 0,
  "messageId": 123456789,
  "channelId": "...",
  "spaceId": "...",
  "user": {
    "userId": "...",
    "username": "alice",
    "displayName": "Alice",
    "avatarUrl": null
  },
  "controlId": "deploy_confirm"
}

Responding — reply with updated controls

// Handle the interaction in your SSE event loop
if (eventType == "ControlInteraction") {
    var data = JsonSerializer.Deserialize<ControlInteractionEvent>(payload);

    if (data.ControlId == "deploy_confirm") {
        // Reply to the channel
        await httpClient.PostAsJsonAsync("/IInteractions/v1/Reply", new {
            channelId = data.ChannelId,
            text      = "🚀 Deploying...",
            randomId  = Random.Shared.NextInt64()
        });

        // Disable the buttons on the original message
        await httpClient.PatchAsJsonAsync("/IInteractions/v1/EditMessage", new {
            channelId = data.ChannelId,
            messageId = data.MessageId,
            controls  = new[] {
                new { controls = new[] {
                    new { type = 0, variant = 0, label = "✅ Deployed", id = "deploy_confirm", disabled = true },
                    new { type = 0, variant = 0, label = "❌ Cancel", id = "deploy_cancel", disabled = true }
                }}
            }
        });
    }
}

TypeScript — full interaction handler

const sse = new EventSource(
  `/IEvents/v1/Stream?intents=${8192}`,
  { headers: { Authorization: `Bot ${TOKEN}` } }
);

sse.addEventListener("ControlInteraction", async (e) => {
  const { controlId, channelId, messageId, user } = JSON.parse(e.data);

  switch (controlId) {
    case "approve":
      await fetch("/IInteractions/v1/Reply", {
        method: "POST",
        headers: { Authorization: `Bot ${TOKEN}`, "Content-Type": "application/json" },
        body: JSON.stringify({
          channelId,
          text: `Approved by ${user.displayName}`,
          randomId: Date.now(),
        }),
      });
      break;
  }
});

Editing Controls #

Use PATCH /IInteractions/v1/EditMessage to update the text or controls on a message your bot sent. Pass an empty controls: [] array to remove all controls.

Limits

  • Max 5 rows per message, 5 controls per row (25 total)
  • Control id values must be unique across all rows
  • Link button URLs must be https:// or http://
  • Bots can only edit their own messages
  • Rate limit: 15 requests/min for IInteractions