Skip to content
SMS SDK
Esc
↑↓navigate↵open⌘Jpreview
On this page

Inbound SMS

Receive replies to your numbers as verified message.received events, with sender, recipient, text, and media URLs.

Point the provider’s inbound webhook at your server and parse each request with parseSmsWebhook(). Every text sent to your number becomes a message.received event.

1. Configure the inbound URL

Inbound messages go to the URL set on the receiving number or its group. A per-message webhookUrl only affects status events.

Provider Where to set it
Twilio The phone number’s “A message comes in” webhook, or the Messaging Service’s incoming message setting.
Telnyx The messaging profile’s inbound webhook URL.
Plivo The message URL of the Plivo application linked to the number.
Vonage The Inbound URL of the Vonage application linked to the number. Send from a linked number with JWT auth. Vonage documents that Basic auth fails with 401 for linked numbers.

2. Parse the message

import { parseSmsWebhook, WebhookSignatureError } from "@opencoredev/sms-sdk/webhooks";
import { db } from "./db";

export async function POST(request: Request): Promise<Response> {
  try {
    const event = await parseSmsWebhook({
      provider: "plivo",
      request,
      publicUrl: "https://example.com/webhooks/sms/plivo",
      credentials: { authToken: process.env.PLIVO_AUTH_TOKEN ?? "" },
    });

    if (event.type === "message.received") {
      await db.inbox.save({ from: event.from, to: event.to, body: event.body, providerId: event.providerId });
      console.log(`Reply from ${event.from} with ${event.mediaUrls.length} attachment(s)`);
    }
    return new Response(null, { status: 200 });
  } catch (error) {
    if (error instanceof WebhookSignatureError) {
      return new Response("Invalid signature", { status: 401 });
    }
    throw error;
  }
}

Plivo signs the URL it called, so the example passes publicUrl. See Webhook security.

A message.received event has:

Field Value
providerId The provider’s ID for the inbound message.
from The sender’s number. Vonage numbers, which arrive without +, are normalized to E.164.
to Your number that received it.
body The text, as sent.
mediaUrls Attached media from Twilio MediaUrl0…, Telnyx media[].url, or Plivo Media0…. Always empty for Vonage.
dedupeKey, raw, occurredAt See Webhook events.

Keywords become opt-out events

When the whole message is a keyword such as STOP, UNSUBSCRIBE, or HELP, you get recipient.opted_out or recipient.help instead of message.received. Handle those too, or turn detection off with detectKeywords: false. See STOP, HELP, and opt-outs.

Reply to a message

SMS SDK never replies automatically. To answer, send a new message from the number that was texted:

import { isE164 } from "@opencoredev/sms-sdk";
import { parseSmsWebhook } from "@opencoredev/sms-sdk/webhooks";
import { sms } from "./sms";

export async function POST(request: Request): Promise<Response> {
  const event = await parseSmsWebhook({
    provider: "telnyx",
    request,
    credentials: { publicKey: process.env.TELNYX_PUBLIC_KEY ?? "" },
  });

  if (event.type === "message.received" && isE164(event.from) && isE164(event.to)) {
    await sms.send({
      to: event.from,
      from: event.to,
      body: "Thanks, we got your message.",
      idempotencyKey: `reply:${event.dedupeKey}`,
    });
  }
  return new Response(null, { status: 200 });
}

Deriving the idempotency key from the inbound dedupeKey means a repeated webhook cannot send a second reply, as long as the client has an idempotency store. Catch WebhookSignatureError here as in the first example.

Twilio also lets you reply by returning TwiML from the webhook. SMS SDK does not build TwiML. If you do not want Twilio to reply, return an empty <Response></Response> document with Content-Type: text/xml.

Limitations

  • SMS SDK is not a conversation store. It does not thread messages, keep history, or manage an inbox.
  • Inbound MMS media URLs point at provider storage. They may expire, and fetching them may need provider credentials.
  • Vonage inbound requires a number linked to a Vonage application. See Vonage.

Next steps