Skip to main content
Channels connect your deployed agents to the surfaces where users interact: websites, messaging apps, and voice calls. The Agent Platform (Artemis) provides channel adapters that handle the protocol-specific details, so a single agent definition works across every channel without modification. This guide covers embedding a web chat or voice widget, connecting to Slack, WhatsApp, and voice, and using rich content and file attachments across channels. A channel always binds to a deployment in a specific environment. Configure deployments first, then add channels on top. For the deployment lifecycle and the Deployments -> Channels workspace, see Deploy Agents.

How channels connect

The platform exposes two connection mechanisms. The one you use depends on the surface.
MechanismUsed byHow it works
SDK channelWeb chat and voice widgetsA public API key (pk_) bootstraps a browser session. You embed the Web SDK or the <agent-widget> web component in your page.
Channel connectionSlack, WhatsApp, voice, and other messaging adaptersYou register the external provider’s credentials. The platform returns a webhook URL that the provider calls when a user sends a message.
Both mechanisms route to the same agent definition. The adapter normalizes inbound and outbound formats, so your agent doesn’t need channel-specific logic for basic conversations. Use rich content when you want to tailor formatting to a specific channel.
SurfaceAdapterConnection
Web chat and voiceWeb SDK, <agent-widget>SDK channel (pk_ key)
SlackSlack adapterChannel connection
WhatsAppMeta Cloud API, Infobip, TwilioChannel connection
Microsoft TeamsBot FrameworkChannel connection
EmailInbound MIME parsingChannel connection
TelegramTelegram adapterChannel connection
VoiceKore Voice Gateway, AudioCodes, Twilio, BYOC SIPChannel connection

Deploy on Web

Deploy your agent on a website with the Web SDK. The SDK embeds a chat or voice widget that connects to your deployed agent through a public API key. The browser-facing SDK never sends the public pk_ key directly to the runtime. It exchanges the key for a short-lived SDK session token, then authenticates each request with that token. For the full SDK reference, see Agent Platform SDKs Overview.

Create a Public API Key

The Web SDK authenticates with a public key (pk_ prefix). Public keys are scoped to a project and are safe to expose in client-side code.
1

Open API keys

Go to Project -> Settings -> API Keys.
2

Create the key

Click Create API key and select the SDK key type.
3

Set allowed origins

Add the website origins that are allowed to use the key. The runtime validates the Origin header on every SDK request and rejects requests from unlisted origins.
4

Copy the key

Copy the key. It’s displayed only once.

Configure the Web SDK channel

Bind the channel to a deployment so the widget knows which agents to serve.
  1. Go to Deployments -> Channels.
  2. Add a Web SDK channel and select the target environment or deployment. The channel can follow an environment automatically or pin to a specific deployment version.
  3. Associate the public API key you created.

Embed the widget

Add the widget to any page with the web component, or use the SDK directly for full control over the interface.
The <agent-widget> web component is the fastest way to add a widget. It renders a chat button in the configured position and opens the chat interface when clicked.
<script src="https://agents.kore.ai/sdk/agent-widget.js"></script>

<agent-widget
  project-id="your-project-id"
  api-key="pk_your-public-key"
  mode="chat"
  position="bottom-right"
></agent-widget>
Set mode to chat, voice, or unified. Set position to bottom-right, bottom-left, top-right, or top-left.

Enable Voice

Set the widget mode to voice for a voice-only experience, or unified to offer chat and voice together.
<agent-widget
  project-id="your-project-id"
  api-key="pk_your-public-key"
  mode="unified"
  position="bottom-right"
></agent-widget>
When you build your own interface, obtain the voice client with sdk.voice() and call start() to request microphone access. Voice features require HTTPS in production, because microphone access needs a secure context. For voice states, events, and configuration, see the VoiceClient reference.

Restrict to specific origins

Origin restrictions prevent unauthorized sites from embedding your widget. Set the allowed origins on the public API key under Project -> Settings -> API Keys.
{
  "allowedOrigins": [
    "https://www.example.com",
    "https://app.example.com"
  ]
}
Requests from origins that aren’t listed receive a 403 Forbidden response.

Pass user context

Identify authenticated users by attaching metadata to a message. The metadata is validated server-side and is available to your agent for that turn through session.messageMetadata.
const chat = sdk.chat();

