Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
alphaonedev avatar

Twilio Conversations

  • 47 installs
  • 6 repo stars
  • Updated March 13, 2026
  • alphaonedev/openclaw-graph

twilio-conversations is a skill for implementing Twilio Conversations in production, unifying SMS, WhatsApp, and chat into one thread with participant, message, and webhook management.

About

This skill helps engineers implement and operate Twilio Conversations in production, unifying SMS, WhatsApp, and in-app chat into one thread abstraction. A developer uses it to manage conversations, participants, and messages, and to wire up webhooks for delivery, read, and failure state. It also covers rate limits, idempotency, cost controls, and compliance such as STOP handling and 10DLC.

  • Implements Twilio Conversations across SMS, WhatsApp, and chat
  • Manages conversations, participants, and messages with webhooks
  • Handles delivery/read/failure state, idempotency, and compliance

Twilio Conversations by the numbers

  • 47 all-time installs (skills.sh)
  • Ranked #3,252 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Data as of Jul 28, 2026 (Skillselion catalog sync)
At a glance

twilio-conversations capabilities & compatibility

Capabilities
twilio sms · twilio verify · twilio email
Works with
postgres · redis
Use cases
api development
Platforms
macOS · Linux
Pricing
Bring your own API key
From the docs

What twilio-conversations says it does

Build a unified “thread” abstraction across channels (SMS, WhatsApp, in-app chat) with consistent participant/message APIs.
SKILL.md
Use Twilio API Key (recommended) instead of Account SID + Auth Token for production services.
SKILL.md
npx skills add https://github.com/alphaonedev/openclaw-graph --skill twilio-conversations

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs47
repo stars6
Last updatedMarch 13, 2026
Repositoryalphaonedev/openclaw-graph

What it does

Build a unified omnichannel messaging thread across SMS, WhatsApp, and chat with Twilio Conversations and webhooks.

Who is it for?

Building a unified omnichannel thread abstraction across SMS, WhatsApp, and in-app chat.

When should I use this skill?

When implementing or operating Twilio Conversations across SMS, WhatsApp, or chat in production.

By the numbers

  • Tested with Twilio Node SDK 4.23.0 and Python SDK 9.4.1

Files

SKILL.mdMarkdownGitHub ↗

twilio-conversations

Purpose

Enable OpenClaw to implement and operate Twilio Conversations in production: create/manage conversations, participants, and messages across SMS/WhatsApp/chat; integrate webhooks for delivery/read/failure; enforce opt-out and compliance; and compose with Twilio Programmable Messaging/Voice/Verify/Studio/SendGrid patterns.

Concrete engineer value:

  • Build a unified “thread” abstraction across channels (SMS, WhatsApp, in-app chat) with consistent participant/message APIs.
  • Implement reliable webhook-driven state (delivery/read/failure) with idempotency, retries, and backpressure.
  • Operate at scale: rate limits, pagination, cost controls (Messaging Services, geo-matching), and compliance (STOP, 10DLC, toll-free).
  • Provide production-grade observability and incident response playbooks for Twilio error codes and webhook failures.

---

Prerequisites

Accounts & Twilio setup

  • Twilio account with Conversations enabled.
  • At least one of:
  • SMS-capable phone number (10DLC registered for US A2P where applicable)
  • WhatsApp sender (WhatsApp Business API via Twilio)
  • Messaging Service (recommended for scale/cost controls)
  • Webhook endpoint reachable from Twilio (public HTTPS). For local dev: ngrok or cloudflared.

Runtime versions (tested)

  • Node.js: 20.11.1 (LTS)
  • Python: 3.11.7
  • Twilio Node SDK: 4.23.0
  • Twilio Python SDK: 9.4.1
  • OpenSSL: 1.1.1w or 3.0.13 (platform dependent)
  • Docker: 25.0.3 (optional)
  • PostgreSQL: 15.5 (optional, for webhook/event persistence)
  • Redis: 7.2.4 (optional, for idempotency keys / rate limiting)

OS support

  • Ubuntu 22.04 LTS (x86_64, arm64)
  • Fedora 39 (x86_64)
  • macOS 14 Sonoma (Intel + Apple Silicon)

Auth setup

Use Twilio API Key (recommended) instead of Account SID + Auth Token for production services.

1. Create API Key:

  • Twilio Console → Account → API keys & tokens → Create API key

2. Store:

  • TWILIO_ACCOUNT_SID (starts with AC...)
  • TWILIO_API_KEY_SID (starts with SK...)
  • TWILIO_API_KEY_SECRET

Environment variables (example):

export TWILIO_ACCOUNT_SID="YOUR_ACCOUNT_SID"
export TWILIO_API_KEY_SID="YOUR_API_KEY_SID"
export TWILIO_API_KEY_SECRET="YOUR_API_KEY_SECRET"

If you must use Auth Token (legacy):

export TWILIO_AUTH_TOKEN="your_auth_token"

Network & firewall

  • Allow outbound HTTPS to api.twilio.com and conversations.twilio.com.
  • Inbound webhook endpoint must accept Twilio IP ranges (or validate signatures; prefer signature validation over IP allowlists).

