Skip to content

CODEANDTRUST/clawcall

Repository files navigation

clawcall

Give your OpenClaw / self-hosted AI agent inbound phone calls and SMS.

CI MIT License Node 20+ TypeScript

A self-hostable TypeScript service that bridges Twilio inbound voice calls and SMS to an OpenClaw gateway — routing every utterance or message through a full agent turn with native tool access.


Why this exists

OpenClaw's 176k-member Discord has a persistent demand: "I want my agent to answer my phone." There are now two architecturally distinct ways to do that, and clawcall is the second one.

The native plugin approach (@openclaw/voice-call realtime mode, PR #71272 / e2f13959d4): the realtime LLM (OpenAI or Gemini Live) handles the audio session end-to-end and can call a special openclaw_agent_consult tool when it needs to reach the gateway. Sub-second conversational latency; agent tools available via the consult hop.

clawcall's approach: skip the realtime LLM entirely. Every caller utterance goes straight to the OpenClaw gateway via chat.send, running the agent's complete tool-calling loop on the gateway turn — the same path as any other message to your agent. clawcall then synthesises the text reply to speech and delivers it to the caller.

The trade-offs are real and worth stating plainly:

clawcall native realtime plugin
Latency Higher (STT → full agent turn → TTS) Lower (realtime LLM + async consult hop)
Agent tool access Every turn, natively Via openclaw_agent_consult tool call
STT/TTS provider Pluggable (Deepgram / ElevenLabs / keyless dev mode) OpenAI / ElevenLabs
Model control Your gateway's configured model OpenAI or Gemini Live
SMS Yes (POST /twilio/sms, keyless) No
Self-host / keyless dev Yes No

Choose clawcall when you want full agent-turn fidelity on every utterance, control over your STT and TTS providers, or a simpler mental model: the phone call is just another chat.send. Choose the native plugin when you need the lowest possible conversational latency and are comfortable with the realtime LLM + consult-hop model.


Architecture

              PSTN / VoIP
                  │
                  ▼
         ┌─────────────────┐
         │  Twilio Voice   │
         │  (inbound call) │
         └───────┬─────────┘
                 │ POST /twilio/voice
                 │ (TwiML: <Connect><Stream>)
                 ▼
         ┌─────────────────────────────────────────────┐
         │               clawcall server               │
         │  ┌──────────────────────────────────────┐   │
         │  │           CallSession                │   │
         │  │                                      │   │
         │  │  Twilio WS ──► STT ──► GatewayClient │   │
         │  │  (mulaw in)   (Deepgram)  (chat.send) │   │
         │  │                              │        │   │
         │  │                              ▼        │   │
         │  │                     OpenClaw Gateway  │   │
         │  │                     (agent turn —     │   │
         │  │                      tools run here)  │   │
         │  │                              │        │   │
         │  │  Twilio WS ◄── TTS ◄─────────┘        │   │
         │  │  (mulaw out) (ElevenLabs)             │   │
         │  └──────────────────────────────────────┘   │
         └─────────────────────────────────────────────┘
                 │
                 ▼
         OpenClaw Gateway
         ws://127.0.0.1:18789
         (tools: calendar, memory,
          send-message, web search…)

Data flow per utterance:

  1. Twilio streams μ-law 8 kHz audio over WebSocket to /twilio/stream
  2. Deepgram streaming STT transcribes it in real time
  3. On a final transcript, GatewayClient.agentTurn() sends a chat.send message to the OpenClaw gateway over the protocol-v4 WebSocket
  4. The gateway runs the agent's full tool-calling loop; text streams back as deltaText events
  5. ElevenLabs streaming TTS synthesises each chunk as it arrives
  6. μ-law audio is sent back to Twilio, which plays it to the caller
  7. Barge-in: if Deepgram detects speech while audio is playing, TTS is interrupted immediately and a new agent turn begins

Quickstart

1. Prerequisites

2. Clone and install

git clone https://github.com/CODEANDTRUST/clawcall.git
cd clawcall
npm install

3. Configure

cp .env.example .env
# Edit .env — minimum required fields:
#   PUBLIC_URL, TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN,
#   OPENCLAW_GATEWAY_TOKEN, ALLOW_FROM

See Provider setup below for API key details.

4. Expose your server

During development, use ngrok to get a public HTTPS URL:

ngrok http 3000
# Copy the https://... URL into PUBLIC_URL in your .env

5. Point your Twilio number at clawcall

In the Twilio Console, set the Voice webhook for your number to:

https://your-server.example.com/twilio/voice

HTTP method: POST

6. Start the server

npm run build
npm start

# or for development with hot reload:
npm run dev

7. Call it

Call your Twilio number. The agent will answer.


Provider setup

Twilio (required)

  1. Create an account at twilio.com
  2. Buy a voice-capable phone number
  3. Copy your Account SID and Auth Token from the Console dashboard
  4. Set TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN in .env

OpenClaw gateway (required)

clawcall connects to your locally-running OpenClaw gateway over WebSocket.

// In your openclaw.json:
{
  "gateway": {
    "auth": {
      "token": "your-secret-token"  // → OPENCLAW_GATEWAY_TOKEN
    }
  }
}

Set OPENCLAW_GATEWAY_URL (default: ws://127.0.0.1:18789) and OPENCLAW_GATEWAY_TOKEN in .env.

Deepgram STT (recommended)

  1. Sign up at deepgram.com (free tier available)
  2. Create an API key
  3. Set STT_PROVIDER=deepgram and DEEPGRAM_API_KEY=... in .env

Keyless dev mode: set STT_PROVIDER=null. The server logs audio chunk counts and emits a canned transcript after 2 seconds of audio — enough to exercise the full pipeline without any API key.

ElevenLabs TTS (recommended)

  1. Sign up at elevenlabs.io (free tier available)
  2. Create an API key
  3. Set TTS_PROVIDER=elevenlabs, ELEVENLABS_API_KEY=..., and optionally ELEVENLABS_VOICE_ID=... in .env

Keyless dev mode: set TTS_PROVIDER=twilio-say. Twilio synthesises speech using its built-in <Say> verb — no ElevenLabs account needed. Latency is higher (no streaming) but works with zero external TTS API keys.


Inbound allowlist

By default (INBOUND_POLICY=allowlist), only numbers listed in ALLOW_FROM can reach your agent. This is the recommended production configuration.

# Allow specific numbers (comma-separated E.164):
ALLOW_FROM=+18432965626,+18005551234

# Open to all callers (dev/testing only):
INBOUND_POLICY=all

Important: The allowlist authenticates the caller's reported number via the Twilio webhook signature, which proves the request came from Twilio. It does not cryptographically verify PSTN caller-ID ownership — VoIP callers can spoof their From number. Use this as a first line of defence, not as the only security layer for sensitive agents.


Per-number persona

Map individual Twilio "To" numbers to a custom greeting and ElevenLabs voice:

PERSONA_MAP='[{"from":"+18005551234","greeting":"Welcome to Acme Corp, how can I help?","voiceId":"YOUR_VOICE_ID"}]'

Inbound SMS

clawcall handles inbound SMS out of the box. The same inbound allowlist policy and OpenClaw gateway connection used for voice apply to SMS — no extra config needed beyond pointing Twilio at the webhook.

How it works

  1. A text arrives at your Twilio number
  2. Twilio POSTs to POST /twilio/sms (Twilio request signature verified)
  3. The sender is checked against the allowlist (same INBOUND_POLICY / ALLOW_FROM)
  4. The message body is routed through GatewayClient.agentTurn() — exactly like a voice utterance, so gateway tools (calendar, memory, web search…) are all available
  5. The agent's text reply is returned as TwiML <Message> — Twilio delivers it as a reply SMS

Keyless: SMS requires no STT or TTS keys. It works with STT_PROVIDER=null and TTS_PROVIDER=twilio-say (or any provider combination).

Session continuity: SMS and voice from the same number share the same conversation session key (clawcall:<E.164>), so context carries across channels when OPENCLAW_SESSION_SCOPE=per-caller.

Setup

Enable SMS_ENABLED=true (the default) and point your Twilio number's Messaging webhook at:

https://your-server.example.com/twilio/sms

In the Twilio Console:

  1. Go to Phone Numbers → Manage → Active numbers
  2. Click your SMS-capable number
  3. Under Messaging → A message comes in, set:
    • Webhook: https://your-server.example.com/twilio/sms
    • HTTP method: POST
  4. Save

Config

Variable Default Description
SMS_ENABLED true Set to false to disable the /twilio/sms route entirely

All other SMS behaviour is controlled by existing variables (INBOUND_POLICY, ALLOW_FROM, OPENCLAW_SESSION_SCOPE).

Long / async replies

For agents that may take longer than Twilio's 15-second webhook timeout, reply with an empty <Response/> immediately and send the reply via the Twilio REST API once the agent turn completes:

// Instead of TwiML, fire-and-forget the gateway turn and send via REST:
const twilioClient = twilio(accountSid, authToken);
const reply = await gateway.agentTurn(sessionKey, messageBody, () => {});
await twilioClient.messages.create({ from: toNumber, to: senderNumber, body: reply });

This pattern is documented in the POST /twilio/sms route comments in src/server.ts.


Security notes

Prompt injection over voice: A caller can speak arbitrary text that becomes the agent's input. Configure your OpenClaw agent's system prompt to treat the voice channel as untrusted user input. Do not give the agent access to tools that execute code or send messages without confirmation unless you trust all callers in your allowlist.

Cost: Each call incurs Twilio per-minute charges, Deepgram streaming transcription costs, and ElevenLabs character costs. Set INBOUND_POLICY=allowlist to prevent unexpected charges from unknown callers.


Comparison

clawcall @openclaw/voice-call clawphone deepclaw
Agent tools mid-call Yes (every turn, gateway) Via consult tool (#71272) No (CLI spawn) No (side LLM)
Streaming STT Deepgram OpenAI/ElevenLabs Twilio built-in Deepgram
Streaming TTS ElevenLabs ElevenLabs Twilio built-in ElevenLabs
SMS Yes (keyless) No No No
Keyless dev mode Yes No Yes No
Inbound allowlist Yes Yes No No
Self-hostable Yes Plugin only Yes Yes
Barge-in Yes Partial No Yes

For a full comparison including managed platforms (Vapi, Retell, Bland), latency trade-offs, SMS, and license details, see COMPARISON.md.


Examples

The examples/quickstart/ directory has a documented .env.example for keyless dev mode and a step-by-step guide to pointing a Twilio number at the server and placing a test call. A smoke script (examples/quickstart/smoke.js) boots the server and hits /health to confirm it runs — no API keys required:

npm run build
node examples/quickstart/smoke.js
# [smoke] GET /health → 200 {"status":"ok","activeCalls":0}
# [smoke] PASS

Contributing

PRs welcome. See CONTRIBUTING.md for the full workflow — how to run tests, build, and PR norms.

npm test        # run the test suite (36 tests, no API keys needed)
npm run build   # typecheck + compile
npm run lint    # ESLint

Built and maintained by Code and Trust

Code and Trust builds AI agent infrastructure for businesses.

Companion guide: Give your OpenClaw agent a phone number

MIT License — see LICENSE.

About

Give your OpenClaw / self-hosted AI agent inbound phone calls - a Twilio-to-gateway voice bridge with working agent tools mid-call (MIT).

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors