Skill v1.0.0
Trusted Publisher100/100version: "1.0.0" name: twilio-conversations-classic-api description: > 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.
Overview
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).
Prerequisites
- Twilio account with Conversations (classic) enabled
— New to Twilio? See twilio-account-setup — Enable at: Console > Conversations > Manage > Overview > Enable Conversations
- Environment variables:
TWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKEN
— See twilio-iam-auth-setup for credential setup and best practices
- SDK:
pip install twilio/npm install twilio - For SMS/WhatsApp participants: a Twilio number assigned to a Conversations Service
Setup: Create a Conversation Service (classic)
A 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 osfrom twilio.rest import Clientclient = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])# Create a Conversation Serviceservice = 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 Serviceconst 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.
Quickstart
Python
import osfrom twilio.rest import Clientclient = 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 participantclient.conversations.v1 \.conversations(conversation.sid) \.participants \.create(messaging_binding_address="+15558675310",messaging_binding_proxy_address="+15017122661")# Send a messageclient.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 participantawait client.conversations.v1.conversations(conversation.sid).participants.create({messagingBindingAddress: "+15558675310",messagingBindingProxyAddress: "+15017122661",});// Send a messageawait client.conversations.v1.conversations(conversation.sid).messages.create({ body: "Hello! How can I help you today?", author: "support-agent" });
Key Patterns
Add Participants by Channel
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" });
Send Media (All Channels)
Python
# Send a message with mediaclient.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 mediaawait 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.
Add Multiple Participants
Python
# Add multiple SMS participants to a conversationparticipant_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 conversationconst participantNumbers = ["+15558675310","+15558675311","+15558675312"];const twilioNumber = "+15017122661";for (const phoneNumber of participantNumbers) {await client.conversations.v1.conversations(conversationSid).participants.create({messagingBindingAddress: phoneNumber,messagingBindingProxyAddress: twilioNumber});}
Fetch Message History
Python
# Get all messages from a conversationmessages = 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 conversationconst messages = await client.conversations.v1.conversations(conversationSid).messages.list({ limit: 50 });messages.forEach(message => {console.log(`${message.author}: ${message.body}`);});
List Conversations
Python
# List all conversationsconversations = client.conversations.v1.conversations.list(limit=20)for conv in conversations:print(f"{conv.friendly_name} - {conv.sid}")# Filter by stateactive_conversations = client.conversations.v1.conversations.list(state="active",limit=20)
Node.js
// List all conversationsconst conversations = await client.conversations.v1.conversations.list({ limit: 20 });conversations.forEach(conv => {console.log(`${conv.friendlyName} - ${conv.sid}`);});// Filter by stateconst activeConversations = await client.conversations.v1.conversations.list({state: "active",limit: 20});
Remove Participants
Python
# Remove a participant by participant SIDclient.conversations.v1 \.conversations(conversation.sid) \.participants(participant_sid) \.delete()# Find and remove by phone numberparticipants = 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 SIDawait client.conversations.v1.conversations(conversationSid).participants(participantSid).remove();// Find and remove by phone numberconst 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();}}
Close/Complete Conversations
Python
# Close a conversation (marks it inactive)client.conversations.v1 \.conversations(conversation.sid) \.update(state="closed")# Delete a conversation completelyclient.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 completelyawait client.conversations.v1.conversations(conversationSid).remove();
Handle Incoming Messages (Webhook)
Configure at Console > Conversations > Manage > Global Webhooks.
Security: Always validate theX-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 "", 403event_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);});
Advanced Context
Message Delivery Status
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, undeliveredreturn "", 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);});
Conversation Attributes (Metadata)
Store custom metadata on conversations (order IDs, customer info, tags).
Python
# Set attributes when creatingconversation = client.conversations.v1.conversations.create(friendly_name="Customer Support - Order #12345",attributes='{"order_id": "12345", "priority": "high", "customer_tier": "gold"}')# Update attributes on existing conversationclient.conversations.v1 \.conversations(conversation.sid) \.update(attributes='{"order_id": "12345", "status": "resolved"}')# Read attributesconv = client.conversations.v1.conversations(conversation.sid).fetch()import jsonattrs = json.loads(conv.attributes)print(f"Order ID: {attrs['order_id']}")
Node.js
// Set attributes when creatingconst 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 conversationawait client.conversations.v1.conversations(conversationSid).update({ attributes: JSON.stringify({ orderId: "12345", status: "resolved" }) });// Read attributesconst conv = await client.conversations.v1.conversations(conversationSid).fetch();const attrs = JSON.parse(conv.attributes);console.log(`Order ID: ${attrs.orderId}`);
Participant Attributes (Metadata)
Store metadata on individual participants (role, name, account info).
Python
# Set attributes when adding participantclient.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 attributesclient.conversations.v1 \.conversations(conversation.sid) \.participants(participant_sid) \.update(attributes='{"role": "vip_customer", "satisfaction": "high"}')
Node.js
// Set attributes when adding participantawait client.conversations.v1.conversations(conversationSid).participants.create({messagingBindingAddress: "+15558675310",messagingBindingProxyAddress: "+15017122661",attributes: JSON.stringify({ name: "John Doe", role: "customer", accountId: "A123" })});// Update participant attributesawait client.conversations.v1.conversations(conversationSid).participants(participantSid).update({ attributes: JSON.stringify({ role: "vip_customer", satisfaction: "high" }) });
Limits
| Limit | Value | |
|---|---|---|
| Participants per conversation | 1,000 | |
| Messages per conversation | Unlimited (older messages may be archived) | |
| Message retention | Configurable (default: indefinite) |
CANNOT
- Cannot add SMS participants without a Twilio number — Number must be assigned to a Conversations (classic) Service
- Cannot send WhatsApp messages outside the 24-hour window — Subject to service window rules. See
twilio-whatsapp-send-message - Cannot use chat participants without Access Tokens — Client-side SDK auth required. See
twilio-iam-auth-setup - Cannot use WhatsApp Groups API — Deprecated April 2020. Use Conversations (classic) API instead.
- Conversations v1 (classic) is in maintenance mode — Consider Conversations v2 API for new projects with enhanced features and scalability.
Next Steps
- WhatsApp setup and rules:
twilio-whatsapp-send-message - SMS setup:
twilio-sms-send-message - Access Tokens for chat clients:
twilio-iam-auth-setup - Webhook security:
twilio-webhook-architecture