---

Core Concepts

Conversations mental model

  • Conversation: a thread container. Has a sid (CH...), uniqueName, attributes, timers, and state.
  • Participant: an entity in a conversation. Types:
  • Chat participant (identity-based): identity="user-123"
  • Messaging participant (phone-based): messagingBinding.address="+14155550100" and proxyAddress (Twilio number or Messaging Service sender)
  • WhatsApp participant: address like whatsapp:+14155550100
  • Message: content sent within a conversation. Has author, body, media, attributes, and delivery receipts (channel dependent).
  • Webhook events: Twilio sends HTTP callbacks for conversation/message/participant lifecycle and delivery status.

Architecture overview (production)

1. Ingress:

  • Inbound SMS/WhatsApp hits Programmable Messaging webhook.
  • In-app chat uses Conversations SDK (web/mobile) or REST API.

2. Routing:

  • Map inbound message to a Conversation (by phone number, identity, or business key).
  • Add/ensure participants.

3. Egress:

  • Send messages via Conversations API (preferred for unified thread) or Messaging API (for non-threaded blasts).

4. State & observability:

  • Persist webhook events (append-only) and derive state (delivery, read, failed).
  • Idempotency keys to handle Twilio retries.

5. Compliance:

  • STOP/START/HELP handling (Messaging compliance) and participant removal/blacklist.
  • 10DLC/toll-free verification for US traffic; WhatsApp template rules.

Key identifiers

  • Account SID: AC...
  • Conversation SID: CH...
  • Participant SID: MB... (varies)
  • Message SID: IM...
  • Service SID (Conversations Service): IS... (if using Services)
  • Messaging Service SID: MG...

Webhook signature validation

Twilio signs webhook requests with X-Twilio-Signature. Validate using the exact URL Twilio called (including query string) and the POST params.

---

Installation & Setup

Official Python SDK — Conversations

Repository: https://github.com/twilio/twilio-python PyPI: pip install twilio · Supported: Python 3.7–3.13

from twilio.rest import Client
client = Client()

# Create conversation
conv = client.conversations.v1.conversations.create(
    friendly_name="Support Chat #123"
)

# Add participant (SMS)
p = client.conversations.v1.conversations(conv.sid) \
    .participants.create(
        messaging_binding_address="+15558675309",
        messaging_binding_proxy_address="+15017250604"
    )

# Send message
client.conversations.v1.conversations(conv.sid) \
    .messages.create(body="Welcome to support chat!", author="system")

Source: twilio/twilio-python — conversations

1) System dependencies

Ubuntu 22.04
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg jq

Node.js 20.11.1 via NodeSource:

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v  # v20.11.1 (or newer 20.x)
npm -v

Python 3.11:

sudo apt-get install -y python3.11 python3.11-venv python3-pip
python3.11 --version
Fedora 39
sudo dnf install -y jq curl ca-certificates
sudo dnf module install -y nodejs:20
node -v
sudo dnf install -y python3.11 python3.11-pip
python3.11 --version
macOS 14 (Intel/Apple Silicon)

Homebrew:

brew update
brew install jq node@20 python@3.11
node -v
python3.11 --version

2) Project dependencies

Node (recommended for webhook services)
mkdir -p twilio-conversations-service && cd twilio-conversations-service
npm init -y
npm install twilio@4.23.0 express@4.18.3 body-parser@1.20.2 pino@9.0.0 pino-http@9.0.0
npm install --save-dev typescript@5.3.3 ts-node@10.9.2 @types/express@4.17.21 @types/node@20.11.19
Python (batch jobs / admin tooling)
python3.11 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip==24.0
pip install twilio==9.4.1 requests==2.31.0

3) Webhook endpoint (Express) with signature validation

Create src/server.ts:

import express from "express";
import bodyParser from "body-parser";
import pinoHttp from "pino-http";
import twilio from "twilio";

const app = express();
app.use(pinoHttp());

// Twilio signature validation requires the raw body for some frameworks.
// For Express with urlencoded, Twilio helper can validate using parsed params.
// Ensure you use the exact URL configured in Twilio Console.
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

const {
  TWILIO_AUTH_TOKEN,
  PUBLIC_WEBHOOK_BASE_URL,
} = process.env;

if (!TWILIO_AUTH_TOKEN) throw new Error("TWILIO_AUTH_TOKEN is required for webhook signature validation");
if (!PUBLIC_WEBHOOK_BASE_URL) throw new Error("PUBLIC_WEBHOOK_BASE_URL is required (e.g., https://example.com)");

