Skip to main content

SMS webhook

Get the SMS your phone receives
as a webhook, on your own number.

Inbound SMS APIs usually start with renting a new number. Install a free Android app instead: each text your SIM receives reaches your URL as signed JSON, and you answer from that same number.

In a hurry? Summarize this article with an AI

The short answer

Can you receive SMS on your own number by webhook?

Yes, with an Android phone. The free Unlimited Messaging app links your phone's SIM to your account, and every SMS it receives after sign-in is posted to your URL as a signed JSON event, message.received. People keep texting the number they know, and you pay no fee per incoming SMS. Unlike Twilio, there is no number to rent. Webhooks are on the paid Medium and Max plans.

The classic way to receive SMS by API is an A2P provider. With Twilio you lease a number ($1.15 a month for a US local number), point its webhook at your server, and pay $0.0083 per inbound segment plus a carrier fee, $0.0035 from AT&T or $0.0025 from T-Mobile. The number is new, so your customers have to learn it, and Twilio posts form-encoded parameters signed with HMAC-SHA1.

An Android gateway skips the rental. Unlimited Messaging for Android is free and open source (MIT): you install the APK, sign in, and each SIM becomes an SMS account (dual SIM, two accounts). The phone keeps a connection to the API, uploads each SMS it receives, and the API announces it to your webhook in the same JSON envelope it uses for WhatsApp and email. A retried upload is recognized and never announced twice.

The catch is physical: the phone must stay on, online and allowed to run in the background. It handles the texts of one business at a human pace, not the inbound traffic of a national short code.

Compare first: the four Android SMS gateway apps, and what a free SMS API really costs.

Illustration: a phone with text-message bubbles and an SMS tag, with the headline “Incoming SMS, straight to your code”.

The events

Which SMS events does the webhook send?

SMS uses the same event types as the other channels. An endpoint only receives the types in its eventTypes list.

  1. message.received

    A text arrived on one of your SIMs. data.channel.type is SMS, content holds the text, sender the number in +E.164. A sender the carrier only names (a bank, a delivery service) comes with sender null and that name in senderId.

  2. message.sent

    An SMS you sent through the API left the phone. Carries the text and the recipient, fired once, so your inbox can show both sides.

  3. message.status

    An SMS you sent moved to SENT, DELIVERED (when the carrier sends delivery reports), FAILED or UNDELIVERABLE, with error saying why. SMS has no read receipts, so never READ.

  4. account.status_changed

    A SIM account changed status, for instance disconnected when the app was signed out. Subscribe to it to know when the phone stops relaying.

POST https://example.com/webhooks/umJSON
{
  "type": "message.received",
  "eventId": "whd_2b9e7c41-0d3a-4f6e-8c52-91a7d4e0b6f3",
  "occurredAt": "2026-11-09T08:42:06.000Z",
  "data": {
    "message": {
      "id": "msg_7d2c9a10-5e4b-4b8f-a1c3-0f6e2d9b8a47",
      "externalId": "c1f4a9e27b3d4e6f9a0b",
      "direction": "IN",
      "fromMe": false,
      "content": "Running 10 min late for my 9:00, sorry!",
      "sentAt": "2026-11-09T08:42:03.000Z",
      "receivedAt": "2026-11-09T08:42:06.000Z",
      "sender": "+447700900456",
      "senderId": "+447700900456",
      "senderName": null,
      "participant": null,
      "replyTo": null,
      "transcript": null
    },
    "channel": {
      "type": "SMS",
      "account": { "id": "acc_5e8d1f2a-3c4b-4d6e-9f70-1a2b3c4d5e6f", "name": "Front desk SIM" },
      "conversation": { "id": null, "isGroup": false, "address": "+447700900456" }
    }
  }
}
A real message.received body for an SMS, built from the published schema. conversation.id is null for SMS: key the thread on sender. Only SMS received after the app was signed in are uploaded; texts already on the phone are never read.

Set it up

How do you receive incoming SMS in Node.js?

Install the app, register an endpoint, verify each POST. The same handler works for WhatsApp and email later.

  1. 01

    Install the Android app

    Download the APK from GitHub (Android 8.0 or newer), sign in, grant SMS access and unrestricted battery. Each SIM appears as an SMS account.

  2. 02

    Register the endpoint

    POST /webhooks with your URL and eventTypes, or add it on the Webhooks page of the dashboard. The secret (whsec_...) is shown once.

  3. 03

    Verify, acknowledge, act

    Check the webhook-signature header, return 2xx within 10 seconds, deduplicate on eventId, then reply or store.

POST /webhookscurl
curl https://api.unlimitedmessaging.app/webhooks \
  -H "Authorization: Bearer $UM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/um",
    "eventTypes": ["message.received", "message.status"],
    "description": "Incoming SMS"
  }'

# The response carries "secret": "whsec_..." once. Store it as UM_WEBHOOK_SECRET.
Add accountIds to limit the endpoint to one SIM; without it, every account you have is covered, including SIMs added later.
webhook handlerNode.js / Express
import crypto from 'node:crypto';
import express from 'express';
import { UnlimitedMessagingApiClient } from '@unlimited-messaging/sdk';

const um = new UnlimitedMessagingApiClient({ token: process.env.UM_API_KEY! });
const key = Buffer.from(process.env.UM_WEBHOOK_SECRET!.replace(/^whsec_/, ''), 'base64');
const seen = new Set<string>(); // use your database in production

// Standard Webhooks: HMAC-SHA256 of "id.timestamp.rawBody", base64.
function verify(raw: string, id: string, ts: string, header: string) {
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = crypto.createHmac('sha256', key).update(`${id}.${ts}.${raw}`).digest('base64');
  return header.split(' ').some((entry) => {
    const sig = entry.startsWith('v1,') ? entry.slice(3) : '';
    return sig.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  });
}

const app = express();
app.post('/webhooks/um', express.raw({ type: 'application/json' }), async (req, res) => {
  const raw = req.body.toString('utf8');
  const ok = verify(raw, req.get('webhook-id') ?? '', req.get('webhook-timestamp') ?? '',
    req.get('webhook-signature') ?? '');
  if (!ok) return res.sendStatus(401);
  res.sendStatus(200); // answer within 10 s, work afterwards

  const event = JSON.parse(raw);
  if (seen.has(event.eventId)) return; // at-least-once delivery
  seen.add(event.eventId);

  if (event.type === 'message.received' && event.data.channel.type === 'SMS') {
    const { sender, content } = event.data.message;
    if (content?.trim().toUpperCase() === 'C') {
      // Texts back from the same SIM that received the SMS.
      await um.messages.messageSend({
        to: { phone: sender },
        channels: [{ channel: 'SMS', accountId: event.data.channel.account.id }],
        text: 'Thanks, your appointment is confirmed.',
      });
    }
  }
});
app.listen(3000);
The signature check follows the documented Standard Webhooks scheme (HMAC-SHA256 over id.timestamp.rawBody). The reply names the SIM that received the text, so it leaves from the same number.

Vibe coding

Building with Cursor, Claude Code, Lovable or Bolt? Paste this

Your AI editor gets the route, the signature scheme and the SMS event shape in one go.

Prompt to copy
Add an Unlimited Messaging webhook to this app so it receives the SMS my own Android phone receives.

1. Create a POST route at /webhooks/um that reads the RAW request body.
2. Verify the Standard Webhooks signature: key = base64-decode(UM_WEBHOOK_SECRET without "whsec_"); expected = base64(HMAC-SHA256(key, `${webhook-id}.${webhook-timestamp}.${rawBody}`)); accept if any space-separated entry of the webhook-signature header equals `v1,${expected}` (constant-time compare); reject timestamps more than 5 minutes off; return 401 otherwise.
3. Return 200 at once, then process. Deduplicate on body.eventId.
4. On type "message.received" with data.channel.type "SMS", save data.message.sender (+E164, or null with the name in senderId), data.message.content and data.message.sentAt. conversation.id is null for SMS: group by sender.
5. To reply by SMS, install @unlimited-messaging/sdk, read UM_API_KEY from env, and call `client.messages.messageSend({ to: { phone: sender }, channels: [{ channel: 'SMS', accountId: data.channel.account.id }], text })`.
6. Put UM_API_KEY and UM_WEBHOOK_SECRET in .env and .env.example, never in code.

