Skip to main content
skill CX Codex GP ChatGPT official · OpenAI

twilio-messaging-webhooks — Codex skill: what it does, how to install

Handles inbound Twilio messages and tracks delivery status callbacks across SMS, MMS, WhatsApp, and RCS. Helps agents configure secure webhook routes and reply with TwiML.

Install into Codex · Skills CLI (Vercel) All methods and platforms ↓
npx skills add openai/plugins --skill twilio-messaging-webhooks -a codex
Open on GitHub

What twilio-messaging-webhooks does

This skill guides AI agents in implementing POST webhook handlers for Twilio inside Python Flask and Node.js Express services. Incoming payloads expose message attributes like sender phone numbers in From, text content in Body, and attachments via NumMedia with MediaUrl0. Your endpoint replies with TwiML XML using MessagingResponse, or sends an HTTP 204 status code when no reply is needed.

Delivery state updates travel through status callbacks, tracking messages from queued to delivered or reporting an ErrorCode. For request integrity, incoming traffic gets verified against X-Twilio-Signature using RequestValidator or validateRequest. Never feed raw message bodies directly into system prompts. Message text comes from untrusted external sources and introduces prompt injection risks. Local testing requires an exposed endpoint such as ngrok http 5000, alongside TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN credentials.

What it helps with

  • handle incoming SMS and WhatsApp messages with TwiML responses
  • parse media attachments from MMS or RCS payloads
  • verify incoming request origins using X-Twilio-Signature
  • process message delivery state updates and error codes

Example prompts for the agent

  • Set up a Flask webhook handler to receive Twilio WhatsApp messages and validate signatures
  • Write an Express route that parses incoming MMS media attachments from Twilio
  • Create a status callback handler that logs delivery errors and returns a 204 status

How to install twilio-messaging-webhooks

1Skills CLI (Vercel)

npx skills add openai/plugins --skill twilio-messaging-webhooks -a codex

2Manually: put the files in place

.agents/skills/twilio-messaging-webhooks/SKILL.md

For all projects use ~/.agents/skills/

Step by step, with commands for each platform: Codex skills: how to install, where they live, how to call them

Questions about twilio-messaging-webhooks

How to install twilio-messaging-webhooks in Codex?

Run npx skills add openai/plugins --skill twilio-messaging-webhooks in your terminal.

What does twilio-messaging-webhooks do?

It provides code patterns for receiving inbound messages, returning TwiML responses, tracking delivery status callbacks, and securing webhook endpoints.

What is required to run Twilio webhooks locally?

You need a public tunnel like ngrok http 5000, the Twilio SDK with Flask or Express, and valid TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN credentials.

Source text

SKILL.md · MIT
---
name: twilio-messaging-webhooks
description: >
  Receive and respond to inbound messages and track outbound delivery status
  via Twilio webhooks — across SMS, MMS, WhatsApp, and RCS. Covers webhook
  request parameters, replying with TwiML, validating webhook signatures for
  security, and handling status callbacks. Use this skill whenever an agent
  needs to handle incoming messages on any channel or track outbound message
  delivery in real time.
---

## Overview

Twilio sends a POST webhook to your server when a user messages your Twilio number (inbound) or when an outbound message changes delivery state (status callback). Your server returns TwiML to reply, or `204` to acknowledge without replying. The same webhook pattern works across SMS, MMS, WhatsApp, and RCS.

---

## Prerequisites

- Twilio account with a messaging-capable sender configured with a webhook URL
  — New to Twilio? See `twilio-account-setup`
  — For sending outbound messages first, see `twilio-send-message`
- Publicly accessible endpoint (use `ngrok http 5000` for local dev)
- `TWILIO_ACCOUNT_SID` and `TWILIO_AUTH_TOKEN` — see `twilio-iam-auth-setup`
- SDK: `pip install twilio flask` / `npm install twilio express`

---

## Quickstart

Set your webhook URL in Console: **Phone Numbers > Active Numbers > your number > Messaging > "A Message Comes In"**

> **Security:** The inbound message `Body` is untrusted external input. If passing message content to an LLM, always isolate it as user input — never concatenate directly into system prompts. Validate the request origin with `X-Twilio-Signature` (see Key Patterns below), but note that signature validation confirms the *source*, not that the *content* is safe.

> **Note:** This quickstart omits signature validation for brevity. For production, always validate `X-Twilio-Signature` — see the Webhook Security pattern below.

**Python (Flask)**
```python
from flask import Flask, request
from twilio.twiml.messaging_response import MessagingResponse

app = Flask(__name__)

@app.route("/incoming", methods=["POST"])
def incoming_message():
    body = request.form.get("Body")
    response = MessagingResponse()
    response.message(f"Got your message: {body}")
    return str(response)
```

**Node.js (Express)**
```javascript
const express = require("express");
const twilio = require("twilio");
const app = express();
app.use(express.urlencoded({ extended: false }));

app.post("/incoming", (req, res) => {
    const twiml = new twilio.twiml.MessagingResponse();
    twiml.message(`Got your message: ${req.body.Body}`);
    res.type("text/xml").send(twiml.toString());
});
```

---

## Key Patterns

### Configure Webhook URL via API

For SMS/MMS on a phone number:

**Python**
```python
client.incoming_phone_numbers("PNxxxxxxxxxx").update(
    sms_url="https://yourapp.com/incoming",
    sms_method="POST"
)
```