await chat.send('Look up my account', {
  metadata: {
    accountId: 'acct_123',
    plan: 'premium',
  },
});
Use session variables and persistent memory lookups in your agent to act on this context. See Memory and State.

Target a specific deployment

To point the widget at a staging or production deployment, set the channel’s binding under Deployments -> Channels. Pin the channel to a specific deployment version, or let it follow an environment so it always serves the active deployment for that environment.

Troubleshooting

SymptomResolution
Widget doesn’t appearVerify the script URL and that the api-key is valid. Check the browser console for errors.
”Invalid or expired API key”The key may have been rotated or the channel deactivated. Create a new key under Project -> Settings -> API Keys.
CORS errorsAdd the website’s origin to the key’s allowedOrigins list.
Widget loads but the agent doesn’t respondConfirm an active deployment exists for the project and the channel is bound to it.

Set up Slack

Connect your agent to Slack so users can interact with it directly in Slack channels and direct messages. Slack uses a channel connection: you register your Slack app’s credentials with the platform, then point Slack’s event webhook at the URL the platform returns.

Create a Slack app

1

Create the app

Go to api.slack.com/apps and select Create New App -> From scratch. Name the app and select the workspace.
2

Add bot token scopes

Under OAuth & Permissions, add the bot token scopes your agent needs.
ScopePurpose
chat:writeSend messages.
app_mentions:readRespond to @mentions.
im:read, im:writeHandle direct messages.
files:readRead file attachments, if your agent handles them.
3

Install and copy credentials

Install the app to your workspace and copy the Bot User OAuth Token (xoxb-...). Under Basic Information, copy the Signing Secret.

Create a channel connection

Register the Slack app credentials with the platform. The response includes the webhookUrl. Copy it for the next step.
curl -X POST https://your-platform/api/projects/$PROJECT_ID/channel-connections \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_type": "slack",
    "display_name": "Production Slack",
    "external_identifier": "T12345678:A12345678",
    "credentials": {
      "bot_token": "xoxb-your-bot-token",
      "signing_secret": "your-signing-secret"
    },
    "environment": "production"
  }'

Configure the Slack webhook

1

Enable event subscriptions

In your Slack app settings, go to Event Subscriptions, enable events, and paste the webhookUrl from the channel connection response. Slack sends a verification challenge, and the platform responds automatically.
2

Subscribe to bot events

Under Subscribe to bot events, add message.im for direct messages and app_mention for @mentions in channels. Save your changes.

Threaded replies

The Slack adapter supports threaded conversations and enables them by default. When a user mentions the bot in a channel, the agent replies in a thread to keep the channel clean.

Slash commands

Register a custom slash command that routes to your agent.
  1. In your Slack app settings, go to Slash Commands and create a command, for example /ask.
  2. Set the request URL to https://your-platform/api/v1/channels/slack/slash/{connection-identifier}.
  3. The connection identifier is the external identifier from your channel connection, in the format team_id:app_id.

Interactive components

If your agent uses interactive elements such as buttons or select menus, enable interactivity in your Slack app.
  1. Go to Interactivity & Shortcuts and toggle it on.
  2. Set the request URL to the same webhook URL you used for events.
  3. The platform routes interactive payloads, such as button clicks and menu selections, to your agent automatically.
See Rich content for the Slack Block Kit and interactive action syntax.

Multi-workspace apps

To distribute your agent across multiple Slack workspaces, use the generic webhook URL without a connection identifier. The platform extracts the workspace identifier from the event payload.
https://your-platform/api/v1/channels/slack/webhook
Create a separate channel connection for each workspace that installs the app.

Troubleshooting

SymptomResolution
”Channel not configured for this workspace”The connection’s external identifier doesn’t match the workspace. Verify the team_id:app_id matches your Slack app installation.
Bot doesn’t respondConfirm the Event Subscriptions URL verification succeeded (green checkmark in Slack) and the bot_token includes the chat:write scope.
Signature verification failsThe signing_secret must match the Signing Secret on the app’s Basic Information page. Secrets are case-sensitive.
Duplicate responsesSlack requires a response within 3 seconds, so the platform queues messages and responds asynchronously. If your app retries events, the platform deduplicates using event IDs.

Set up WhatsApp

Connect your agent to WhatsApp so users can interact with it through WhatsApp messages. WhatsApp uses a channel connection. You register your WhatsApp Business API credentials with the platform, then point your provider’s webhook at the URL the platform returns.

Set up a WhatsApp Business API account

Before connecting to the platform, set up a WhatsApp Business API provider account. The platform supports Meta’s Cloud API directly and third-party providers such as Infobip and Twilio. For Meta Cloud API:
  1. Go to developers.facebook.com and create a Meta app with the WhatsApp product enabled.
  2. Under WhatsApp -> Getting Started, note your Phone Number ID and WhatsApp Business Account ID.
  3. Generate a permanent System User Access Token with the whatsapp_business_messaging permission.
  4. Under Configuration, note the App Secret for webhook signature verification.

Create a channel connection

Register the WhatsApp credentials with the platform. The response includes the webhookUrl.
curl -X POST https://your-platform/api/projects/$PROJECT_ID/channel-connections \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_type": "whatsapp",
    "display_name": "Production WhatsApp",
    "external_identifier": "your-phone-number-id",
    "credentials": {
      "access_token": "your-system-user-access-token",
      "app_secret": "your-app-secret",
      "verify_token": "a-random-string-you-choose"
    },
    "config": {
      "phoneNumberId": "your-phone-number-id"
    },
    "environment": "production"
  }'

Configure the Meta webhook

  1. In your Meta app settings, go to WhatsApp -> Configuration.
  2. Under Webhook, click Edit and paste the webhookUrl.
  3. Enter the same verify_token you used when creating the channel connection.
  4. Click Verify and Save. Meta sends a GET request to verify the webhook.
  5. Under Webhook fields, subscribe to messages.

Connect through Infobip

For WhatsApp through Infobip, use the provider-specific configuration.
curl -X POST https://your-platform/api/projects/$PROJECT_ID/channel-connections \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_type": "whatsapp",
    "display_name": "Infobip WhatsApp",
    "external_identifier": "your-sender-number",
    "credentials": {
      "access_token": "your-infobip-api-key",
      "app_secret": "your-webhook-secret",
      "verify_token": "unused-for-infobip"
    },
    "config": {
      "provider": "infobip",
      "phoneNumberId": "your-sender-number"
    }
  }'
Set the Infobip webhook URL to:
https://your-platform/api/v1/channels/whatsapp/infobip/webhook

Media support

The WhatsApp adapter receives images, documents, audio, and video from users. The adapter downloads media files and routes them through the attachment pipeline automatically, so no extra configuration is needed. See File attachments for how your agent processes uploaded media.

Interactive messages

Your agent can send interactive WhatsApp messages, such as buttons and lists, through the WHATSAPP rich content format.
FLOW:
  offer_options:
    REASONING: false
    RESPOND: "How can I help you today?"
    RICH_CONTENT:
      WHATSAPP: |
        {
          "type": "interactive",
          "interactive": {
            "type": "list",
            "body": {"text": "Choose a topic:"},
            "action": {
              "button": "Select",
              "sections": [
                {
                  "title": "Support",
                  "rows": [
                    {"id": "billing", "title": "Billing"},
                    {"id": "technical", "title": "Technical Issue"}
                  ]
                }
              ]
            }
          }
        }
    THEN: handle_selection

Troubleshooting

SymptomResolution
Webhook verification failsThe verify_token in the channel connection must match the value entered in the Meta webhook configuration. It’s a plain string you choose, not a Meta-generated secret.
Messages don’t arriveConfirm you subscribed to the messages webhook field in Meta’s configuration, and that the phone number is registered with an active WhatsApp Business account.
Signature verification fails on inbound messagesThe app_secret must be the Meta App Secret from Basic Settings, not the system user access token.
Media messages aren’t processedLarge media files can time out during download. Check the attachment processing logs for the session.

Set up Voice

Set up a voice channel so users can interact with your agent through phone calls. The platform uses speech-to-text and text-to-speech for natural voice conversations, and supports Kore Voice Gateway, AudioCodes, Twilio, and BYOC SIP trunks. Each uses a channel connection.

Kore Voice Gateway

Create a channel connection for Kore Voice Gateway. The response includes a SIP endpoint to point your phone number at.
curl -X POST https://your-platform/api/projects/$PROJECT_ID/channel-connections \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_type": "korevg",
    "display_name": "Voice Support Line",
    "external_identifier": "support-line",
    "config": {
      "provider": "korevg",
      "welcomeMessage": "Hello! How can I help you today?",
      "voiceConfig": {
        "sttProvider": "deepgram",
        "sttLanguage": "en-US",
        "ttsProvider": "elevenlabs",
        "ttsVoice": "rachel"
      }
    },
    "environment": "production"
  }'

