
twilio-conversations-classic-api
✓ Official★ 4,081by openai · part of openai/plugins
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.
This is the playbook your agent receives when the skill activates — you don't need to read it to use the skill, but it's here to audit before installing.
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).
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 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.
Add Multiple Participants
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
});
}Fetch Message History
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}`);
});List Conversations
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
});Remove Participants
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();
}
}Close/Complete Conversations
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();Handle Incoming Messages (Webhook)
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 "", 204Node.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, undelivered
return "", 204Node.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 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}`);Participant Attributes (Metadata)
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" }) });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
npx skills add https://github.com/openai/plugins --skill twilio-conversations-classic-apiRun this in your project — your agent picks the skill up automatically.
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— Seetwilio-iam-auth-setupfor 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 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.
Quickstart
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" });No common issues documented yet. If you hit a problem, the repository's GitHub Issues page is the best place to look.