From twilio-developer-kit
Build multi-channel messaging experiences using Twilio Conversations (classic) API. Covers creating conversations, adding participants (SMS, WhatsApp, chat), sending messages, and handling webhooks. Use this skill to manage persistent multi-party or multi-channel conversations beyond single-message SMS/WhatsApp.
npx claudepluginhub twilio/ai --plugin twilio-developer-kitThis skill uses the workspace's default tool permissions.
Conversations (classic) API provides persistent, multi-channel threads where participants on SMS, WhatsApp, and web chat can message together. Unlike single-message APIs, Conversations maintains history and supports multi-agent access.
Guides Next.js Cache Components and Partial Prerendering (PPR): 'use cache' directives, cacheLife(), cacheTag(), revalidateTag() for caching, invalidation, static/dynamic optimization. Auto-activates on cacheComponents: true.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Share bugs, ideas, or general feedback.
Conversations (classic) API provides persistent, multi-channel threads where participants on SMS, WhatsApp, and web chat can message together. Unlike single-message APIs, Conversations maintains history and supports multi-agent access.
Note: This is the Conversations (classic) API (v1).
twilio-account-setup
— Enable at: Console > Conversations > Manage > Overview > Enable ConversationsTWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKEN
— See twilio-iam-auth-setup for credential setup and best practicespip install twilio / npm install twilioA Conversation Service is the parent configuration container for all your conversations in the classic API. You need one before creating conversations with SMS/WhatsApp participants.
Python
import os
from twilio.rest import Client
client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])
# Create a Conversation Service
service = client.conversations.v1.services.create(
friendly_name="Customer Support Service"
)
print(f"Service SID: {service.sid}")
Node.js
const twilio = require("twilio");
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
// Create a Conversation Service
const service = await client.conversations.v1.services.create({
friendlyName: "Customer Support Service"
});
console.log(`Service SID: ${service.sid}`);
Next: Assign your Twilio phone number to this service at Console > Conversations > Manage > Services > Select your service > Add phone number.
Python
import os
from twilio.rest import Client
client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])
# Create a conversation (use default service or specify service_sid)
conversation = client.conversations.v1.conversations.create(
friendly_name="Customer Support - Order #12345"
)
# Add an SMS participant
client.conversations.v1 \
.conversations(conversation.sid) \
.participants \
.create(
messaging_binding_address="+15558675310",
messaging_binding_proxy_address="+15017122661"
)
# Send a message
client.conversations.v1 \
.conversations(conversation.sid) \
.messages \
.create(body="Hello! How can I help you today?", author="support-agent")
Node.js
const twilio = require("twilio");
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
// Create a conversation (use default service or specify serviceSid)
const conversation = await client.conversations.v1.conversations.create({
friendlyName: "Customer Support - Order #12345",
});
// Add an SMS participant
await client.conversations.v1
.conversations(conversation.sid)
.participants.create({
messagingBindingAddress: "+15558675310",
messagingBindingProxyAddress: "+15017122661",
});
// Send a message
await client.conversations.v1
.conversations(conversation.sid)
.messages.create({ body: "Hello! How can I help you today?", author: "support-agent" });
WhatsApp participant — Python
client.conversations.v1 \
.conversations(conversation.sid) \
.participants \
.create(
messaging_binding_address="whatsapp:+15558675310",
messaging_binding_proxy_address="whatsapp:+14155238886"
)
WhatsApp participant — Node.js
await client.conversations.v1
.conversations(conversationSid)
.participants.create({
messagingBindingAddress: "whatsapp:+15558675310",
messagingBindingProxyAddress: "whatsapp:+14155238886",
});
Chat participant (web/mobile) — Python
client.conversations.v1 \
.conversations(conversation.sid) \
.participants \
.create(identity="user-123")
Chat participant (web/mobile) — Node.js
await client.conversations.v1
.conversations(conversationSid)
.participants.create({ identity: "user-123" });
Python
# Send a message with media
client.conversations.v1 \
.conversations(conversation.sid) \
.messages \
.create(
body="Check out this image!",
author="support-agent",
media_url="https://example.com/image.jpg"
)
# Multiple media URLs (up to 10)
client.conversations.v1 \
.conversations(conversation.sid) \
.messages \
.create(
body="Here are the documents",
author="support-agent",
media_url=[
"https://example.com/doc1.pdf",
"https://example.com/doc2.pdf"
]
)
Node.js
// Send a message with media
await client.conversations.v1
.conversations(conversationSid)
.messages.create({
body: "Check out this image!",
author: "support-agent",
mediaUrl: "https://example.com/image.jpg"
});
// Multiple media URLs (up to 10)
await client.conversations.v1
.conversations(conversationSid)
.messages.create({
body: "Here are the documents",
author: "support-agent",
mediaUrl: [
"https://example.com/doc1.pdf",
"https://example.com/doc2.pdf"
]
});
Media must be publicly accessible URLs. Supported: JPG, PNG, GIF, PDF, vCard. Max 10 URLs per message. Works across all channels: SMS (as MMS), WhatsApp, and chat participants all receive media.
Python
# Add multiple SMS participants to a conversation
participant_numbers = [
"+15558675310",
"+15558675311",
"+15558675312"
]
twilio_number = "+15017122661"
for phone_number in participant_numbers:
client.conversations.v1 \
.conversations(conversation.sid) \
.participants \
.create(
messaging_binding_address=phone_number,
messaging_binding_proxy_address=twilio_number
)
Node.js
// Add multiple SMS participants to a conversation
const participantNumbers = [
"+15558675310",
"+15558675311",
"+15558675312"
];
const twilioNumber = "+15017122661";
for (const phoneNumber of participantNumbers) {
await client.conversations.v1
.conversations(conversationSid)
.participants.create({
messagingBindingAddress: phoneNumber,
messagingBindingProxyAddress: twilioNumber
});
}
Python
# Get all messages from a conversation
messages = client.conversations.v1 \
.conversations(conversation.sid) \
.messages \
.list(limit=50)
for message in messages:
print(f"{message.author}: {message.body}")
Node.js
// Get all messages from a conversation
const messages = await client.conversations.v1
.conversations(conversationSid)
.messages
.list({ limit: 50 });
messages.forEach(message => {
console.log(`${message.author}: ${message.body}`);
});
Python
# List all conversations
conversations = client.conversations.v1.conversations.list(limit=20)
for conv in conversations:
print(f"{conv.friendly_name} - {conv.sid}")
# Filter by state
active_conversations = client.conversations.v1.conversations.list(
state="active",
limit=20
)
Node.js
// List all conversations
const conversations = await client.conversations.v1.conversations.list({ limit: 20 });
conversations.forEach(conv => {
console.log(`${conv.friendlyName} - ${conv.sid}`);
});
// Filter by state
const activeConversations = await client.conversations.v1.conversations.list({
state: "active",
limit: 20
});
Python
# Remove a participant by participant SID
client.conversations.v1 \
.conversations(conversation.sid) \
.participants(participant_sid) \
.delete()
# Find and remove by phone number
participants = client.conversations.v1 \
.conversations(conversation.sid) \
.participants \
.list()
for p in participants:
if p.messaging_binding and p.messaging_binding.get("address") == "+15558675310":
client.conversations.v1 \
.conversations(conversation.sid) \
.participants(p.sid) \
.delete()
Node.js
// Remove a participant by participant SID
await client.conversations.v1
.conversations(conversationSid)
.participants(participantSid)
.remove();
// Find and remove by phone number
const participants = await client.conversations.v1
.conversations(conversationSid)
.participants
.list();
for (const p of participants) {
if (p.messagingBinding?.address === "+15558675310") {
await client.conversations.v1
.conversations(conversationSid)
.participants(p.sid)
.remove();
}
}
Python
# Close a conversation (marks it inactive)
client.conversations.v1 \
.conversations(conversation.sid) \
.update(state="closed")
# Delete a conversation completely
client.conversations.v1 \
.conversations(conversation.sid) \
.delete()
Node.js
// Close a conversation (marks it inactive)
await client.conversations.v1
.conversations(conversationSid)
.update({ state: "closed" });
// Delete a conversation completely
await client.conversations.v1
.conversations(conversationSid)
.remove();
Configure at Console > Conversations > Manage > Global Webhooks.
Security: Always validate the
X-Twilio-Signatureheader in production to confirm requests originate from Twilio. Seetwilio-webhook-architecturefor validation patterns.
Python (Flask)
from twilio.request_validator import RequestValidator
@app.route("/conversations/webhook", methods=["POST"])
def conversations_webhook():
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])
if not validator.validate(request.url, request.form, request.headers.get("X-Twilio-Signature", "")):
return "", 403
event_type = request.form.get("EventType")
conversation_sid = request.form.get("ConversationSid")
author = request.form.get("Author")
if event_type == "onMessageAdded" and author != "support-agent":
client.conversations.v1.conversations(conversation_sid).messages.create(
body="Thanks — an agent will be with you shortly.",
author="support-bot"
)
return "", 204
Node.js (Express)
app.post("/conversations/webhook", async (req, res) => {
const valid = twilio.validateRequest(
process.env.TWILIO_AUTH_TOKEN,
req.headers["x-twilio-signature"],
`https://${req.headers.host}${req.originalUrl}`,
req.body
);
if (!valid) return res.status(403).send("Forbidden");
const { EventType, ConversationSid, Author } = req.body;
if (EventType === "onMessageAdded" && Author !== "support-agent") {
await client.conversations.v1
.conversations(ConversationSid)
.messages.create({ body: "Thanks — an agent will be with you shortly.", author: "support-bot" });
}
res.sendStatus(204);
});
Track message delivery through webhooks. Configure at Console > Conversations > Manage > Global Webhooks.
Available delivery events:
onMessageAdded — Message createdonMessageUpdated — Message status changedonDeliveryUpdated — Delivery receipt received (SMS/WhatsApp only)Python (Flask)
@app.route("/conversations/webhook", methods=["POST"])
def delivery_webhook():
event_type = request.form.get("EventType")
if event_type == "onDeliveryUpdated":
delivery_status = request.form.get("DeliveryStatus")
message_sid = request.form.get("MessageSid")
print(f"Message {message_sid}: {delivery_status}")
# Status values: sent, delivered, failed, undelivered
return "", 204
Node.js (Express)
app.post("/conversations/webhook", (req, res) => {
const { EventType, DeliveryStatus, MessageSid } = req.body;
if (EventType === "onDeliveryUpdated") {
console.log(`Message ${MessageSid}: ${DeliveryStatus}`);
// Status values: sent, delivered, failed, undelivered
}
res.sendStatus(204);
});
Store custom metadata on conversations (order IDs, customer info, tags).
Python
# Set attributes when creating
conversation = client.conversations.v1.conversations.create(
friendly_name="Customer Support - Order #12345",
attributes='{"order_id": "12345", "priority": "high", "customer_tier": "gold"}'
)
# Update attributes on existing conversation
client.conversations.v1 \
.conversations(conversation.sid) \
.update(attributes='{"order_id": "12345", "status": "resolved"}')
# Read attributes
conv = client.conversations.v1.conversations(conversation.sid).fetch()
import json
attrs = json.loads(conv.attributes)
print(f"Order ID: {attrs['order_id']}")
Node.js
// Set attributes when creating
const conversation = await client.conversations.v1.conversations.create({
friendlyName: "Customer Support - Order #12345",
attributes: JSON.stringify({ orderId: "12345", priority: "high", customerTier: "gold" })
});
// Update attributes on existing conversation
await client.conversations.v1
.conversations(conversationSid)
.update({ attributes: JSON.stringify({ orderId: "12345", status: "resolved" }) });
// Read attributes
const conv = await client.conversations.v1.conversations(conversationSid).fetch();
const attrs = JSON.parse(conv.attributes);
console.log(`Order ID: ${attrs.orderId}`);
Store metadata on individual participants (role, name, account info).
Python
# Set attributes when adding participant
client.conversations.v1 \
.conversations(conversation.sid) \
.participants \
.create(
messaging_binding_address="+15558675310",
messaging_binding_proxy_address="+15017122661",
attributes='{"name": "John Doe", "role": "customer", "account_id": "A123"}'
)
# Update participant attributes
client.conversations.v1 \
.conversations(conversation.sid) \
.participants(participant_sid) \
.update(attributes='{"role": "vip_customer", "satisfaction": "high"}')
Node.js
// Set attributes when adding participant
await client.conversations.v1
.conversations(conversationSid)
.participants.create({
messagingBindingAddress: "+15558675310",
messagingBindingProxyAddress: "+15017122661",
attributes: JSON.stringify({ name: "John Doe", role: "customer", accountId: "A123" })
});
// Update participant attributes
await client.conversations.v1
.conversations(conversationSid)
.participants(participantSid)
.update({ attributes: JSON.stringify({ role: "vip_customer", satisfaction: "high" }) });
| Limit | Value |
|---|---|
| Participants per conversation | 1,000 |
| Messages per conversation | Unlimited (older messages may be archived) |
| Message retention | Configurable (default: indefinite) |
twilio-whatsapp-send-messagetwilio-iam-auth-setuptwilio-whatsapp-send-messagetwilio-sms-send-messagetwilio-iam-auth-setuptwilio-webhook-architecture