app.post("/twilio/conversations/webhook", (req, res) => {
  const signature = req.header("X-Twilio-Signature") || "";
  const url = `${PUBLIC_WEBHOOK_BASE_URL}/twilio/conversations/webhook`;

  const isValid = twilio.validateRequest(
    TWILIO_AUTH_TOKEN,
    signature,
    url,
    req.body
  );

  if (!isValid) {
    req.log.warn({ signature }, "Invalid Twilio signature");
    return res.status(403).send("Forbidden");
  }

  // Idempotency: Twilio may retry. Use EventSid or MessageSid as a dedupe key.
  const eventType = req.body.EventType;
  const eventSid = req.body.EventSid;
  req.log.info({ eventType, eventSid, body: req.body }, "Twilio Conversations webhook");

  // TODO: enqueue to a worker; respond fast.
  res.status(200).send("ok");
});

const port = Number(process.env.PORT || 3000);
app.listen(port, () => {
  // eslint-disable-next-line no-console
  console.log(`listening on :${port}`);
});

Run:

npx ts-node src/server.ts

Expose with ngrok:

ngrok http 3000
# Set PUBLIC_WEBHOOK_BASE_URL to the https URL ngrok gives you

4) Configure Conversations webhooks

In Twilio Console:

  • Conversations → Services → (your service) → Webhooks
  • Configure:
  • Pre-event Webhook (optional; for authorization/routing decisions)
  • Post-event Webhook (recommended; for event ingestion)
  • Point to:
  • https://your-domain.example.com/twilio/conversations/webhook

For inbound SMS/WhatsApp into Conversations, also configure Programmable Messaging inbound webhook to your app if you’re doing custom mapping, or use Conversations’ messaging bindings (preferred).

---

Key Capabilities

Create and manage Conversations

  • Create conversation with uniqueName for idempotent lookup.
  • Set attributes JSON for business metadata (tenantId, caseId, SLA).
  • Control lifecycle: state (active/inactive/closed), timers, and cleanup.

Add participants (chat + messaging + WhatsApp)

  • Chat participants by identity (for SDK users).
  • Messaging participants by messagingBinding.address and proxyAddress (Twilio sender).
  • WhatsApp addresses use whatsapp:+E164.

Production patterns:

  • Always ensure a deterministic mapping from business entity → conversation uniqueName.
  • Enforce participant limits and blocklists before adding.

Send messages with delivery tracking

  • Send message with author and body.
  • Attach attributes for correlation IDs.
  • Track delivery via webhook events and/or message status callbacks (channel dependent).

Webhooks: event ingestion, retries, idempotency

  • Validate X-Twilio-Signature.
  • Respond within 5 seconds; enqueue work.
  • Dedupe by EventSid (preferred) or (MessageSid, EventType, Timestamp).

Compliance: STOP/START/HELP and opt-out

  • For SMS/WhatsApp, STOP handling is primarily a Programmable Messaging concern.
  • If you ingest inbound messages into Conversations, you must still respect opt-out:
  • Maintain a suppression list keyed by phone number.
  • On STOP, remove participant or mark as blocked; prevent outbound.

Cost optimization with Messaging Services

  • Use Messaging Service with:
  • Geo-matching
  • Sticky sender
  • Smart encoding
  • For Conversations messaging participants, set proxyAddress to a Twilio number; for large scale, prefer Messaging Service where supported by your design (often via Messaging API for outbound, while keeping Conversations as system-of-record).

Compose with Voice/Verify/Studio/SendGrid

  • Escalate a conversation to Voice (Dial/Conference) and post call artifacts back into the conversation.
  • Use Verify for step-up auth before adding a participant or revealing sensitive info.
  • Trigger Studio flows on conversation events.
  • Send SendGrid transactional emails and mirror them into the conversation as messages/attributes.

---

Command Reference

This skill assumes OpenClaw can execute via:

  • Twilio SDKs (Node/Python)
  • Direct REST calls (curl)
  • Twilio CLI (optional; limited Conversations coverage)

REST API base

  • Conversations API base: https://conversations.twilio.com/v1

Auth options:

  • Basic auth with API Key:
  • username: TWILIO_API_KEY_SID
  • password: TWILIO_API_KEY_SECRET
  • Or Account SID + Auth Token.
curl helper (API Key)
export TWILIO_API_KEY_SID="YOUR_API_KEY_SID"
export TWILIO_API_KEY_SECRET="YOUR_API_KEY_SECRET"
export TWILIO_ACCOUNT_SID="YOUR_ACCOUNT_SID"

Conversations

Create conversation

Endpoint:

  • POST /v1/Conversations

Flags/fields:

  • FriendlyName (string)
  • UniqueName (string; use for idempotency)
  • Attributes (stringified JSON)
  • MessagingServiceSid (string; optional)
  • State (active|inactive|closed)
  • Timers.Inactive (ISO-8601 duration string, e.g. PT1H) depending on API support

curl:

curl -sS -X POST "https://conversations.twilio.com/v1/Conversations" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "FriendlyName=Support Case 10492" \
  --data-urlencode "UniqueName=case-10492" \
  --data-urlencode 'Attributes={"tenantId":"acme","caseId":10492,"priority":"p1"}'

Node:

import twilio from "twilio";

const client = twilio(process.env.TWILIO_API_KEY_SID, process.env.TWILIO_API_KEY_SECRET, {
  accountSid: process.env.TWILIO_ACCOUNT_SID,
});

