skills/twilio/twilio-conversations/SKILL.md
Unified: omnichannel SMS+WhatsApp+chat, conversation/participant/message management, webhooks
npx skillsauth add alphaonedev/openclaw-graph twilio-conversationsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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:
ngrok or cloudflared.Use Twilio API Key (recommended) instead of Account SID + Auth Token for production services.
TWILIO_ACCOUNT_SID (starts with AC...)TWILIO_API_KEY_SID (starts with SK...)TWILIO_API_KEY_SECRETEnvironment 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"
api.twilio.com and conversations.twilio.com.sid (CH...), uniqueName, attributes, timers, and state.identity="user-123"messagingBinding.address="+14155550100" and proxyAddress (Twilio number or Messaging Service sender)whatsapp:+14155550100AC...CH...MB... (varies)IM...IS... (if using Services)MG...Twilio signs webhook requests with X-Twilio-Signature. Validate using the exact URL Twilio called (including query string) and the POST params.
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
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
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
Homebrew:
brew update
brew install jq node@20 [email protected]
node -v
python3.11 --version
mkdir -p twilio-conversations-service && cd twilio-conversations-service
npm init -y
npm install [email protected] [email protected] [email protected] [email protected] [email protected]
npm install --save-dev [email protected] [email protected] @types/[email protected] @types/[email protected]
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
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
In Twilio Console:
https://your-domain.example.com/twilio/conversations/webhookFor 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).
uniqueName for idempotent lookup.attributes JSON for business metadata (tenantId, caseId, SLA).state (active/inactive/closed), timers, and cleanup.identity (for SDK users).messagingBinding.address and proxyAddress (Twilio sender).whatsapp:+E164.Production patterns:
author and body.attributes for correlation IDs.X-Twilio-Signature.EventSid (preferred) or (MessageSid, EventType, Timestamp).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).This skill assumes OpenClaw can execute via:
https://conversations.twilio.com/v1Auth options:
TWILIO_API_KEY_SIDTWILIO_API_KEY_SECRETexport TWILIO_API_KEY_SID="YOUR_API_KEY_SID"
export TWILIO_API_KEY_SECRET="YOUR_API_KEY_SECRET"
export TWILIO_ACCOUNT_SID="YOUR_ACCOUNT_SID"
Endpoint:
POST /v1/ConversationsFlags/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 supportcurl:
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);
GET /v1/Conversations/{ConversationSid}curl -sS "https://conversations.twilio.com/v1/Conversations/CHXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
GET /v1/Conversations?PageSize=50&PageToken=...Query params:
PageSize (1–1000; practical: 50–200)PageToken (string)State filter may be available depending on API versioncurl -sS "https://conversations.twilio.com/v1/Conversations?PageSize=50" \
-u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
POST /v1/Conversations/{ConversationSid}Fields:
FriendlyNameAttributes (stringified JSON)Statecurl -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 /v1/Conversations/{ConversationSid}curl -sS -X DELETE "https://conversations.twilio.com/v1/Conversations/CHXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
POST /v1/Conversations/{ConversationSid}/ParticipantsFields:
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"}'
Fields:
MessagingBinding.Address (E.164, e.g. +14155550100)MessagingBinding.ProxyAddress (Twilio number in E.164, e.g. +14155551234)Attributescurl -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"}'
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"}'
GET /v1/Conversations/{ConversationSid}/Participants?PageSize=50curl -sS "https://conversations.twilio.com/v1/Conversations/CH.../Participants?PageSize=50" \
-u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
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"
POST /v1/Conversations/{ConversationSid}/MessagesFields:
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"}'
GET /v1/Conversations/{ConversationSid}/Messages?PageSize=50&Order=asc|desccurl -sS "https://conversations.twilio.com/v1/Conversations/CH.../Messages?PageSize=50&Order=desc" \
-u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
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 /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"
Conversations commonly uses a Service to configure defaults and webhooks.
GET /v1/ServicesPOST /v1/ServicesPOST /v1/Services/{ServiceSid}/Configuration/WebhooksKey fields (vary by webhook type):
PostWebhookUrlPostWebhookMethod (GET|POST)Filters (array of event types)PreWebhookUrl, PreWebhookMethodWebhookTimeout (seconds; if supported)Because webhook configuration fields evolve, prefer SDK typing or console for initial setup; then export config into IaC (Terraform) where possible.
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://:[email protected]: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
/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://:[email protected]: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"]
/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;
}
}
Pipeline:
uniqueName = "sms:+14155550100" (or tenant-scoped).MessagingBinding.Address = sender.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.
MessagingServiceSid=MG... for geo-matching and throughput.MessageSid.This avoids sender management complexity while keeping a single thread.
onMessageAdded.Handle Twilio errors by code + HTTP status + retryability. Always log:
X-Twilio-Request-IdError (typical):
"code": 20003"message": "Authenticate"Root cause:
accountSid in SDK config.Fix:
TWILIO_API_KEY_SID/SECRET.{ accountSid: TWILIO_ACCOUNT_SID } when using API keys.Error:
"code": 20429"message": "Too Many Requests"Root cause:
Fix:
PageSize if timeouts occur.Error:
"code": 21211"message": "The 'To' number +1415555 is not a valid phone number."Root cause:
Fix:
Error (Messaging status callback):
"ErrorCode": "30003""MessageStatus": "undelivered""ErrorMessage": "Unreachable destination handset." (carrier-dependent)Root cause:
Fix:
Log:
Invalid Twilio signatureRoot cause:
PUBLIC_WEBHOOK_BASE_URL mismatch with actual URL Twilio calls (scheme/host/path).application/x-www-form-urlencoded (or vice versa).Fix:
X-Forwarded-* and reconstruct correctly.Typical API error:
"message": "Participant already exists"Root cause:
Fix:
Error:
"message": "The requested resource /Conversations/CH... was not found"Root cause:
Fix:
uniqueName, re-resolve by listing/filtering (or store mapping in DB).Symptom:
Root cause:
Fix:
EventSid TTL 24h+.Symptom:
Root cause:
Fix:
Symptom:
Root cause:
Fix:
TWILIO_API_KEY_SECRET in repo.LoadCredential= (systemd 252+) where availableX-Twilio-Signature.openclaw user).NoNewPrivileges=trueProtectSystem=strictProtectHome=truePrivateTmp=trueGoal: p95 webhook handler < 50ms, always < 500ms.
Optimizations:
Expected impact:
PageSize=200 for list operations to reduce round trips, but watch response size/timeouts.uniqueName in Redis (TTL 1h) to avoid list/search calls.Expected impact:
(tenantId, externalUserId) -> conversationSid(tenantId, phoneE164) -> conversationSidExpected impact:
If you add many participants (group chats), outbound message fanout can be expensive and slow.
Use pre-event webhook to:
Implementation notes:
Twilio doesn’t guarantee atomic “create if not exists by uniqueName” across concurrent callers.
Pattern:
UniqueName.UniqueName via your mapping DB (preferred) or list/filter (fallback).uniqueName with tenant: t_acme_case_10492tenantId in attributes.You may receive:
Unify by correlating:
MessageSid in Conversations message attributes when you send via Messaging API.# 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 .
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"}
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:
dedupeEvent(EventSid) is false, return 200 immediately.High-level steps:
<Dial><Conference record="record-from-start">case-10492</Conference></Dial>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"}'
Pattern:
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"}'
Python batch job:
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)
| Task | Endpoint / Command | Key fields / flags |
|---|---|---|
| Create conversation | POST /v1/Conversations | FriendlyName, UniqueName, Attributes, State |
| List conversations | GET /v1/Conversations | PageSize, PageToken |
| Update conversation | POST /v1/Conversations/{CH} | State, Attributes, FriendlyName |
| Delete conversation | DELETE /v1/Conversations/{CH} | n/a |
| Add chat participant | POST /v1/Conversations/{CH}/Participants | Identity, Attributes |
| Add SMS participant | same | MessagingBinding.Address, MessagingBinding.ProxyAddress |
| Add WhatsApp participant | same | MessagingBinding.Address=whatsapp:+E164, ProxyAddress=whatsapp:+E164 |
| List participants | GET /v1/Conversations/{CH}/Participants | PageSize, PageToken |
| Remove participant | DELETE /v1/Conversations/{CH}/Participants/{MB} | n/a |
| Send message | POST /v1/Conversations/{CH}/Messages | Author, Body, Attributes |
| List messages | GET /v1/Conversations/{CH}/Messages | PageSize, Order, PageToken |
| Webhook validation | X-Twilio-Signature | Validate against exact URL + params |
| Retry handling | n/a | Dedupe by EventSid, backoff on 20429 |
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)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)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)tools
Root web development: project structure, tooling selection, deployment decisions
development
WebAssembly: Rust/Go/C to WASM, wasm-bindgen, Emscripten, WASM Component Model
development
Vue 3: Composition API script setup, Pinia, Vue Router 4, SFCs, Vite, Nuxt 3
tools
Tailwind CSS 4: utility classes, config, JIT, arbitrary values, darkMode, plugins, shadcn/ui