Skip to main content
Receive inbound SMS and MMS messages via webhooks. When someone texts your Telnyx number, Telnyx sends an HTTP POST request with the message details to your configured webhook URL.

Prerequisites

  • A Telnyx phone number assigned to a messaging profile
  • A webhook URL configured on your messaging profile
  • ngrok or similar tool for local development
Already have a webhook server? Skip to Configure your webhook URL to point it at your messaging profile.

Quick Start

1

Create a webhook server

Build a web server that accepts POST requests from Telnyx:
import express from 'express';

const app = express();
app.use(express.json());

app.post('/webhooks', (req, res) => {
  const { data } = req.body;

  if (data.event_type === 'message.received') {
    const { payload } = data;
    console.log(`From: ${payload.from.phone_number}`);
    console.log(`Text: ${payload.text}`);
    console.log(`Type: ${payload.type}`); // SMS or MMS

    if (payload.media?.length > 0) {
      console.log(`Media attachments: ${payload.media.length}`);
      payload.media.forEach(m => console.log(`  ${m.content_type}: ${m.url}`));
    }
  }

  res.sendStatus(200);
});

app.listen(5000, () => console.log('Webhook server running on port 5000'));
2

Expose your local server

Use ngrok to create a public URL that forwards to your local server:
ngrok http 5000
Copy the forwarding URL (e.g., https://abc123.ngrok.io).
3

Configure your webhook URL

Set the webhook URL on your messaging profile via the Portal or API:
curl -X PATCH "https://api.telnyx.com/v2/messaging_profiles/YOUR_PROFILE_ID" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "webhook_url": "https://abc123.ngrok.io/webhooks"
  }'
Find your messaging profile ID in the Portal or via the List Messaging Profiles endpoint.
4

Test it

Send a text message from your phone to your Telnyx number. You should see the message details logged in your server console.You can also test locally without a phone:
curl -X POST http://localhost:5000/webhooks \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "event_type": "message.received",
      "payload": {
        "from": {"phone_number": "+15551234567"},
        "to": [{"phone_number": "+15559876543"}],
        "text": "Hello from test!",
        "type": "SMS",
        "media": []
      }
    }
  }'
Your webhook must return a 2xx response within 2 seconds (API v2) or 5 seconds (API v1). If delivery fails, Telnyx retries up to 2 times per URL. With a failover URL configured, that’s up to 4 total attempts.

Auto-Reply to Incoming Messages

A common pattern is sending an automatic reply when a message arrives. Combine your webhook handler with the Send Message API:
import express from 'express';
import Telnyx from 'telnyx';

const app = express();
app.use(express.json());

const client = new Telnyx({ apiKey: process.env.TELNYX_API_KEY });

app.post('/webhooks', async (req, res) => {
  const { data } = req.body;

  if (data.event_type === 'message.received') {
    const { payload } = data;
    const from = payload.from.phone_number;
    const to = payload.to[0].phone_number;

    // Send auto-reply
    await client.messages.send({
      from: to,   // Reply from the number that received the message
      to: from,   // Reply to the sender
      text: `Thanks for your message! We received: "${payload.text}"`
    });

    console.log(`Auto-reply sent to ${from}`);
  }

  res.sendStatus(200);
});

app.listen(5000);
Avoid reply loops. If both sides auto-reply, they’ll ping each other forever. Guard against this by checking the sender isn’t one of your own numbers, or by tracking recently replied conversations.

Webhook Payload Reference

Inbound SMS

{
  "data": {
    "event_type": "message.received",
    "id": "b301ed3f-1490-491f-995f-6e64e69674d4",
    "occurred_at": "2024-01-15T20:16:07.588+00:00",
    "payload": {
      "direction": "inbound",
      "encoding": "GSM-7",
      "from": {
        "carrier": "T-Mobile USA",
        "line_type": "long_code",
        "phone_number": "+13125550001",
        "status": "webhook_delivered"
      },
      "id": "84cca175-9755-4859-b67f-4730d7f58aa3",
      "media": [],
      "messaging_profile_id": "740572b6-099c-44a1-89b9-6c92163bc68d",
      "parts": 1,
      "received_at": "2024-01-15T20:16:07.503+00:00",
      "record_type": "message",
      "text": "Hello from Telnyx!",
      "to": [
        {
          "carrier": "Telnyx",
          "line_type": "Wireless",
          "phone_number": "+17735550002",
          "status": "webhook_delivered"
        }
      ],
      "type": "SMS"
    },
    "record_type": "event"
  },
  "meta": {
    "attempt": 1,
    "delivered_to": "https://example.com/webhooks"
  }
}

Inbound MMS

MMS messages include a media array with downloadable attachments:
{
  "data": {
    "event_type": "message.received",
    "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "occurred_at": "2024-01-15T20:18:30.000+00:00",
    "payload": {
      "direction": "inbound",
      "from": {
        "carrier": "T-Mobile USA",
        "line_type": "long_code",
        "phone_number": "+13125550001"
      },
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "media": [
        {
          "url": "https://media.telnyx.com/example-image.png",
          "content_type": "image/png",
          "sha256": "ab1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
          "size": 102400
        }
      ],
      "messaging_profile_id": "740572b6-099c-44a1-89b9-6c92163bc68d",
      "parts": 1,
      "received_at": "2024-01-15T20:18:30.000+00:00",
      "record_type": "message",
      "text": "Check out this photo!",
      "to": [
        {
          "carrier": "Telnyx",
          "line_type": "Wireless",
          "phone_number": "+17735550002"
        }
      ],
      "type": "MMS"
    },
    "record_type": "event"
  }
}
MMS media URLs expire after 30 days. Download and store media files if you need long-term access.

Key Payload Fields

FieldDescription
event_typemessage.received for inbound messages
payload.from.phone_numberSender’s phone number (E.164)
payload.to[].phone_numberYour Telnyx number that received the message
payload.textMessage body (may be empty for media-only MMS)
payload.typeSMS or MMS
payload.mediaArray of media attachments (MMS only)
payload.encodingGSM-7 or UCS-2 (details)
payload.partsNumber of message segments
payload.messaging_profile_idMessaging profile that received the message
meta.attemptWebhook delivery attempt number (1–3)

Downloading MMS Media

Download media attachments from inbound MMS messages using the URLs in the media array. Authenticate with your API key:
import fs from 'fs';

async function downloadMedia(mediaUrl, apiKey) {
  const response = await fetch(mediaUrl, {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });

  const buffer = Buffer.from(await response.arrayBuffer());
  const filename = mediaUrl.split('/').pop();
  fs.writeFileSync(filename, buffer);
  console.log(`Downloaded: ${filename} (${buffer.length} bytes)`);
}

// In your webhook handler:
for (const media of payload.media) {
  await downloadMedia(media.url, process.env.TELNYX_API_KEY);
}

Verifying Webhook Signatures

In production, verify webhook signatures to confirm requests are from Telnyx. All webhooks are signed using ED25519 public key cryptography. Each webhook includes two headers:
  • telnyx-signature-ed25519 — Base64-encoded signature
  • telnyx-timestamp — Unix timestamp when the webhook was sent
import Telnyx from 'telnyx';

const telnyx = new Telnyx('YOUR_API_KEY');

app.post('/webhooks', (req, res) => {
  const signature = req.headers['telnyx-signature-ed25519'];
  const timestamp = req.headers['telnyx-timestamp'];
  const payload = JSON.stringify(req.body);

  try {
    const event = telnyx.webhooks.constructEvent(
      payload,
      signature,
      timestamp,
      process.env.TELNYX_PUBLIC_KEY
    );
    console.log('Verified event:', event.data.event_type);
    res.sendStatus(200);
  } catch (err) {
    console.error('Signature verification failed:', err.message);
    res.sendStatus(400);
  }
});
Get your public key from the Mission Control Portal under Account Settings > Keys & Credentials > Public Key.
For detailed webhook signature verification, see Webhook Fundamentals.

Troubleshooting

Check your webhook URL is configured:
curl -s "https://api.telnyx.com/v2/messaging_profiles/YOUR_PROFILE_ID" \
  -H "Authorization: Bearer YOUR_API_KEY" | jq '.data.webhook_url'
Common causes:
  • Webhook URL not set on the messaging profile
  • Phone number not assigned to the messaging profile
  • Server not publicly accessible (use ngrok for local dev)
  • Firewall blocking Telnyx IPs
Test your endpoint directly:
curl -X POST https://your-server.com/webhooks \
  -H "Content-Type: application/json" \
  -d '{"data":{"event_type":"message.received","payload":{"text":"test"}}}'
Duplicates happen when your server doesn’t respond with 2xx within the timeout (2 seconds for API v2). Telnyx retries up to 2 times.Fix: Return 200 immediately, then process the message asynchronously. Use the payload.id field to deduplicate.
// Deduplicate using a Set (use Redis in production)
const processed = new Set();

app.post('/webhooks', (req, res) => {
  res.sendStatus(200); // Respond immediately

  const messageId = req.body.data.payload?.id;
  if (messageId && processed.has(messageId)) return;
  processed.add(messageId);

  // Process message asynchronously
  handleMessage(req.body.data);
});
Your server must respond within 2 seconds (API v2) or 5 seconds (API v1).Solutions:
  • Return 200 immediately, process in background
  • Use a message queue (Redis, SQS, RabbitMQ) for heavy processing
  • Set up a failover URL as a backup
  • Check the type field is MMS (SMS messages have an empty media array)
  • Media URLs require authentication — include your API key in the Authorization header
  • Media URLs expire after 30 days
  • Verify your number is MMS-enabled in the Portal

Webhook URL Hierarchy

Telnyx checks for webhook URLs in this order:
  1. Request body — URLs provided when sending a message (outbound delivery receipts only)
  2. Messaging profile — URLs configured on the profile
  3. No URL — Webhook delivery is skipped
Configure a failover URL on your messaging profile for redundancy. If the primary URL fails all retry attempts, Telnyx sends to the failover URL.
Your webhook URL receives more than just message.received. For delivery status tracking and other events, see Receiving Webhooks.
EventDescription
message.receivedInbound SMS or MMS received (this guide)
message.sentOutbound message accepted by carrier
message.finalizedFinal delivery status (delivered, failed, etc.)

Next Steps