Twilio
ChannelPut your agent on a phone number: SMS and speech-transcribed calls.
At a glance
From Eve docs · synced Jul 22, 2026
Webhook route
POST /eve/v1/twilioEnvironment variables
- TWILIO_ACCOUNT_SID
- required for default outbound SMS
- TWILIO_AUTH_TOKEN
- required for inbound signature verification
Capabilities
- Human-in-the-loop — SMS and voice have no native button or card affordance, so HITL prompts do not render as interactive controls.
- Proactive sessions — Start a session without an inbound message through receive(twilio, { message, target, auth }) from a schedule run handler, or args.receive(twilio, ...) from another channel.
- Attachments — Inbound media attachments are not supported on this channel today.
Derived from Eve's Twilio docs.
Install
Install the framework:
npm install eve@latestQuick start
Create agent/channels/twilio.ts. allowFrom is required and gates who can reach the inbound hooks:
// agent/channels/twilio.tsimport { twilioChannel } from "eve/channels/twilio";export default twilioChannel({ allowFrom: "+15551234567", messaging: { from: "+15557654321" },});TWILIO_ACCOUNT_SID=AC... # required for default outbound SMSTWILIO_AUTH_TOKEN=... # required for inbound signature verificationConfigure
In the Twilio console, point your number's Messaging webhook at /eve/v1/twilio/messages and its Voice webhook at /eve/v1/twilio/voice. Inbound calls are answered with speech gathering, and the transcript feeds the same session SMS uses. See the Twilio channel docs for dispatch, streaming, and voice specifics.
How it behaves
Excerpts from Eve's Twilio docs. Prefer the source when something looks out of date.
Dispatch
allowFrom is required. It gates who can reach the inbound hooks. Pass a single number, a list, an async resolver, or "*". The wildcard is dangerous; only use it with an explicit check inside onText/onVoice.
export default twilioChannel({ allowFrom: ["+15551234567", "+15557654321"] });
onText and onVoiceTranscription decide dispatch and auth. Return { auth } to proceed, or null to drop the message. onVoice fires the moment a call comes in. Return null to reject it, or return an object to override the spoken prompt, language, <Say voice>, and speech-recognition options.
export default twilioChannel({
allowFrom: ["+15551234567"],
onText: (ctx, message) => ({
auth: {
principalId: message.from,
principalType: "user",
authenticator: "twilio",
attributes: { to: message.to ?? "" },
},
}),
});
Delivery
The default message.completed handler sends the reply as SMS through Twilio's Messages API. A reply to an inbound message can reuse the webhook's To as the sender, but a proactive send has nothing to reuse, so it needs messaging.from or messaging.messagingServiceSid. Behind a proxy, set webhookUrl so signature verification matches the exact configured URL, and publicBaseUrl so voice TwiML can build absolute callback URLs.
Human-in-the-loop (HITL)
SMS and voice have no native button or card affordance, so HITL prompts do not render as interactive controls. The agent's input.requested event reaches your events["input.requested"] handler if you declare one. Handle it by sending the prompt as text and mapping the caller's reply back to the input request yourself.
Proactive sessions
Start a session without an inbound message through receive(twilio, { message, target, auth }) from a schedule run handler, or args.receive(twilio, ...) from another channel. target.phoneNumber is required, and the channel needs messaging.from or messaging.messagingServiceSid for the outbound sender.
Attachments
Inbound media attachments are not supported on this channel today.