const conv = await client.conversations.v1.conversations.create({
  friendlyName: "Support Case 10492",
  uniqueName: "case-10492",
  attributes: JSON.stringify({ tenantId: "acme", caseId: 10492, priority: "p1" }),
});
console.log(conv.sid);
Fetch conversation
  • GET /v1/Conversations/{ConversationSid}
curl -sS "https://conversations.twilio.com/v1/Conversations/CHXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
List conversations (pagination)
  • GET /v1/Conversations?PageSize=50&PageToken=...

Query params:

  • PageSize (1–1000; practical: 50–200)
  • PageToken (string)
  • State filter may be available depending on API version
curl -sS "https://conversations.twilio.com/v1/Conversations?PageSize=50" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
Update conversation
  • POST /v1/Conversations/{ConversationSid}

Fields:

  • FriendlyName
  • Attributes (stringified JSON)
  • State
curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/CHXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "State=closed" \
  --data-urlencode 'Attributes={"tenantId":"acme","caseId":10492,"priority":"p1","closedBy":"agent-7"}'
Delete conversation
  • DELETE /v1/Conversations/{ConversationSid}
curl -sS -X DELETE "https://conversations.twilio.com/v1/Conversations/CHXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"

Participants

Add chat participant (identity)
  • POST /v1/Conversations/{ConversationSid}/Participants

Fields:

  • Identity (string)
  • Attributes (stringified JSON)
curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/CH.../Participants" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "Identity=user-123" \
  --data-urlencode 'Attributes={"role":"customer"}'
Add messaging participant (SMS)

Fields:

  • MessagingBinding.Address (E.164, e.g. +14155550100)
  • MessagingBinding.ProxyAddress (Twilio number in E.164, e.g. +14155551234)
  • Attributes
curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/CH.../Participants" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "MessagingBinding.Address=+14155550100" \
  --data-urlencode "MessagingBinding.ProxyAddress=+14155551234" \
  --data-urlencode 'Attributes={"role":"customer","channel":"sms"}'
Add messaging participant (WhatsApp)

Use whatsapp: prefix:

curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/CH.../Participants" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "MessagingBinding.Address=whatsapp:+14155550100" \
  --data-urlencode "MessagingBinding.ProxyAddress=whatsapp:+14155559876" \
  --data-urlencode 'Attributes={"role":"customer","channel":"whatsapp"}'
List participants
  • GET /v1/Conversations/{ConversationSid}/Participants?PageSize=50
curl -sS "https://conversations.twilio.com/v1/Conversations/CH.../Participants?PageSize=50" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
Remove participant
  • DELETE /v1/Conversations/{ConversationSid}/Participants/{ParticipantSid}
curl -sS -X DELETE "https://conversations.twilio.com/v1/Conversations/CH.../Participants/MB..." \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"

Messages

Send message
  • POST /v1/Conversations/{ConversationSid}/Messages

Fields:

  • Author (string; identity or system label)
  • Body (string)
  • Attributes (stringified JSON)
  • MediaSid / media fields (if using media; depends on API)
curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/CH.../Messages" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "Author=agent-7" \
  --data-urlencode "Body=We’re looking into this now. ETA 15 minutes." \
  --data-urlencode 'Attributes={"correlationId":"req-01HPQ9K7Z9Y7J8V7Z0","visibility":"customer"}'
List messages
  • GET /v1/Conversations/{ConversationSid}/Messages?PageSize=50&Order=asc|desc
curl -sS "https://conversations.twilio.com/v1/Conversations/CH.../Messages?PageSize=50&Order=desc" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
Fetch message
  • GET /v1/Conversations/{ConversationSid}/Messages/{MessageSid}
curl -sS "https://conversations.twilio.com/v1/Conversations/CH.../Messages/IM..." \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
Delete message (moderation)
  • DELETE /v1/Conversations/{ConversationSid}/Messages/{MessageSid}
curl -sS -X DELETE "https://conversations.twilio.com/v1/Conversations/CH.../Messages/IM..." \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"

Webhooks (Conversations Service)

Conversations commonly uses a Service to configure defaults and webhooks.

  • List services: GET /v1/Services
  • Create service: POST /v1/Services
  • Configure webhooks: POST /v1/Services/{ServiceSid}/Configuration/Webhooks

Key fields (vary by webhook type):

  • PostWebhookUrl
  • PostWebhookMethod (GET|POST)
  • Filters (array of event types)
  • PreWebhookUrl, PreWebhookMethod
  • WebhookTimeout (seconds; if supported)

Because webhook configuration fields evolve, prefer SDK typing or console for initial setup; then export config into IaC (Terraform) where possible.

---

Configuration Reference

Environment variables

Recommended file: /etc/openclaw/twilio-conversations.env (Linux) or ~/.config/openclaw/twilio-conversations.env (dev)

TWILIO_ACCOUNT_SID=YOUR_ACCOUNT_SID
TWILIO_API_KEY_SID=YOUR_API_KEY_SID
TWILIO_API_KEY_SECRET=replace_me
TWILIO_AUTH_TOKEN=replace_me_for_webhook_validation_only