Docs: https://docs.unlimitedmessaging.app (Webhooks). Register the URL with POST https://api.unlimitedmessaging.app/webhooks and eventTypes ["message.received", "message.status"].

What people build

What can you do with incoming SMS on your own number?

Whatever starts with a customer texting the number on your van, your flyer or your invoice.

Reply-to-confirm reminders

Send a reminder by SMS, read the C or the R that comes back, update the booking and confirm, all from your usual number.

SMS into your CRM

Attach each incoming text to the contact by sender, next to their WhatsApp and email, since all three arrive in the same envelope.

  1. STOP handling

    Catch STOP or UNSUBSCRIBE in content and flag the contact before your next send.

  2. AI first answer

    Hand the text to an LLM for a draft, send it back by SMS or route it to a human.

  3. Alerts from machines and services

    Forward texts from an alarm, a sensor SIM or a supplier to Slack or email.

  4. No-code flows

    Point the webhook at n8n, Make or Zapier and branch on content without a server.

The table

Receiving SMS by webhook: Twilio vs Android gateways vs Unlimited Messaging

A rented number from a provider, or the SIM already in your phone.

Receiving SMS by webhook: Twilio vs Android gateways vs Unlimited Messaging
TwilioRented number (US)SMSGate / httpSMSOpen source Android gatewaysUnlimited MessagingAndroid app, paid plans
Number that receivesA number you lease from TwilioYour phone's SIMYour phone's SIM
Monthly number cost$1.15 local, $2.15 toll-freeNone beyond your carrier planNone beyond your carrier plan; webhooks need Medium or Max
Cost per incoming SMS$0.0083 per segment plus a carrier feeNoneNone from Unlimited Messaging
PayloadForm-encoded, X-Twilio-Signature (HMAC-SHA1)Webhook or callback URL (SMSGate: sms:received event)JSON, Standard Webhooks signature (HMAC-SHA256)
HardwareNoneAn Android phone kept onAn Android 8.0+ phone kept on
WhatsApp and email in the same webhookWhatsApp via a registered sender; email via SendGrid, separatelySMS onlyYes, same envelope, same signature
VolumeBuilt for high volumeOne phone's paceOne phone's pace

Checked on 9 November 2026 on Twilio's US SMS pricing, its incoming message and webhook security docs, and the SMSGate and httpSMS repositories (links in the sources). Carrier fees vary by sending carrier.

The catch

What are the limits of an SMS webhook from your phone?

No rented number and no per-SMS fee, but a phone is a phone.

  1. Webhooks are a paid feature

    On the Free plan, incoming SMS show up in the web app inbox. Pushing them to your server needs Medium (€19 a month, or €17 a month billed yearly) or Max.

  2. The phone must stay on and online

    If it is off, out of signal or killed by battery saving, nothing is relayed until it is back. Keep the app's battery unrestricted, and allow auto-start on Samsung, Xiaomi, Huawei, Oppo and Vivo.

  3. Android only, outside Google Play

    Google Play grants SMS permissions only to the default SMS app, so the app ships as a signed APK from GitHub. There is no iPhone version.

  4. Text only, from sign-in onward

    SMS is text only (no MMS). Only messages received after the app was signed in are uploaded: the texts already on the phone are never read.

  5. A personal line, a personal pace

    Consumer plans are sold for person-to-person use, and replies count against your plan. For thousands of inbound texts an hour or a short code, use an A2P provider.

FAQ

Questions about SMS webhooks

Ilian, senior software engineer

Written by

· Senior Software Engineer

A senior software engineer, he founds and runs several tech companies. He builds products around messaging APIs, automation and AI integration, and shares here what he learns while building.

View LinkedIn profileUpdated

Your own number, wired to your code.

Create an account, install the free Android app, and turn on webhooks with Medium or Max. Sending SMS stays free on every plan.