Surfaces
A surface is a channel where people talk to an agent. Seven are wired: WhatsApp, iMessage, Slack, Discord, Telegram, voice and web chat. Replies go back on the channel the message came from. The agent and its guardrails are the same on every channel; only the entry point changes.
Available surfaces
Section titled “Available surfaces”Two paths:
- Personal (bridge): a Baileys-based bridge pairs with your own WhatsApp account by QR code. No Business account needed. Self-hosted only.
- Business Cloud API: the surface gateway has a WhatsApp Cloud API adapter for a dedicated business number.
To set up personal WhatsApp:
- Open Channels (
/surfaces) and choose WhatsApp - Select Personal (bridge)
- Scan the QR code with your WhatsApp app
- The bridge connects and your agent is now reachable via WhatsApp
Bridge tenant env var
Section titled “Bridge tenant env var”Set LANTERN_TENANT_ID to the tenant UUID the bridge runs under. LANTERN_DEFAULT_TENANT_ID is still read as a fallback. The iMessage bridge and bridge-core resolve it in that order. The macOS iMessage bridge is covered in PAL (personal agent).
The surface gateway (services/surface-gateway) has a Slack adapter for events and bot messages, and the control plane serves a slash-command endpoint at POST /v1/surfaces/slack/command. In production, unsigned slash commands are refused unless SLACK_SIGNING_SECRET is set. Configure the surface under Channels (POST /v1/surfaces with a surfaceId and displayName).
Discord
Section titled “Discord”A Discord adapter ships in the surface gateway. Configure it under Channels with your bot credentials.
Telegram
Section titled “Telegram”Connect a Telegram bot to an agent:
- Create a bot via
@BotFatheron Telegram - Add the bot token under Channels
- Assign the surface to an agent
How a message becomes a reply. Telegram POSTs the update to the surface-gateway at /webhooks/telegram. The gateway checks the X-Telegram-Bot-Api-Secret-Token header against TELEGRAM_SECRET_TOKEN (a missing or wrong token is a 401 and nothing runs), creates a run for LANTERN_AGENT_NAME in LANTERN_TENANT_ID, waits for it to finish, and sends the run’s output to the same chat with sendMessage. The same loop serves WhatsApp Cloud, whose POSTs are verified with X-Hub-Signature-256 (WHATSAPP_APP_SECRET). Try it end to end with examples/telegram-assistant (calendar-aware assistant) and see the fixture suite.
Limits:
- The agent needs a promoted version before the gateway can run it: run it once from the dashboard or
POST /v1/runsfirst, otherwise the gateway logsagent has no promoted version. - Replies are plain text, sent once when the run ends (no token streaming to the chat). Replies are wired for Telegram and WhatsApp only; a Slack or Discord run completes but is not posted back.
- A run that does not finish within 3 minutes gets no reply.
TELEGRAM_API_BASE_URLre-points Bot API calls at a fixture server for tests; the gateway refuses to start with it whenLANTERN_ENVis prod, production or staging.
Voice (Twilio and LiveKit)
Section titled “Voice (Twilio and LiveKit)”Phone numbers route inbound calls to an agent. Two providers are supported:
- Twilio: TwiML webhooks for inbound PSTN calls. The control-plane verifies Twilio’s webhook signature and replies with TwiML. SMS is also available on the same Twilio connector.
- LiveKit: realtime audio; the control-plane mints a short-lived join token and the audio loop runs in a separately-deployed LiveKit Agents worker (media last-mile).
Budget gating
Section titled “Budget gating”Voice spend counts against the same agent_budgets as runs. A Twilio inbound call over a hard-fail budget is declined with <Reject> (no carrier cost). A LiveKit join token is refused with HTTP 402 (no token means no media). A flat cost estimate is recorded on connect; the provider’s status callback at POST /v1/voice/calls/status/{provider} reconciles to actual duration cost when the call ends.
Both Twilio endpoints verify X-Twilio-Signature (HMAC-SHA1 over the public URL plus the sorted form fields, keyed with the number’s auth token) and answer a bad or missing one with 403 before touching any state, so a forged status callback cannot move a budget. Billing is by started minute: a 61-second call is two minutes at $0.03, so the $0.05 connect-time reservation reconciles to $0.06; a call that never connects refunds it to zero. Try it with examples/voice-receptionist; the signed-webhook flow is asserted in the fixture suite. The inbound reply is a fixed greeting today; the realtime conversation needs the LiveKit worker (media last mile).
Voice API
Section titled “Voice API”| Method | Path | Description |
|---|---|---|
POST |
/v1/voice/numbers |
Link a phone number to an agent (provider + config) |
GET |
/v1/voice/numbers |
List linked numbers |
DELETE |
/v1/voice/numbers/{id} |
Unlink a number |
GET |
/v1/voice/calls |
Recent calls with duration and cost |
POST |
/v1/voice/token |
Mint a short-lived LiveKit join token |
POST |
/v1/voice/webhook/{provider} |
Inbound call webhook (Twilio TwiML or LiveKit JWT) |
POST |
/v1/voice/calls/status/{provider} |
Call-end status callback; reconciles actual cost into the budget rollup. Point Twilio’s “call status changes” webhook here. |
Env vars
Section titled “Env vars”| Variable | Purpose |
|---|---|
LANTERN_VOICE_CALLER_ID |
E.164 caller-ID shown on outbound calls. Must be a Twilio verified number. |
LANTERN_VOICE_SMS_HEADSUP |
on (default): sends an SMS heads-up before the call so recipients recognize the number. |
LANTERN_VOICE_CLONE |
Optional. 1 speaks outbound calls in a cloned voice via ElevenLabs. Requires LANTERN_ELEVENLABS_API_KEY and LANTERN_ELEVENLABS_VOICE_ID. |
Web Chat
Section titled “Web Chat”Embed a chat widget on any website:
<!-- widget.js is served by the dashboard app; copy the exact snippet from /embed --><script src="http://localhost:3001/widget.js" data-api-key="YOUR_SCOPED_API_KEY" data-agent="your-agent-name" data-api-base="http://localhost:8080" data-theme="dark" data-position="bottom-right" async defer></script>The widget uses the same /v1/sessions endpoints as the dashboard and streams replies over SSE. The API key is visible in the browser, so scope it to sessions and messages. The dashboard’s /embed page generates the snippet.
REST API
Section titled “REST API”Every agent is accessible via the REST API. See the API Reference for full details.
curl -X POST http://localhost:8080/v1/runs \ -H "Authorization: Bearer $LANTERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"agentName": "my-agent", "input": {"topic": "quantum computing"}}'Assigning surfaces to agents
Section titled “Assigning surfaces to agents”Configure a surface once for the tenant, then point it at an agent:
- Set up the surface under Channels
- On the agent’s page, open its Channels tab and enable the surfaces it should answer on
Two-way communication
Section titled “Two-way communication”When a user sends a message:
- The surface gateway (or a bridge) receives the message
- It routes to the correct agent based on the surface configuration
- The agent runs with the message as input
- The response streams back through the same surface, in the same conversation or thread