PUBLIC_WEBHOOK_BASE_URL=https://conversations-webhooks.acme.example
TWILIO_CONVERSATIONS_SERVICE_SID=YOUR_IS_SID

DEFAULT_PROXY_ADDRESS=+14155551234
DEFAULT_WHATSAPP_PROXY=whatsapp:+14155559876

# Compliance / suppression
SUPPRESSION_REDIS_URL=redis://:redispass@redis-1.internal:6379/2

Load with systemd unit:

/etc/systemd/system/openclaw-twilio-conversations.service:

[Unit]
Description=OpenClaw Twilio Conversations Webhook Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
EnvironmentFile=/etc/openclaw/twilio-conversations.env
WorkingDirectory=/opt/openclaw/twilio-conversations
ExecStart=/usr/bin/node dist/server.js
Restart=on-failure
RestartSec=2
User=openclaw
Group=openclaw
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/openclaw /var/log/openclaw

[Install]
WantedBy=multi-user.target

OpenClaw skill config (example)

/opt/openclaw/config/skills/twilio-conversations.toml:

[twilio_conversations]
service_sid = "YOUR_IS_SID"
default_proxy_address = "+14155551234"
default_whatsapp_proxy = "whatsapp:+14155559876"

[twilio_conversations.webhooks]
public_base_url = "https://conversations-webhooks.acme.example"
path = "/twilio/conversations/webhook"
validate_signature = true
max_processing_ms = 2000

[twilio_conversations.idempotency]
backend = "redis"
redis_url = "redis://:redispass@redis-1.internal:6379/2"
ttl_seconds = 86400

[twilio_conversations.compliance]
enforce_stop = true
stop_keywords = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"]
start_keywords = ["START", "YES", "UNSTOP"]
help_keywords = ["HELP", "INFO"]

NGINX reverse proxy

/etc/nginx/conf.d/openclaw-twilio-conversations.conf:

server {
  listen 443 ssl http2;
  server_name conversations-webhooks.acme.example;

  ssl_certificate     /etc/letsencrypt/live/conversations-webhooks.acme.example/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/conversations-webhooks.acme.example/privkey.pem;

  client_max_body_size 1m;

  location /twilio/conversations/webhook {
    proxy_pass http://127.0.0.1:3000/twilio/conversations/webhook;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_connect_timeout 2s;
    proxy_read_timeout 5s;
    proxy_send_timeout 5s;
  }
}

---

Integration Patterns

Pattern: Inbound SMS → map to Conversation → agent reply

Pipeline:

1. Programmable Messaging inbound webhook receives SMS. 2. Lookup/create conversation by uniqueName = "sms:+14155550100" (or tenant-scoped). 3. Ensure messaging participant exists with MessagingBinding.Address = sender. 4. Post inbound message into conversation (author = phone). 5. Agent replies via Conversations message API.

Key detail: inbound SMS already exists as a Messaging event; you’re mirroring into Conversations for unified thread. Ensure you don’t double-send outbound.

Pattern: Conversations as system-of-record + Messaging Service for outbound

  • Store all messages in Conversations for UI/history.
  • For outbound SMS at scale, send via Messaging API using MessagingServiceSid=MG... for geo-matching and throughput.
  • Mirror outbound message into Conversations with attributes linking to Messaging MessageSid.

This avoids sender management complexity while keeping a single thread.

Pattern: Verify before adding a new phone participant

  • User requests to add phone number to a conversation.
  • Trigger Verify V2 SMS to that number.
  • Only after successful verification, add as messaging participant.

Pattern: Escalate to Voice and attach call recording/transcript

  • When conversation hits escalation keyword, create a Voice call (TwiML Dial/Conference).
  • Enable recording and transcription.
  • Post recording URL/transcript back into conversation as a message or as conversation attributes.

Pattern: Studio flow triggered by conversation events

  • Post-event webhook ingests onMessageAdded.
  • For certain intents, call Studio REST Trigger API to run a flow.
  • Flow can send messages back via Conversations or Messaging.

---

Error Handling & Troubleshooting

Handle Twilio errors by code + HTTP status + retryability. Always log:

  • Twilio X-Twilio-Request-Id
  • HTTP status
  • error code/message
  • relevant SIDs

1) Auth failure (20003)

Error (typical):

  • HTTP 401
  • Body includes:
  • "code": 20003
  • "message": "Authenticate"

Root cause:

  • Wrong API key secret, wrong auth token, or using API key without accountSid in SDK config.

Fix:

  • Verify TWILIO_API_KEY_SID/SECRET.
  • In Node SDK, pass { accountSid: TWILIO_ACCOUNT_SID } when using API keys.
  • Rotate compromised keys.

2) Rate limiting (20429)

Error:

  • HTTP 429
  • "code": 20429
  • "message": "Too Many Requests"

Root cause:

  • Burst traffic exceeding Twilio API limits or your account’s concurrency.

Fix:

  • Implement exponential backoff with jitter (e.g., base 250ms, max 5s).
  • Queue writes; batch list operations; reduce PageSize if timeouts occur.
  • Use worker concurrency caps per account/service.

3) Invalid phone number (21211)

Error:

  • HTTP 400
  • "code": 21211
  • "message": "The 'To' number +1415555 is not a valid phone number."

Root cause:

  • Non-E.164 formatting, missing country code, invalid characters.

Fix:

  • Normalize to E.164 before adding messaging participants.
  • Use libphonenumber in your app layer.
  • Reject early with actionable error.

4) Messaging delivery failure (30003)

Error (Messaging status callback):

  • "ErrorCode": "30003"
  • "MessageStatus": "undelivered"
  • "ErrorMessage": "Unreachable destination handset." (carrier-dependent)

Root cause:

  • Carrier unreachable, handset off, blocked, or invalid route.

Fix:

  • Mark participant as unreachable; fall back to alternate channel (WhatsApp/email/voice).
  • Retry policy: limited retries; avoid infinite loops.
  • For WhatsApp, ensure templates and opt-in.

5) Webhook signature validation failures

Log:

  • Invalid Twilio signature

Root cause:

  • PUBLIC_WEBHOOK_BASE_URL mismatch with actual URL Twilio calls (scheme/host/path).
  • NGINX rewriting path or missing query string in validation URL.
  • Using JSON body parsing when Twilio sends application/x-www-form-urlencoded (or vice versa).

Fix:

  • Ensure the validation URL exactly matches Twilio configuration.
  • Preserve original host/proto via X-Forwarded-* and reconstruct correctly.
  • Confirm content-type and body parsing.

6) Participant already exists

Typical API error:

  • HTTP 409
  • Message similar to:
  • "message": "Participant already exists"

Root cause:

  • Non-idempotent add participant calls under retries/concurrency.

Fix:

  • Treat 409 as success if the participant matches desired binding.
  • Use deterministic participant lookup before create (list participants and match by identity/address).
  • Serialize participant creation per conversation.

7) Conversation not found

Error:

  • HTTP 404
  • "message": "The requested resource /Conversations/CH... was not found"

Root cause:

  • Wrong SID, deleted conversation, or cross-account SID.

Fix:

  • Validate SID prefix and length.
  • Ensure correct account credentials.
  • If using uniqueName, re-resolve by listing/filtering (or store mapping in DB).

8) Webhook timeouts and retries

Symptom:

  • Twilio retries same event multiple times; your logs show duplicates.

Root cause:

  • Your webhook handler exceeds Twilio timeout or returns non-2xx.
  • Downstream dependencies (DB) slow.

Fix:

  • Respond 200 immediately after enqueue.
  • Use durable queue (SQS/Kafka/RabbitMQ) and process async.
  • Implement idempotency with Redis keyed by EventSid TTL 24h+.

9) WhatsApp template / session issues (common)

Symptom:

  • Outbound WhatsApp fails with policy-related error (varies by region/account).

Root cause:

  • Attempting to send freeform message outside 24-hour session window; template required.

Fix:

  • Use approved templates for re-engagement.
  • Track last inbound timestamp per participant; enforce template usage.

10) Opt-out violations

Symptom:

  • Carrier filtering, complaints, or Twilio compliance warnings.

Root cause:

  • Sending SMS after STOP, or failing to honor opt-out across systems.

Fix:

  • Central suppression list; check before every outbound.
  • On STOP inbound, immediately suppress and confirm opt-out per policy.

---

Security Hardening

Secrets management

  • Do not store TWILIO_API_KEY_SECRET in repo.
  • Use:
  • AWS Secrets Manager / GCP Secret Manager / Vault
  • systemd LoadCredential= (systemd 252+) where available
  • Rotate API keys quarterly; immediately on incident.

Webhook verification

  • Always validate X-Twilio-Signature.
  • Enforce HTTPS only; redirect HTTP → HTTPS at edge.
  • Reject requests with missing signature.

Least privilege

  • Use separate API keys per service (webhooks vs batch jobs).
  • Restrict key usage by internal policy (Twilio keys are account-wide; enforce via network segmentation and secret distribution).

Data minimization

  • Avoid storing full message bodies if not required; store hashes/metadata.
  • If storing bodies, encrypt at rest (Postgres TDE alternative: disk encryption + app-level envelope encryption).

CIS-aligned host hardening (practical mapping)

  • CIS Ubuntu Linux 22.04 LTS Benchmark:
  • Disable password SSH auth; enforce key-based.
  • Enable automatic security updates.
  • Restrict inbound ports to 443 only for webhook edge.
  • Run service as non-root (openclaw user).
  • systemd sandboxing:
  • NoNewPrivileges=true
  • ProtectSystem=strict
  • ProtectHome=true
  • PrivateTmp=true

Audit logging

  • Log all admin actions:
  • conversation create/close/delete
  • participant add/remove
  • outbound message send
  • Include correlation IDs and actor identity.

---

Performance Tuning

Webhook ingestion latency

Goal: p95 webhook handler < 50ms, always < 500ms.

Optimizations:

  • Parse and validate signature, then enqueue and return 200.
  • Avoid synchronous DB writes in request thread.

Expected impact:

  • Before: p95 800–1500ms under DB contention → Twilio retries.
  • After: p95 10–30ms; retries near-zero.

API rate limiting and batching

  • Use PageSize=200 for list operations to reduce round trips, but watch response size/timeouts.
  • Cache conversation SID by uniqueName in Redis (TTL 1h) to avoid list/search calls.

Expected impact:

  • Reduce Twilio API calls by 60–90% in high-traffic routing services.

Participant lookup strategy

  • Maintain your own mapping table:
  • (tenantId, externalUserId) -> conversationSid
  • (tenantId, phoneE164) -> conversationSid
  • Avoid listing participants/messages to “discover” state.

Expected impact:

  • Avoid O(n) scans; stable latency as conversation size grows.

Message fanout control

If you add many participants (group chats), outbound message fanout can be expensive and slow.

  • Enforce max participants per conversation (policy).
  • For broadcast, use Messaging API + segmentation rather than a single conversation.

---

Advanced Topics

Pre-event webhooks for authorization

Use pre-event webhook to:

  • Block participant additions from unknown identities.
  • Enforce tenant boundaries (identity must match conversation attributes).
  • Reject messages containing disallowed content (PII leakage) before they are accepted.

Implementation notes:

  • Pre-event webhook must be highly available; failures can block message flow.
  • Return explicit allow/deny per Twilio’s pre-event webhook contract.

Idempotent conversation creation

Twilio doesn’t guarantee atomic “create if not exists by uniqueName” across concurrent callers.

Pattern: 1. Try create with UniqueName. 2. If conflict/duplicate occurs, fetch by UniqueName via your mapping DB (preferred) or list/filter (fallback). 3. Store mapping.

Multi-tenant isolation

  • Prefix uniqueName with tenant: t_acme_case_10492
  • Store tenantId in attributes.
  • Validate tenant on every webhook event before processing.

Message ordering and eventual consistency

  • Webhook events can arrive out of order.
  • Use event timestamps and message index (if provided) to order.
  • Treat webhooks as an event stream; build derived state with replay capability.

Media handling

  • Media messages may require separate retrieval and storage policies.
  • Enforce content-type allowlist and size limits.
  • Consider virus scanning for inbound media before exposing to internal users.

Interop with Programmable Messaging status callbacks

You may receive:

  • Conversations post-event webhooks (message added)
  • Messaging status callbacks (delivered/failed)

Unify by correlating:

  • Store Messaging MessageSid in Conversations message attributes when you send via Messaging API.
  • On status callback, update your internal message state and optionally post a system message into the conversation (or update external UI).

---

Usage Examples

Scenario 1: Create conversation for a support case, add SMS customer + chat agent, send initial message

# 1) Create conversation
CONV_SID=$(curl -sS -X POST "https://conversations.twilio.com/v1/Conversations" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "FriendlyName=Support Case 10492" \
  --data-urlencode "UniqueName=t_acme_case_10492" \
  --data-urlencode 'Attributes={"tenantId":"acme","caseId":10492,"priority":"p1"}' | jq -r .sid)

echo "$CONV_SID"

# 2) Add SMS participant (customer)
curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/$CONV_SID/Participants" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "MessagingBinding.Address=+14155550100" \
  --data-urlencode "MessagingBinding.ProxyAddress=+14155551234" \
  --data-urlencode 'Attributes={"role":"customer","channel":"sms"}' | jq .

# 3) Add chat participant (agent)
curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/$CONV_SID/Participants" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "Identity=agent-7" \
  --data-urlencode 'Attributes={"role":"agent"}' | jq .

# 4) Send message
curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/$CONV_SID/Messages" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "Author=agent-7" \
  --data-urlencode "Body=Hi—this is Acme Support. We’re on it." \
  --data-urlencode 'Attributes={"correlationId":"req-01HPQ9K7Z9Y7J8V7Z0"}' | jq .

Scenario 2: Inbound STOP handling with suppression list (Redis) and participant removal

Python snippet to process inbound message webhook (from Messaging) and enforce STOP:

import os
import redis
from twilio.rest import Client

STOP_WORDS = {"STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"}
START_WORDS = {"START", "YES", "UNSTOP"}

r = redis.Redis.from_url(os.environ["SUPPRESSION_REDIS_URL"])
client = Client(os.environ["TWILIO_API_KEY_SID"], os.environ["TWILIO_API_KEY_SECRET"], os.environ["TWILIO_ACCOUNT_SID"])

def handle_inbound_sms(from_e164: str, body: str, conversation_sid: str, participant_sid: str):
    normalized = body.strip().upper()
    key = f"suppress:sms:{from_e164}"

    if normalized in STOP_WORDS:
        r.set(key, "1")
        # Remove participant to prevent further outbound via Conversations
        client.conversations.v1.conversations(conversation_sid).participants(participant_sid).delete()
        return {"action": "suppressed"}

    if normalized in START_WORDS:
        r.delete(key)
        return {"action": "unsuppressed"}

    if r.get(key):
        return {"action": "ignored_suppressed"}

    return {"action": "accepted"}

