CtrlK
BlogDocsLog inGet started
Tessl Logo

twilio-conversations-classic-api

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.

68

Quality

82%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

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_SID
    • TWILIO_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 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" });

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-Signature header in production to confirm requests originate from Twilio. See twilio-webhook-architecture for 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);
});

Advanced Context

Message Delivery Status

Track message delivery through webhooks. Configure at Console > Conversations > Manage > Global Webhooks.

Available delivery events:

  • onMessageAdded — Message created
  • onMessageUpdated — Message status changed
  • onDeliveryUpdated — 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);
});

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

LimitValue
Participants per conversation1,000
Messages per conversationUnlimited (older messages may be archived)
Message retentionConfigurable (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
Repository
openai/plugins
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.