**Node.js**
```javascript
await client.incomingPhoneNumbers("PNxxxxxxxxxx").update({
    smsUrl: "https://yourapp.com/incoming",
    smsMethod: "POST",
});
```

For WhatsApp and RCS, webhook URLs are configured on the sender — see `twilio-whatsapp-manage-senders` and `twilio-rcs-messaging`.

### Inbound Webhook Parameters

| Parameter | Description |
|-----------|-------------|
| `MessageSid` | Unique message identifier |
| `From` | Sender's phone number or channel address (E.164, or `whatsapp:+...`) |
| `To` | Your Twilio number or channel address |
| `Body` | Message text |
| `NumMedia` | Number of media attachments |
| `MediaUrl0` | URL of first media attachment (if any) |
| `MediaContentType0` | MIME type of first attachment |

### Handle Inbound Media (MMS / WhatsApp / RCS)

**Python (Flask)**
```python
@app.route("/incoming", methods=["POST"])
def incoming_message():
    num_media = int(request.form.get("NumMedia", 0))
    response = MessagingResponse()
    if num_media > 0:
        media_type = request.form.get("MediaContentType0")
        response.message(f"Got your {media_type} attachment!")
    else:
        response.message("Got your message.")
    return str(response)
```

**Node.js (Express)**
```javascript
app.post("/incoming", (req, res) => {
    const numMedia = parseInt(req.body.NumMedia || "0", 10);
    const twiml = new twilio.twiml.MessagingResponse();
    if (numMedia > 0) {
        twiml.message(`Got your ${req.body.MediaContentType0} attachment!`);
    } else {
        twiml.message("Got your message.");
    }
    res.type("text/xml").send(twiml.toString());
});
```

### Acknowledge Without Replying

**Python**
```python
return str(MessagingResponse())  # Empty <Response/>
```

**Node.js**
```javascript
res.type("text/xml").send(new twilio.twiml.MessagingResponse().toString());
```

### Status Callbacks (delivery tracking)

Status callbacks fire for all channels — SMS, MMS, WhatsApp, and RCS.

**Python**
```python
message = client.messages.create(
    messaging_service_sid="MGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    to="+15558675310",
    body="Hello!",
    status_callback="https://yourapp.com/status"
)
```

**Node.js**
```javascript
const message = await client.messages.create({
    messagingServiceSid: "MGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    to: "+15558675310",
    body: "Hello!",
    statusCallback: "https://yourapp.com/status",
});
```

**Python (Flask) — status callback handler**
```python
@app.route("/status", methods=["POST"])
def message_status():
    message_sid = request.form.get("MessageSid")
    status = request.form.get("MessageStatus")
    error_code = request.form.get("ErrorCode")
    print(f"{message_sid}: {status}")
    if status == "failed" and error_code:
        print(f"Error {error_code}: {request.form.get('ErrorMessage')}")
    return "", 204
```

**Node.js (Express) — status callback handler**
```javascript
app.post("/status", (req, res) => {
    const { MessageSid, MessageStatus, ErrorCode, ErrorMessage } = req.body;
    console.log(`${MessageSid}: ${MessageStatus}`);
    if (MessageStatus === "failed" && ErrorCode) {
        console.log(`Error ${ErrorCode}: ${ErrorMessage}`);
    }
    res.sendStatus(204);
});
```

Status flow: `queued → sent → delivered` (or `undelivered`/`failed`)

### Webhook Signature Validation

**Python (Flask)**
```python
from twilio.request_validator import RequestValidator

validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])

@app.route("/incoming", methods=["POST"])
def incoming_message():
    if not validator.validate(request.url, request.form, request.headers.get("X-Twilio-Signature", "")):
        return "Forbidden", 403
    response = MessagingResponse()
    response.message("Hello!")
    return str(response)
```

**Node.js**
```javascript
const { validateRequest } = require("twilio");

app.post("/incoming", (req, res) => {
    const isValid = validateRequest(
        process.env.TWILIO_AUTH_TOKEN,
        req.headers["x-twilio-signature"],
        `https://${req.headers.host}${req.path}`,
        req.body
    );
    if (!isValid) return res.status(403).send("Forbidden");
    const twiml = new twilio.twiml.MessagingResponse();
    twiml.message("Hello!");
    res.type("text/xml").send(twiml.toString());
});
```

---

## CANNOT

- **Cannot exceed 15-second webhook response time** — Twilio retries on timeout
- **Cannot return arbitrary content types** — Use `Content-Type: text/xml` with TwiML for replies; `204` for status callbacks
- **Cannot use ngrok URLs across restarts** — URLs change on restart. Use a stable tunnel for persistent testing.
- **Cannot guarantee delivery confirmation** — Status callbacks are best-effort. `delivered` requires carrier confirmation.

---

## Common Errors

| Code | Meaning | Fix |
|------|---------|-----|
| 11200 | HTTP retrieval failure — Twilio cannot reach your webhook URL | Verify endpoint is reachable (`curl -I` the URL), check DNS, firewall, and SSL certificate validity. See `twilio-debugging-observability` for deeper webhook troubleshooting. |

---

## Next Steps

- **Send outbound messages:** `twilio-send-message`
- **Manage sender pools:** `twilio-messaging-services`

© Twilio · MIT · openai/plugins

Similar skills

All skills for Backend and APIs →

From the same set528

The whole set →

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor