# How to Send Twilio Incoming SMS Alerts to a Shared Room

> Verify one Twilio incoming-message webhook and send fixed shared-phone copy without forwarding the sender, message body, media, or location metadata.

- Published: 2026-11-07
- Author: PingRoom
- Category: Automation
- Topics: Twilio incoming SMS notification, Twilio webhook push notification, Twilio shared SMS alert, incoming message webhook
- Canonical: https://pingroom.io/blog/twilio-incoming-sms-push-notifications
- Cover image: https://pingroom.io/blog/twilio-incoming-sms-push-notifications-cover.webp
- Cover description: Pingo watches two PingRoom avatars receive one sealed clay capsule while a separate content box stays closed.

**To alert a shared group when one Twilio number receives an SMS, point that number's incoming-message webhook at a small server-side relay. Verify Twilio's signature, accept only your account and destination number, discard the sender and message content, then queue one fixed Ping for a private PingRoom Room.**

Twilio remains the message record. PingRoom carries the arrival signal, not the conversation.

This is a webhook recipe, not a native Twilio integration or shared SMS inbox. The destination Room's owner needs **PingRoom Pro**. You also need an SMS-capable Twilio number, permission to change its Messaging webhook, a public HTTPS relay, durable storage for accepted event IDs, and server-side secret storage.

## Decide whether an arrival signal is enough

| What the group needs                                                              | Better fit                                  |
| --------------------------------------------------------------------------------- | ------------------------------------------- |
| Read messages, reply, assign conversations, and see history together              | A shared SMS inbox built for team messaging |
| One operator working inside Twilio's approved message workflow                    | Keep the work in Twilio                     |
| One terse arrival signal for a stable group that should open the system of record | Twilio webhook → relay → PingRoom Room      |

Do not replace an inbox with a notification relay. The recipe below is deliberately narrow: **New SMS received — open Twilio to review it.** It does not say who sent the message or what it contains.

## Create the private destination

Create a private Room for the people who need the signal. Under **Settings → Connections → From another app**, add an incoming webhook and store its full URL as `PINGROOM_WEBHOOK_URL` in the relay's secret store.

The [incoming-webhook guide](/blog/webhook-to-push-notification) covers the Room setup. The [webhook reference](/documents/webhooks) carries the current request fields, limits, cooldown, idempotency, and error contract.

## Configure one Twilio number

Twilio's [Messaging webhook reference](https://www.twilio.com/docs/usage/webhooks/messaging-webhooks) says a Twilio number can send an HTTP request when it receives a message. In the Twilio Console:

1. Open **Communications → Numbers & senders → Overview** and select the intended number.
2. Under its Messaging configuration, set **A message comes in** to **Webhook**.
3. Choose **HTTP POST** and enter the relay's exact public HTTPS URL.
4. Save the configuration.

Record that exact URL as `TWILIO_WEBHOOK_URL`. Signature validation depends on the URL Twilio used, including its scheme, host, path, port, and query string. If a proxy terminates TLS, reconstructing a different internal URL can make a genuine request fail validation.

The handler must return TwiML. When you do not want an automatic SMS reply, Twilio documents an empty response:

```xml
<Response></Response>
```

## Verify every form field before using three of them

Incoming Messaging webhooks use `application/x-www-form-urlencoded` data. Twilio includes fields such as `From`, `To`, `Body`, `NumMedia`, `AccountSid`, and `MessageSid`.

Pass the **complete parsed form** and the exact public URL to Twilio's official request validator. Do not build a hand-picked subset for signature verification. Twilio's [webhook security guide](https://www.twilio.com/docs/usage/webhooks/webhooks-security) warns that parameters can evolve and recommends the server-side SDK helpers.

After validation, this recipe reads only:

- `AccountSid`, to keep the route inside one Twilio account;
- `To`, to keep it on one selected number;
- `MessageSid`, to give one source message one replay identity.

Everything else is discarded after the request is verified. Do not log or queue `From`, `Body`, media URLs, geographic fields, or the complete form body.

```js
import express from "express";
import twilio from "twilio";

const app = express();
app.use(express.urlencoded({ extended: false, limit: "32kb" }));

app.post("/twilio/incoming", async (req, res) => {
  const signature = req.get("X-Twilio-Signature") ?? "";
  const valid = twilio.validateRequest(
    process.env.TWILIO_AUTH_TOKEN,
    signature,
    process.env.TWILIO_WEBHOOK_URL,
    req.body,
  );

  if (!valid) return res.sendStatus(403);

  const { AccountSid, To, MessageSid } = req.body;
  if (
    AccountSid !== process.env.TWILIO_ACCOUNT_SID ||
    To !== process.env.TWILIO_TO_NUMBER ||
    !/^SM[0-9a-f]{32}$/i.test(MessageSid ?? "")
  ) {
    return res.sendStatus(403);
  }

  await queue.enqueueOnce(MessageSid, {
    eventKey: MessageSid,
    title: "New SMS received",
    message: "Open Twilio to review it.",
  });

  res.type("text/xml").send("<Response></Response>");
});
```

The primary Twilio Auth Token is a credential. Keep it server-side, rotate it through Twilio's supported process, and never put it in browser code or the PingRoom payload.

## Queue before returning the empty response

Persist the minimal job before returning success. A database unique constraint on `MessageSid` is stronger than an in-memory set: two relay processes must not create two jobs for the same message.

The consumer sends only fixed copy:

```js
await fetch(process.env.PINGROOM_WEBHOOK_URL, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": job.eventKey,
  },
  body: JSON.stringify({
    title: job.title,
    message: job.message,
    correlation_id: job.eventKey,
  }),
});
```

Reuse the same `MessageSid` for the queue key, PingRoom `Idempotency-Key`, and `correlation_id`. A retry with the same fixed body can return the original PingRoom response instead of creating another Ping. If you deliberately change the body, use a new key; do not repurpose one source identity for two different Pings.

## Pingo leaves the message sealed

![Pingo stands between two PingRoom avatars as one sealed clay capsule rests in a queue tray and an identical replay stays behind a wooden stop.](/blog/twilio-incoming-sms-push-notifications-1.webp)

In the illustrated example, [Pingo the Pigeon](/Pingo), PingRoom's fictional CMO, watches one sealed arrival pass the gate while its replay stops. He is not reading a private Room, handling an SMS, or sending a Ping. The human team still owns the relay, credentials, privacy rules, and copy.

## Rehearse the boundaries before using a real number

Use synthetic messages and a disposable destination while you test.

| Test                                                              | Expected result                                                       |
| ----------------------------------------------------------------- | --------------------------------------------------------------------- |
| Valid signature, account, number, and new `MessageSid`            | One minimal job, then empty TwiML                                     |
| Exact repeat of that signed event                                 | No second job; empty TwiML                                            |
| Same `MessageSid` with changed signed content                     | Still no second job; the event identity already exists                |
| Missing or invalid signature                                      | Reject; queue nothing                                                 |
| Wrong account, destination number, or malformed SID               | Reject; queue nothing                                                 |
| Queue persistence fails                                           | Do not return success; investigate the failed handoff                 |
| PingRoom returns `429` or a server error                          | Retry the consumer with the same key and body, honoring `Retry-After` |
| PingRoom returns a permanent credential or disabled-webhook error | Stop retrying and fix the connection                                  |

The companion fixture for this article passed **36 deterministic assertions** with Twilio-shaped forms, locally generated HMAC-SHA1 signatures, an in-memory queue stand-in, and a local PingRoom contract mock. It did not use a live Twilio number, public relay, PingRoom Room, SMS content, phone, or notification delivery. For production signature validation, use Twilio's maintained SDK helper rather than copying the fixture's test implementation.

## Know what success means

An accepted Twilio webhook means the relay verified and durably queued one source event. An accepted PingRoom request means PingRoom created one Ping. Neither result proves that a person read the SMS or that a phone displayed the notification; connectivity, operating-system policy, Room mutes, and notification settings still apply.

If the team needs the actual sender, body, replies, ownership, or conversation status, stop here and use a proper shared inbox. If a fixed arrival signal is the whole job, [create the private destination Room](/app/rooms), test with non-sensitive data, and keep the message sealed all the way to the phone.
