camelAI Documentation

Guides

Channels

Let people talk to agents from Slack, Telegram and Discord, and have agents answer GitHub and any webhook

A channel lets people talk to agents from a messaging service (Telegram, Slack or Discord), or has agents answer activity on GitHub. A webhook channel lets any service that sends webhooks (Sentry, Linear, Stripe, your own) start agents. Manage channels with /v1/channels or the console's Channels page:

bash
curl https://run.camelai.com/v1/channels \
  -H "Authorization: Bearer $CAMELAI_API_KEY" -H "Content-Type: application/json" \
  -d '{"type": "telegram", "credentials": {"botToken": "<from @BotFather>"},
       "definition": "def_...", "access": {"allow": ["@ada", "123456789"]}}'

Each conversation's agent is made from the channel's definition. A channel created without one gets an empty definition of its own.

ServiceCredentialsMessages arrive byOne conversation (one agent) is
telegrambotTokenWebhook, registered for youA chat
slackbotToken (xoxb-...), signingSecretWebhook, pasted into the appA thread started by an @mention, or a DM
discordbotTokenThe Gateway, which camelRun keeps connectedA DM, or a channel or thread where the bot is @mentioned
githubappId, privateKey, webhookSecretWebhook, pasted into the GitHub AppA pull request or issue
webhooksecret, optional replyUrlWebhook, pasted into the sending serviceWhatever the key template names

Creating a channel checks its credentials with the service. Credentials are stored encrypted, and the API returns only masked values.

Set up each service

The runtime also supports email channels, an address that agents answer mail at. They require a runtime configured to receive mail, and aren't enabled on run.camelai.com.

What all channels share

  • Each external conversation gets its own agent, made on first contact from the channel's definition.
  • Senders must be on the allowlist (access.allow) unless the channel sets access.public. Each sender is rate limited (limits.perSenderPerMinute, default 10), and the channel has a daily turn cap (limits.turnsPerDay, default 1,000).
  • Each message carries its sender as from, and tool calls carry a runtime-set origin ({channel, conversationId, sender}) that tools can authorize against.
  • Attachments are streamed into the agent's workspace at uploads/<requestId>/<name>: up to 10 files a message and 25 MiB each (20 MB on Telegram).
  • The turn's final answer is sent back when the turn ends, split to the service's message limit. Channel agents also get a send_message tool for updates mid-turn, and files the agent presents follow the reply.
  • Messages are recorded durably before they are acknowledged, duplicates are dropped, and replies go through a durable outbox, so each part is sent once.
  • Questions and approvals appear as text, and the next fitting message from an allowed person answers them. See Human input.

Generic webhooks

A webhook channel turns each delivery from any service into a prompt. Give it the secret the service signs with, then point the service at the channel's webhookUrl:

bash
curl https://run.camelai.com/v1/channels \
  -H "Authorization: Bearer $CAMELAI_API_KEY" -H "Content-Type: application/json" \
  -d '{"type": "webhook", "credentials": {"secret": "<the signing secret>"}, "definition": "def_...",
       "settings": {"signature": {"type": "hmac-sha256", "header": "Sentry-Hook-Signature"},
                    "key": "sentry-{{data.issue.id}}",
                    "filter": [{"path": "action", "in": ["created"]}],
                    "prompt": "Sentry issue {{data.issue.title}} ({{data.issue.web_url}}) was created. Triage it."}}'
Setting
signatureHow a delivery proves itself: {"type": "standard"} (Standard Webhooks, the default), {"type": "hmac-sha256", "header", "prefix"?, "encoding"?} (GitHub, Sentry, Linear, Shopify and most others), or {"type": "token", "header"}. Unsigned or wrongly signed deliveries get a 401
keyPicks the conversation, and so the agent. Deliveries that render to the same key go to the same agent. Without a key, the channel has one agent
promptWhat the agent is told. By default, the payload itself. Either way the whole payload is attached as payload.json
senderWho a delivery is from, for from and rate limits, such as {{actor.email}}
filterConditions that must all hold (path with in, equals or exists), or the delivery is acknowledged and ignored
idPath, idHeaderWhere a delivery's id is, so retries are dropped

Templates are {{path}}: a dotted path into the JSON payload (data.items.0.id), or {{headers.<name>}} for a request header. Bodies must be JSON, up to 1 MiB.

There's no conversation to answer, so the agent acts through its tools and the turn's end reaches you as a run.completed event. To get the reply too, set credentials.replyUrl: each reply is POSTed there as {"type": "message", "conversationId", "text"}, signed per Standard Webhooks with the channel's secret.