Scenario 3: Webhook ingestion with idempotency (Redis) keyed by EventSid

Node snippet (core logic):

import Redis from "ioredis";

const redis = new Redis(process.env.SUPPRESSION_REDIS_URL);

export async function dedupeEvent(eventSid) {
  const key = `twilio:event:${eventSid}`;
  const ok = await redis.set(key, "1", "NX", "EX", 86400);
  return ok === "OK"; // true if first time
}

In webhook handler:

  • If dedupeEvent(EventSid) is false, return 200 immediately.

Scenario 4: Escalate to Voice conference and post recording link back into conversation

High-level steps: 1. Create Voice conference via TwiML <Dial><Conference record="record-from-start">case-10492</Conference></Dial> 2. On recording.completed webhook, post message into conversation with recording URL.

Posting back:

curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/CH.../Messages" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "Author=system" \
  --data-urlencode "Body=Call recording available: https://api.twilio.com/2010-04-01/Accounts/AC.../Recordings/RE... .mp3" \
  --data-urlencode 'Attributes={"type":"call_recording","recordingSid":"RE0123456789abcdef0123456789abcdef"}'

Scenario 5: WhatsApp re-engagement using templates + mirror into conversation

Pattern:

  • If last inbound > 24h, send WhatsApp template via Messaging API (not freeform).
  • Mirror template send into conversation with attributes.

Mirror message:

curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/CH.../Messages" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "Author=system" \
  --data-urlencode "Body=Sent WhatsApp template: order_update_v2" \
  --data-urlencode 'Attributes={"channel":"whatsapp","template":"order_update_v2","messagingMessageSid":"SM0123456789abcdef0123456789abcdef"}'

Scenario 6: Bulk close inactive conversations safely

Python batch job:

  • List conversations, filter by last activity (from your DB or Twilio fields if available), set State=closed.
import os
from twilio.rest import Client

client = Client(os.environ["TWILIO_API_KEY_SID"], os.environ["TWILIO_API_KEY_SECRET"], os.environ["TWILIO_ACCOUNT_SID"])

for conv in client.conversations.v1.conversations.list(limit=200):
    # Prefer your own last_activity tracking; Twilio fields vary by API.
    if conv.state == "active" and conv.friendly_name.startswith("Support Case"):
        client.conversations.v1.conversations(conv.sid).update(state="closed")
        print("closed", conv.sid)

---

Quick Reference

TaskEndpoint / CommandKey fields / flags
Create conversationPOST /v1/ConversationsFriendlyName, UniqueName, Attributes, State
List conversationsGET /v1/ConversationsPageSize, PageToken
Update conversationPOST /v1/Conversations/{CH}State, Attributes, FriendlyName
Delete conversationDELETE /v1/Conversations/{CH}n/a
Add chat participantPOST /v1/Conversations/{CH}/ParticipantsIdentity, Attributes
Add SMS participantsameMessagingBinding.Address, MessagingBinding.ProxyAddress
Add WhatsApp participantsameMessagingBinding.Address=whatsapp:+E164, ProxyAddress=whatsapp:+E164
List participantsGET /v1/Conversations/{CH}/ParticipantsPageSize, PageToken
Remove participantDELETE /v1/Conversations/{CH}/Participants/{MB}n/a
Send messagePOST /v1/Conversations/{CH}/MessagesAuthor, Body, Attributes
List messagesGET /v1/Conversations/{CH}/MessagesPageSize, Order, PageToken
Webhook validationX-Twilio-SignatureValidate against exact URL + params
Retry handlingn/aDedupe by EventSid, backoff on 20429

---

Graph Relationships

DEPENDS_ON

  • twilio-core-auth (API keys, token rotation, request signing validation patterns)
  • webhook-ingestion (idempotency, queueing, backpressure, signature verification)
  • redis (optional; idempotency/suppression)
  • postgres (optional; event store / audit log)

COMPOSES

  • twilio-programmable-messaging (SMS/MMS/WhatsApp delivery callbacks, STOP handling, 10DLC/toll-free)
  • twilio-voice (escalation, recordings, transcription, IVR state machines)
  • twilio-verify (step-up verification before participant add / sensitive actions)
  • twilio-studio (flow triggers based on conversation events)
  • sendgrid (transactional email mirroring into conversation threads)

SIMILAR_TO

  • slack-conversations (thread + participant model, event-driven updates)
  • zendesk-ticketing (case/ticket lifecycle mapped to conversation state)
  • intercom-messaging (omnichannel messaging with identity + contact bindings)

Related skills

FAQ

Which channels does it unify?

SMS, WhatsApp, and in-app chat into a single conversation thread abstraction.

What auth is recommended?

A Twilio API Key (TWILIO_API_KEY_SID / TWILIO_API_KEY_SECRET) is recommended over Account SID plus Auth Token for production.

Backend & APIsbackendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.