Twilio Voice

For Twilio-based voice, create a channel connection with Twilio credentials, then configure the Twilio phone number’s webhook to point to the platform’s voice endpoint.
curl -X POST https://your-platform/api/projects/$PROJECT_ID/channel-connections \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_type": "voice_twilio",
    "display_name": "Twilio Voice",
    "external_identifier": "+15551234567",
    "credentials": {
      "account_sid": "your-twilio-account-sid",
      "auth_token": "your-twilio-auth-token"
    },
    "config": {
      "phoneNumber": "+15551234567"
    },
    "environment": "production"
  }'

Use caller identity

For voice sessions, the runtime makes caller identity available through the session namespace. Your agent can pass the caller’s number or the dialed number into tools without asking the caller to repeat it.
FieldDescription
session.anonymousIdNormalized caller identity. For phone calls, this is the caller ANI when the gateway provides a valid phone number.
session.sessionPrincipalIdThe recognized session principal used for contact resolution and session ownership checks.
session.calledNumberNormalized number the caller dialed, when available.
session.rawCallerIdRaw caller value before phone normalization.
session.rawFromRaw upstream From value before phone normalization.
session.rawCalledNumberRaw called value before phone normalization.
session.rawToRaw upstream To value before phone normalization.
The runtime normalizes phone-like values from the gateway. For example, sip:+15551234567@example.com and +1 (555) 123-4567 both become +15551234567. If the first caller value isn’t a phone number, the runtime checks trusted SIP identity headers before falling back to the raw identity value. Use the normalized fields for most tools:
TOOLS:
  lookup_customer(phone: string, called_number?: string) -> object
    description: "Find the customer record for a voice caller."

FLOW:
  identify_caller

  identify_caller:
    REASONING: false
    CALL: lookup_customer
      WITH:
        phone: session.anonymousId
        called_number: session.calledNumber
      AS: customer
    RESPOND: "Thanks. I found your account."
Use raw fields only when your integration needs the original gateway value for audit or carrier-specific troubleshooting.
CALL: record_voice_context
  WITH:
    caller_id: session.anonymousId
    raw_from: session.rawFrom
    called_number: session.calledNumber
    raw_to: session.rawTo
  AS: voiceContext
Caller identity fields can contain phone numbers or carrier-provided identifiers, so treat them as end-user identity data. Don’t pass raw fields to external tools unless the tool needs the original gateway value.

Add voice-specific responses

Use VOICE: blocks to provide voice-optimized output alongside text. The block accepts ssml, instructions, and plain_text properties.
FLOW:
  welcome:
    REASONING: false
    RESPOND: "Welcome to Acme Support. How can I help?"
    VOICE:
      instructions: "Use a warm, professional tone. Speak at a moderate pace."

  confirm_booking:
    REASONING: false
    RESPOND: "Your booking is confirmed. Confirmation number: {{confirmation_id}}."
    VOICE:
      instructions: "Read the confirmation number slowly and clearly, one character at a time."
      ssml: |
        <speak>
          Your booking is confirmed.
          Confirmation number: <say-as interpret-as="characters">{{confirmation_id}}</say-as>.
        </speak>

BYOC SIP

Connect your existing SIP trunk with the bring-your-own-carrier provider.
curl -X POST https://your-platform/api/projects/$PROJECT_ID/channel-connections \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_type": "korevg",
    "display_name": "Enterprise SIP",
    "external_identifier": "enterprise-sip",
    "config": {
      "provider": "byoc_sip",
      "sipGateway": "192.168.1.100:5060",
      "voiceConfig": {
        "sttProvider": "deepgram",
        "ttsProvider": "elevenlabs",
        "ttsVoice": "rachel"
      }
    }
  }'

AudioCodes Voice Gateway

For AudioCodes VoiceAI Connect integration:
curl -X POST https://your-platform/api/projects/$PROJECT_ID/channel-connections \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_type": "audiocodes",
    "display_name": "AudioCodes Voice",
    "external_identifier": "audiocodes-main",
    "config": {
      "botUrl": "https://your-platform/api/v1/channels/audiocodes/webhook"
    }
  }'

Browser-based Voice

Generate a Twilio token for browser-based voice calls from your web application.
curl -X POST https://your-platform/api/v1/voice/token \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "your-project-id",
    "identity": "user-123"
  }'
Use the returned token with the Twilio Client SDK in your frontend:
import { Device } from '@twilio/voice-sdk';

const device = new Device(token);
await device.register();

const call = await device.connect({
  params: { projectId: 'your-project-id' },
});

Control speech with SSML

Use SSML for fine-grained control over speech output, such as pauses, emphasis, and pronunciation.
RESPOND: "Your account balance is ${{balance}}."
VOICE:
  ssml: |
    <speak>
      Your account balance is
      <say-as interpret-as="currency">USD{{balance}}</say-as>.
      <break time="500ms"/>
      Is there anything else I can help with?
    </speak>

Troubleshooting

SymptomResolution
No audio in callsConfirm the STT and TTS providers are configured with valid API keys. Check the voice service health endpoint.
Webhook signature validation fails (Twilio)The auth_token must match the Twilio account’s Auth Token. The platform uses it to validate X-Twilio-Signature on incoming webhooks.
Voice responses cut offLong responses can exceed TTS limits. Keep voice responses concise, or use SSML <break> tags to create natural pauses.
SIP registration failsFor BYOC SIP, verify the SIP gateway IP and port, and confirm the gateway allows connections from the platform’s IP range.

Rich Content

Use rich content to send formatted responses that adapt to each channel’s capabilities: Markdown, Adaptive Cards, Slack Block Kit, WhatsApp interactive messages, carousels, and interactive elements. Attach a RICH_CONTENT: block to any RESPOND statement. The runtime selects the format that matches the delivery channel and falls back to the plain text RESPOND message when no matching format exists. For the complete reference, see Rich Content and Expressions.
FormatDescription
MARKDOWNFormatted Markdown text.
ADAPTIVE_CARDJSON conforming to the Microsoft Adaptive Cards schema.
HTMLHTML content for web-based channels.
SLACKJSON conforming to the Slack Block Kit format.
WHATSAPPJSON for WhatsApp interactive messages.
AG_UIJSON for AG-UI events.
CAROUSELA scrollable collection of cards.

Add Rich Content to a response

