Appearance
Channels & Triggers

A channel is an inbound surface that hands a message to an agent. A trigger is an inbound event (webhook, cron tick) that launches a task. Together they turn Agentcy from a chat app into an autonomous system that reacts to your world.
Related:
Channels catalog
Channel types exposed by GET /api/v1/channels:
| Channel | Transport | Status | Notes |
|---|---|---|---|
whatsapp | Baileys (Node.js gateway) | stable | QR-code pairing flow. Gateway auto-spawned. |
slack | Socket Mode | stable | Feature-gated; requires Slack app creds. |
webchat | HTTP + SSE | stable | Embeddable widget, see Chat Widget. |
email | SMTP/IMAP | beta | Inbound email → message. |
sms | Twilio | beta | Bidirectional SMS. |
The catalog endpoint lists every possible channel and, per-org, its configured state (configured: true/false, last connection, bindings).
How a channel becomes an agent run
Inbound message (WhatsApp / Slack / …)
│
▼
Channel adapter translates to ChannelMessage {
channel: "whatsapp", from, to, text, media, thread
}
│
▼
ChannelBinding resolver — which agent handles this?
│ (match_rule: {channel, phone, group, user})
▼
Agent runtime (same loop as UI chat)
│
▼
Outbound reply → channel adapter → provider APIThe key object is a binding:
json
POST /api/v1/bindings
{
"agent": "incident-responder",
"match_rule": { "channel": "whatsapp", "group": "+972500000001" }
}Rules are evaluated top-to-bottom; first match wins. If no binding matches, the message is logged and dropped (never auto-responded-to by default).
WhatsApp specifics
WhatsApp connects via a local Node.js gateway running Baileys. The gateway:
- Is auto-extracted into
~/.openfang/whatsapp-gateway/on first configure. - Requires Node ≥ 18 on the host.
- Pairs via QR code (
POST /api/v1/channels/whatsapp/qr/startreturns a PNG data URL; poll/qr/status?session_id=). - Is re-branded as "Agentcy" in the device list (the gateway's
browser:line is patched at startup).
Messages arrive as ChannelMessage with media attachments downloaded to agentcy-storage and referenced by artifact id.
Slack specifics
Slack runs in Socket Mode so you don't need a public URL. You provide:
SLACK_APP_TOKEN— app-level token starting withxapp-SLACK_BOT_TOKEN— bot token starting withxoxb-
The listener (agentcy-slack-channel) opens a websocket to Slack and fans events into the same ChannelMessage path.
Webchat
webchat is how embedded widgets deliver messages. The widget posts to POST /api/v1/chat/incoming/webchat with a shared secret header; the API resolves it to a conversation and streams the agent's reply back over SSE.
See Chat Widget.
Triggers
A trigger is an event source that starts a scheduled task. Three types:
| Trigger | Source | Payload |
|---|---|---|
schedule | cron (UTC) | { "kind": "schedule", "fired_at": "…" } |
webhook | inbound HTTP | { "kind": "webhook", "headers": {…}, "body": {…} } |
lifecycle | Agentcy events (new node, source sync failed, …) | { "kind": "lifecycle", "event": "…", "payload": {…} } |
Webhook triggers
Webhooks have a public URL per trigger and an HMAC secret:
URL: https://your-agentcy/api/v1/hooks/<trigger_id>
Header: X-Agentcy-Signature: sha256=<hex>Verification is mandatory — the receiver rejects mismatches with 401.
You can drop a sample payload into the UI and trigger a dry run without a real caller (POST /api/v1/webhook-samples/:id/run). Samples live in agentcy-api::routes::webhook_samples.
See How-To: Webhooks & Triggers.
Lifecycle triggers
Emitted by the platform itself. Useful for reactive automation: "when a new :Incident node appears, page oncall."
| Event | When |
|---|---|
node.created | Any new node landed in the graph. |
node.updated | Node properties changed. |
source.sync.failed | A connector sync errored out. |
policy.deny | A policy blocked a tool call or API request. |
approval.requested | An agent is blocked on a human approval. |
Cron triggers
Standard 5-field cron, UTC. agentcy-tasks uses tokio-cron-scheduler under the hood.
*/15 * * * * every 15 minutes
0 9 * * 1-5 weekdays at 09:00 UTC
0 0 * * 0 sundays at midnightAuthorization
Channel inbound messages can produce tool calls just like UI chat. Policies still apply. A common pattern:
rego
# Allow WhatsApp bindings to run read-only tools without approval,
# but require human approval for write tools.
deny[msg] {
input.subject.channel == "whatsapp"
input.action == "chat.tool.execute"
input.resource.tool_effect == "write"
not input.context.approval_granted
msg := "write tool on whatsapp needs approval"
}Channel testing
Every channel exposes POST /api/v1/channels/:name/test, which validates credentials (sends a no-op probe where possible). Always run this after configuration; if it fails, reload with POST /api/v1/channels/reload after fixing.
Under the hood
Channels and triggers share one bus (ConnectorEventBus + channel plugin) so the UI can render a single "inbound" timeline. That's why channel messages, webhook hits, and cron ticks all show up together in Activity.
Key files:
backend/crates/agentcy-api/src/routes/channels.rs— REST surface.backend/crates/agentcy-whatsapp/— Baileys gateway wrapper.backend/crates/agentcy-slack-channel/— Socket Mode listener.backend/crates/agentcy-tasks/— scheduler, cron, trigger dispatch.backend/crates/agentcy-api/src/routes/webhook_receiver.rs— HMAC webhook verifier.