Twilio API Integration (SMS, Voice, WhatsApp, Verify)
Integrates the Twilio Communications API — SMS, Voice, WhatsApp, Verify (2FA), Conversations, and Video — using the twilio Python SDK v9.x. When loaded, this skill makes the model implement Twilio operations with proper client initialization, TwiML generation, webhook signature validation, error handling, and async patterns.
TL;DR for Code Generation
When to Use
Use this skill when:
- Sending SMS, WhatsApp messages, or programmatic Voice calls from Python applications
- Implementing phone number verification (2FA) via Twilio Verify API
- Building IVR (Interactive Voice Response) systems with TwiML
- Managing Conversations (group chat, multi-channel) via Twilio Conversations API
- Handling incoming SMS/Voice webhooks and validating Twilio signatures
- Integrating Programmable Video for real-time video conferencing
- Sending messages with messaging services for A2P 10DLC compliance
When NOT to Use
Avoid this skill for:
- Email delivery (use
coding-sendgrid-api or coding-mailgun-api instead)
- In-app chat or team messaging (use
coding-slack-api or coding-discord-api instead)
- Simple push notifications (use Firebase Cloud Messaging or platform-specific push APIs)
- Bulk marketing SMS (use Twilio SendGrid Marketing Campaigns for email, or consult Twilio's A2P 10DLC guidelines for SMS)
Core Workflow
Initialize the Client — Create a Client(account_sid, auth_token) using environment variables. Never hardcode credentials. Checkpoint: Call client.api.accounts(sid).fetch() to validate credentials on startup.
Construct the Message or Call — Choose the appropriate API: client.messages.create() for SMS/WhatsApp, client.calls.create() for voice, client.verify.services() for 2FA. Set all required parameters (to, from, body for SMS; to, from, url for calls). Checkpoint: Validate phone numbers with PhoneNumbers API before sending production traffic.
Handle the Response and Errors — Inspect the returned instance for sid, status, error_code, and error_message. Catch TwilioRestException and inspect the status and code properties. Checkpoint: Log message.sid for every sent message as an audit trail.
Generate TwiML for Voice/IVR — Use twilio.twiml.VoiceResponse or twilio.twiml.MessagingResponse to build XML responses for webhooks. Chain verbs like <Say>, <Gather>, <Dial>, <Record>. Checkpoint: Test TwiML output with the TwiML Bin simulator or twilio CLI before deploying.
Validate Incoming Webhooks — Use RequestValidator to check X-Twilio-Signature on every incoming webhook. Fail closed if validation fails. Checkpoint: Log a security alert on every validation failure — do not silently drop invalid requests.
Implementation Patterns
Pattern 1: Sending SMS with Status Callback
import os
from twilio.rest import Client
from twilio.base.exceptions import TwilioRestException
# ❌ BAD — hardcoded credentials, no error handling, no status callback
client = Client("ACxxx", "tokxxx")
message = client.messages.create(
to="+15551234567",
from_="+15559876543",
body="Your code is 123456"
)
print(f"Sent: {message.sid}")
# ✅ GOOD — env-based auth, error handling, status callback, structured logging
import logging
logger = logging.getLogger(__name__)
account_sid: str | None = os.environ.get("TWILIO_ACCOUNT_SID")
auth_token: str | None = os.environ.get("TWILIO_AUTH_TOKEN")
if not account_sid or not auth_token:
raise RuntimeError("TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN must be set")
client = Client(account_sid, auth_token)
def send_sms(
to: str,
body: str,
from_: str | None = None,
status_callback_url: str | None = None,
) -> str:
"""Send an SMS and return the message SID."""
kwargs: dict = {
"to": to,
"body": body,
}
if from_:
kwargs["from_"] = from_
if status_callback_url:
kwargs["status_callback"] = status_callback_url
try:
message = client.messages.create(**kwargs)
logger.info("SMS sent", extra={"sid": message.sid, "to": to, "status": message.status})
return message.sid
except TwilioRestException as exc:
logger.error(
"Twilio error sending SMS",
extra={"status": exc.status, "code": exc.code, "msg": str(exc)},
)
raise
Pattern 2: Twilio Verify (2FA) Integration
import os
from twilio.rest import Client
from twilio.base.exceptions import TwilioRestException
# ❌ BAD — no service check, no channel fallback, swallows exceptions
client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])
verification = client.verify.services("VAxxx").verifications.create(
to="+15551234567", channel="sms"
)
print(verification.status)
# ✅ GOOD — verifies service exists, supports channel fallback, typed responses
from typing import Literal
Channel = Literal["sms", "call", "email", "whatsapp"]
def send_verification_code(
service_sid: str,
to: str,
channel: Channel = "sms",
) -> dict:
"""Send a verification code via the specified channel."""
service = client.verify.services(service_sid)
try:
# Verify the service exists before attempting to send
service.fetch()
except TwilioRestException as exc:
if exc.status == 404:
raise ValueError(f"Verify service {service_sid} not found") from exc
raise
try:
verification = service.verifications.create(to=to, channel=channel)
return {"status": verification.status, "sid": verification.sid, "channel": channel}
except TwilioRestException as exc:
if exc.status == 429:
raise RuntimeError("Rate limited — wait before requesting another code") from exc
raise
def check_verification_code(
service_sid: str,
to: str,
code: str,
) -> bool:
"""Check a verification code. Returns True if approved."""
try:
check = client.verify.services(service_sid).verification_checks.create(
to=to, code=code
)
return check.status == "approved"
except TwilioRestException:
return False
Pattern 3: TwiML Voice Response with Input Gathering
from twilio.twiml.voice_response import VoiceResponse, Gather
from twilio.request_validator import RequestValidator
import os
# ❌ BAD — no input validation on webhook, no security check
response = VoiceResponse()
response.say("Press 1 for sales, 2 for support")
response.redirect("/menu")
# ✅ GOOD — secure webhook validation, input gathering with timeout and retry
def build_menu_twiml() -> str:
"""Build an IVR menu with DTMF gathering."""
response = VoiceResponse()
gather = Gather(
num_digits=1,
action="/handle-menu",
method="POST",
timeout=5,
speech_timeout="auto",
)
gather.say("Welcome. Press 1 for sales, 2 for support, or 3 to repeat this menu.")
response.append(gather)
# If the user doesn't press anything, repeat
response.say("We didn't receive any input. Goodbye.")
response.hangup()
return str(response)
def validate_webhook(request) -> bool:
"""Validate an incoming Twilio webhook using the request validator."""
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])
url = request.url
params = request.form.to_dict()
signature = request.headers.get("X-Twilio-Signature", "")
return validator.validate(url, params, signature)
Constraints
MUST DO
- Store
TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN in environment variables or a secret manager — never in source code
- Validate every incoming webhook with
RequestValidator — Twilios webhook signature is your only CSRF defense
- Use
status_callback URL on messages to receive delivery receipts asynchronously
- Set up a Messaging Service for A2P 10DLC compliance when sending to US phone numbers
- Log the
message.sid or call.sid for every outbound communication for auditability
- Handle
TwilioRestException with status-specific logic (401 = bad auth, 404 = invalid number, 429 = rate limit, 5xx = server error)
MUST NOT DO
- Disable webhook signature validation in production — this enables SMS spoofing
- Rely on synchronous
message.status after create() — always use status_callback for delivery confirmation
- Hardcode phone numbers or sender IDs — load them from configuration
- Use the same API key for development and production accounts — use separate subaccounts
- Ignore
error_code on a message response — non-null error_code means delivery failure
Output Template
When implementing Twilio API code, the output must follow this structure:
- Client Initialization —
Client instantiated from environment, validated with a fetch() call
- API Parameters — All parameters typed and documented; phone numbers in E.164 format (
+1XXXXXXXXXX)
- Error Handling — Catches
TwilioRestException with status/code inspection; logs every error with context
- TwiML Response — Uses
twilio.twiml builders (not raw XML strings); validates output structure
- Webhook Security —
RequestValidator check on every incoming webhook before any business logic
Related Skills
| Skill |
Purpose |
coding-sendgrid-api |
Email delivery via SendGrid — use for transactional email alongside Twilio SMS |
coding-mailgun-api |
Email delivery via Mailgun — alternative to SendGrid for email |
coding-slack-api |
Team messaging via Slack — use for internal notifications alongside Twilio |
Live References
1---2name: twilio-api3description: Integrates Twilio API (SMS, Voice, WhatsApp, Verify, Conversations, Video) using the twilio-python SDK v9.x with proper client initialization, TwiML generation, and webhook validation.4license: MIT5---678910# Twilio API Integration (SMS, Voice, WhatsApp, Verify)1112Integrates the Twilio Communications API — SMS, Voice, WhatsApp, Verify (2FA), Conversations, and Video — using the `twilio` Python SDK v9.x. When loaded, this skill makes the model implement Twilio operations with proper client initialization, TwiML generation, webhook signature validation, error handling, and async patterns.1314## TL;DR for Code Generation1516- [ ] Initialize `Client()` from environment with `TWILIO_ACCOUNT_SID` and `TWILIO_AUTH_TOKEN` — never hardcode credentials17- [ ] Use `twilio.rest.Client` for REST API calls and `twilio.twiml` for TwiML response generation18- [ ] Validate incoming webhooks with `RequestValidator` to prevent request forgery19- [ ] Wrap API calls in try/except catching `TwilioRestException` with status codes20- [ ] Use message `status_callback` for delivery confirmation instead of polling21- [ ] Implement exponential backoff for transient 429 rate-limit responses22- [ ] For WhatsApp, use `messaging_service_sid` with a pre-configured Messaging Service for content templates2324---2526## When to Use2728Use this skill when:2930- Sending SMS, WhatsApp messages, or programmatic Voice calls from Python applications31- Implementing phone number verification (2FA) via Twilio Verify API32- Building IVR (Interactive Voice Response) systems with TwiML33- Managing Conversations (group chat, multi-channel) via Twilio Conversations API34- Handling incoming SMS/Voice webhooks and validating Twilio signatures35- Integrating Programmable Video for real-time video conferencing36- Sending messages with messaging services for A2P 10DLC compliance3738---3940## When NOT to Use4142Avoid this skill for:4344- Email delivery (use `coding-sendgrid-api` or `coding-mailgun-api` instead)45- In-app chat or team messaging (use `coding-slack-api` or `coding-discord-api` instead)46- Simple push notifications (use Firebase Cloud Messaging or platform-specific push APIs)47- Bulk marketing SMS (use Twilio SendGrid Marketing Campaigns for email, or consult Twilio's A2P 10DLC guidelines for SMS)4849---5051## Core Workflow52531. **Initialize the Client** — Create a `Client(account_sid, auth_token)` using environment variables. Never hardcode credentials. **Checkpoint:** Call `client.api.accounts(sid).fetch()` to validate credentials on startup.54552. **Construct the Message or Call** — Choose the appropriate API: `client.messages.create()` for SMS/WhatsApp, `client.calls.create()` for voice, `client.verify.services()` for 2FA. Set all required parameters (to, from, body for SMS; to, from, url for calls). **Checkpoint:** Validate phone numbers with `PhoneNumbers` API before sending production traffic.56573. **Handle the Response and Errors** — Inspect the returned instance for `sid`, `status`, `error_code`, and `error_message`. Catch `TwilioRestException` and inspect the `status` and `code` properties. **Checkpoint:** Log `message.sid` for every sent message as an audit trail.58594. **Generate TwiML for Voice/IVR** — Use `twilio.twiml.VoiceResponse` or `twilio.twiml.MessagingResponse` to build XML responses for webhooks. Chain verbs like `<Say>`, `<Gather>`, `<Dial>`, `<Record>`. **Checkpoint:** Test TwiML output with the TwiML Bin simulator or `twilio CLI` before deploying.60615. **Validate Incoming Webhooks** — Use `RequestValidator` to check `X-Twilio-Signature` on every incoming webhook. Fail closed if validation fails. **Checkpoint:** Log a security alert on every validation failure — do not silently drop invalid requests.6263---6465## Implementation Patterns6667### Pattern 1: Sending SMS with Status Callback6869```python70import os71from twilio.rest import Client72from twilio.base.exceptions import TwilioRestException7374# ❌ BAD — hardcoded credentials, no error handling, no status callback75client = Client("ACxxx", "tokxxx")76message = client.messages.create(77 to="+15551234567",78 from_="+15559876543",79 body="Your code is 123456"80)81print(f"Sent: {message.sid}")8283# ✅ GOOD — env-based auth, error handling, status callback, structured logging84import logging8586logger = logging.getLogger(__name__)8788account_sid: str | None = os.environ.get("TWILIO_ACCOUNT_SID")89auth_token: str | None = os.environ.get("TWILIO_AUTH_TOKEN")9091if not account_sid or not auth_token:92 raise RuntimeError("TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN must be set")9394client = Client(account_sid, auth_token)959697def send_sms(98 to: str,99 body: str,100 from_: str | None = None,101 status_callback_url: str | None = None,102) -> str:103 """Send an SMS and return the message SID."""104 kwargs: dict = {105 "to": to,106 "body": body,107 }108 if from_:109 kwargs["from_"] = from_110 if status_callback_url:111 kwargs["status_callback"] = status_callback_url112113 try:114 message = client.messages.create(**kwargs)115 logger.info("SMS sent", extra={"sid": message.sid, "to": to, "status": message.status})116 return message.sid117 except TwilioRestException as exc:118 logger.error(119 "Twilio error sending SMS",120 extra={"status": exc.status, "code": exc.code, "msg": str(exc)},121 )122 raise123```124125### Pattern 2: Twilio Verify (2FA) Integration126127```python128import os129from twilio.rest import Client130from twilio.base.exceptions import TwilioRestException131132# ❌ BAD — no service check, no channel fallback, swallows exceptions133client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])134verification = client.verify.services("VAxxx").verifications.create(135 to="+15551234567", channel="sms"136)137print(verification.status)138139# ✅ GOOD — verifies service exists, supports channel fallback, typed responses140from typing import Literal141142Channel = Literal["sms", "call", "email", "whatsapp"]143144145def send_verification_code(146 service_sid: str,147 to: str,148 channel: Channel = "sms",149) -> dict:150 """Send a verification code via the specified channel."""151 service = client.verify.services(service_sid)152 try:153 # Verify the service exists before attempting to send154 service.fetch()155 except TwilioRestException as exc:156 if exc.status == 404:157 raise ValueError(f"Verify service {service_sid} not found") from exc158 raise159160 try:161 verification = service.verifications.create(to=to, channel=channel)162 return {"status": verification.status, "sid": verification.sid, "channel": channel}163 except TwilioRestException as exc:164 if exc.status == 429:165 raise RuntimeError("Rate limited — wait before requesting another code") from exc166 raise167168169def check_verification_code(170 service_sid: str,171 to: str,172 code: str,173) -> bool:174 """Check a verification code. Returns True if approved."""175 try:176 check = client.verify.services(service_sid).verification_checks.create(177 to=to, code=code178 )179 return check.status == "approved"180 except TwilioRestException:181 return False182```183184### Pattern 3: TwiML Voice Response with Input Gathering185186```python187from twilio.twiml.voice_response import VoiceResponse, Gather188from twilio.request_validator import RequestValidator189import os190191# ❌ BAD — no input validation on webhook, no security check192response = VoiceResponse()193response.say("Press 1 for sales, 2 for support")194response.redirect("/menu")195196# ✅ GOOD — secure webhook validation, input gathering with timeout and retry197def build_menu_twiml() -> str:198 """Build an IVR menu with DTMF gathering."""199 response = VoiceResponse()200201 gather = Gather(202 num_digits=1,203 action="/handle-menu",204 method="POST",205 timeout=5,206 speech_timeout="auto",207 )208 gather.say("Welcome. Press 1 for sales, 2 for support, or 3 to repeat this menu.")209 response.append(gather)210211 # If the user doesn't press anything, repeat212 response.say("We didn't receive any input. Goodbye.")213 response.hangup()214 return str(response)215216217def validate_webhook(request) -> bool:218 """Validate an incoming Twilio webhook using the request validator."""219 validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])220221 url = request.url222 params = request.form.to_dict()223 signature = request.headers.get("X-Twilio-Signature", "")224225 return validator.validate(url, params, signature)226```227228---229230## Constraints231232### MUST DO233- Store `TWILIO_ACCOUNT_SID` and `TWILIO_AUTH_TOKEN` in environment variables or a secret manager — never in source code234- Validate every incoming webhook with `RequestValidator` — Twilios webhook signature is your only CSRF defense235- Use `status_callback` URL on messages to receive delivery receipts asynchronously236- Set up a Messaging Service for A2P 10DLC compliance when sending to US phone numbers237- Log the `message.sid` or `call.sid` for every outbound communication for auditability238- Handle `TwilioRestException` with status-specific logic (401 = bad auth, 404 = invalid number, 429 = rate limit, 5xx = server error)239240### MUST NOT DO241- Disable webhook signature validation in production — this enables SMS spoofing242- Rely on synchronous `message.status` after `create()` — always use `status_callback` for delivery confirmation243- Hardcode phone numbers or sender IDs — load them from configuration244- Use the same API key for development and production accounts — use separate subaccounts245- Ignore `error_code` on a message response — non-null `error_code` means delivery failure246247---248249## Output Template250251When implementing Twilio API code, the output must follow this structure:2522531. **Client Initialization** — `Client` instantiated from environment, validated with a `fetch()` call2542. **API Parameters** — All parameters typed and documented; phone numbers in E.164 format (`+1XXXXXXXXXX`)2553. **Error Handling** — Catches `TwilioRestException` with status/code inspection; logs every error with context2564. **TwiML Response** — Uses `twilio.twiml` builders (not raw XML strings); validates output structure2575. **Webhook Security** — `RequestValidator` check on every incoming webhook before any business logic258259---260261## Related Skills262263| Skill | Purpose |264|---|---|265| `coding-sendgrid-api` | Email delivery via SendGrid — use for transactional email alongside Twilio SMS |266| `coding-mailgun-api` | Email delivery via Mailgun — alternative to SendGrid for email |267| `coding-slack-api` | Team messaging via Slack — use for internal notifications alongside Twilio |268269---270271## Live References272273- [Twilio Python SDK Documentation (v9.x)](https://www.twilio.com/docs/libraries/reference/twilio-python/)274- [Twilio SMS API Reference](https://www.twilio.com/docs/sms/api)275- [Twilio Verify API Reference](https://www.twilio.com/docs/verify/api)276- [TwiML Voice Response Reference](https://www.twilio.com/docs/voice/twiml)277- [Twilio Webhook Security (Signature Validation)](https://www.twilio.com/docs/usage/webhooks/webhook-security)278- [Twilio Conversations API](https://www.twilio.com/docs/conversations/api)279- [Twilio WhatsApp API Guide](https://www.twilio.com/docs/whatsapp/api)280- [PyPI: twilio package](https://pypi.org/project/twilio/)281- [GitHub: twilio/twilio-python](https://github.com/twilio/twilio-python)