Provide channel-specific formatting alongside the plain text response.
FLOW:
  show_results:
    REASONING: false
    RESPOND: "Here are your search results."
    RICH_CONTENT:
      MARKDOWN: |
        ## Search Results

        | Hotel | Price | Rating |
        |-------|-------|--------|
        {{#each hotels}}
        | {{name}} | ${{price}} | {{rating}} stars |
        {{/each}}
If you don’t provide a channel-specific format, the runtime falls back to the plain text RESPOND message.

Adaptive Cards for Microsoft Teams

Send rich interactive cards on Teams with the Adaptive Cards format.
RESPOND: "Your booking summary"
RICH_CONTENT:
  ADAPTIVE_CARD: |
    {
      "type": "AdaptiveCard",
      "version": "1.5",
      "body": [
        {"type": "TextBlock", "text": "Booking Confirmed", "size": "large", "weight": "bolder"},
        {"type": "FactSet", "facts": [
          {"title": "Hotel", "value": "{{hotel_name}}"},
          {"title": "Dates", "value": "{{checkin}} - {{checkout}}"},
          {"title": "Total", "value": "${{total}}"}
        ]}
      ]
    }

Slack Block Kit

Send formatted messages with Slack blocks.
RESPOND: "Your order status"
RICH_CONTENT:
  SLACK: |
    {
      "blocks": [
        {"type": "header", "text": {"type": "plain_text", "text": "Order Status"}},
        {"type": "section", "fields": [
          {"type": "mrkdwn", "text": "*Order:* #{{order_id}}"},
          {"type": "mrkdwn", "text": "*Status:* {{status}}"}
        ]},
        {"type": "divider"},
        {"type": "section", "text": {"type": "mrkdwn", "text": "Estimated delivery: {{delivery_date}}"}}
      ]
    }

WhatsApp Interactive Messages

Send interactive list or button messages on WhatsApp.
RESPOND: "Select an option"
RICH_CONTENT:
  WHATSAPP: |
    {
      "type": "interactive",
      "interactive": {
        "type": "button",
        "body": {"text": "How would you like to proceed?"},
        "action": {
          "buttons": [
            {"type": "reply", "reply": {"id": "modify", "title": "Modify Booking"}},
            {"type": "reply", "reply": {"id": "cancel", "title": "Cancel Booking"}},
            {"type": "reply", "reply": {"id": "info", "title": "More Info"}}
          ]
        }
      }
    }
Send a scrollable carousel of cards across supported channels. Each card has a title, an optional subtitle and image, and action buttons.
RESPOND: "Available hotels"
RICH_CONTENT:
  CAROUSEL:
    - title: "Grand Hotel"
      subtitle: "$200/night - 4.5 stars"
      imageUrl: "https://example.com/grand-hotel.jpg"
      buttons:
        - id: book_grand
          type: button
          label: "Book Now"
          value: "grand_hotel"
    - title: "City Lodge"
      subtitle: "$120/night - 4.0 stars"
      imageUrl: "https://example.com/city-lodge.jpg"
      buttons:
        - id: book_city
          type: button
          label: "Book Now"
          value: "city_lodge"

Interactive actions

Add buttons, select menus, and input fields with an ACTIONS: block. Handle the user’s interaction with ON_ACTION.
FLOW:
  offer_actions:
    REASONING: false
    RESPOND: "What would you like to do?"
    ACTIONS:
      - id: view_details
        type: button
        label: "View Details"
      - id: select_category
        type: select
        label: "Category"
        options:
          - id: billing
            label: "Billing"
          - id: technical
            label: "Technical"
          - id: general
            label: "General"
    ON_ACTION:
      view_details:
        RESPOND: "Here are the details..."
        GOTO: show_details
      select_category:
        SET:
          selected_category = action.value
        GOTO: route_to_category
The type property accepts button, select, or input. For input elements, set inputType to text, number, date, time, or email, and add a submitLabel and submitId for the form’s submit button. See Interactive actions for the full property list.

Multi-channel templates

Define reusable, named responses in the TEMPLATES: block. Templates support {{}} interpolation and multi-format variants. Reference a template with TEMPLATE(name).
TEMPLATES:
  booking_summary:
    DEFAULT: |
      Booking Confirmed!
      Hotel: {{hotel_name}}
      Total: ${{total}}
    MARKDOWN: |
      ## Booking Confirmed

      | Detail | Value |
      |--------|-------|
      | **Hotel** | {{hotel_name}} |
      | **Total** | ${{total}} |

FLOW:
  confirm:
    REASONING: false
    RESPOND: TEMPLATE(booking_summary)
    THEN: COMPLETE
The reference defines DEFAULT, MARKDOWN, ADAPTIVE_CARD, and ACTIONS as template variants. The HTML and voice template variants from earlier drafts aren’t in the reference. Verify with the product team before documenting them.

How the runtime selects a format

The runtime selects the response format based on the delivery channel and falls back through the priority chain until it finds a defined format. Plain text is always the final fallback.
ChannelPriority formatFallback
Web and SDKRICH_CONTENT (Markdown, HTML, AG-UI)Plain text
MobileRICH_CONTENT (Adaptive Card, Carousel)Plain text
VoiceVOICE (SSML, instructions, plain text)RESPOND text
SlackRICH_CONTENT (Slack)Markdown
WhatsAppRICH_CONTENT (WhatsApp)Plain text

Troubleshooting

SymptomResolution
Rich content doesn’t renderThe channel may not support the format. Always provide a plain text RESPOND fallback. The runtime uses it when no matching rich format exists.
Adaptive Card JSON parse errorValidate the JSON with the Adaptive Cards Designer. Template variables resolve before JSON parsing.
Slack blocks rejected by the APISlack limits block structure to 50 blocks and 3,000 characters per text element. Validate against Slack’s Block Kit Builder.
Interactive action isn’t handledConfirm each button or select element has an id that matches a handler key in the ON_ACTION block.

File Attachments

Handle file attachments so your agent can accept, process, and respond to user-uploaded images, documents, audio, and video.
The ATTACHMENTS ABL block and the attachments REST API in this section are pending a published reference. Verify block properties, endpoint paths, and field names before publishing.

Declare attachment fields

Use the ATTACHMENTS block in your agent to declare the file types your agent accepts. The runtime prompts the user for each required attachment and validates file type and size before processing.
AGENT: Document_Processor
GOAL: "Process uploaded documents and extract information"

ATTACHMENTS:
  id_document:
    prompt: "Please upload a photo of your ID"
    category: image
    required: true
    maxFileSizeMb: 10
    allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"]
    ocrEnabled: true

  supporting_document:
    prompt: "Upload any supporting documents (optional)"
    category: document
    required: false
    maxFileSizeMb: 20
    allowedMimeTypes: ["application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"]

Upload attachments through the API

When you build a custom integration, upload files through the attachments API. The response includes an attachmentId and a processing status.
curl -X POST https://your-platform/api/projects/$PROJECT_ID/sessions/$SESSION_ID/attachments \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@document.pdf"
For the Web SDK, upload with chat.uploadAttachment(file) and send the returned ID with a message. The SDK accepts files up to 25 MB each, with a maximum of 10 files per message. See Agent Platform SDKs Overview.

Check processing status

curl https://your-platform/api/projects/$PROJECT_ID/sessions/$SESSION_ID/attachments/$ATTACHMENT_ID/status \
  -H "Authorization: Bearer $TOKEN"
FieldDescription
scanStatusMalware scan result: pending, clean, or infected.
processingStatusContent extraction: pending, processing, or complete.
embeddingStatusEmbedding generation: pending or complete.

Extract text from images with OCR

Enable OCR to extract text from images, which is useful for receipts, IDs, and handwritten notes. The extracted text is available in the session as receipt.extractedText.
ATTACHMENTS:
  receipt:
    prompt: "Upload a photo of your receipt"
    category: image
    required: true
    ocrEnabled: true

Transcribe audio

Enable transcription for audio uploads. The transcribed text is available as voicemail.transcription.
ATTACHMENTS:
  voicemail:
    prompt: "Upload the voicemail recording"
    category: audio
    required: true
    transcriptionEnabled: true
    allowedMimeTypes: ["audio/mpeg", "audio/wav", "audio/ogg"]

Extract key frames from video

Extract key frames from video for visual analysis.
ATTACHMENTS:
  product_video:
    prompt: "Upload a video of the defective product"
    category: video
    required: false
    maxFileSizeMb: 50
    keyFrameExtraction: true

Request attachments in a flow step

Request attachments at specific points in a flow.
FLOW:
  collect_documents:
    REASONING: false
    PROMPT: "I need to verify your identity. Please upload a photo of your ID."
    GATHER:
      FIELDS:
        - id_photo
          type: attachment
          category: image
          required: true
    THEN: verify_identity

  verify_identity:
    REASONING: true
    GOAL: "Verify the uploaded ID document"
    AVAILABLE_TOOLS: [verify_document, extract_id_data]
    EXIT_WHEN: identity_verified IS SET
    THEN: proceed

Channel-specific handling

Different channels handle attachments differently. The platform’s channel adapters normalize all formats, so no agent-side configuration is needed.
ChannelAttachment support
Web SDKDrag-and-drop file upload in the chat widget.
SlackNative file sharing, downloaded automatically.
WhatsAppImage, document, audio, and video through the media API.
TeamsFile attachments through the Bot Framework.
EmailMIME attachments parsed from inbound email.
TelegramPhotos, documents, audio, and video messages.

List and retrieve attachments

The download URL is time-limited (one hour by default) and scoped to the authenticated user.
# List all attachments for a session
curl https://your-platform/api/projects/$PROJECT_ID/sessions/$SESSION_ID/attachments \
  -H "Authorization: Bearer $TOKEN"

# Get a download URL for a specific attachment
curl https://your-platform/api/projects/$PROJECT_ID/sessions/$SESSION_ID/attachments/$ATTACHMENT_ID/url \
  -H "Authorization: Bearer $TOKEN"

Troubleshooting

SymptomResolution
”PAYLOAD_TOO_LARGE” on uploadThe file exceeds the maximum size, either the upload endpoint limit or the maxFileSizeMb declared in the attachment field. Reduce the file size or adjust the limit.
Processing status stuck on “pending”The multimodal processing service may be unavailable. Check service health.
OCR returns empty textThe image may be too low resolution, or the text isn’t clearly visible. Recommend that users upload high-quality images.
Attachment isn’t available in session variablesWait for processingStatus to reach complete before accessing extracted content. Use the status endpoint to poll.