Skip to main content
All posts
Automation5 min read

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.

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 needsBetter fit
Read messages, reply, assign conversations, and see history togetherA shared SMS inbox built for team messaging
One operator working inside Twilio's approved message workflowKeep the work in Twilio
One terse arrival signal for a stable group that should open the system of recordTwilio 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 covers the Room setup. The webhook reference carries the current request fields, limits, cooldown, idempotency, and error contract.

Configure one Twilio number

Twilio's Messaging webhook reference 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:

<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 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.

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:

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.

In the illustrated example, Pingo the Pigeon, 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.

TestExpected result
Valid signature, account, number, and new MessageSidOne minimal job, then empty TwiML
Exact repeat of that signed eventNo second job; empty TwiML
Same MessageSid with changed signed contentStill no second job; the event identity already exists
Missing or invalid signatureReject; queue nothing
Wrong account, destination number, or malformed SIDReject; queue nothing
Queue persistence failsDo not return success; investigate the failed handoff
PingRoom returns 429 or a server errorRetry the consumer with the same key and body, honoring Retry-After
PingRoom returns a permanent credential or disabled-webhook errorStop 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, test with non-sensitive data, and keep the message sealed all the way to the phone.

PingRoom

Short-form communication.

View all posts
Product

Give each webhook its own Ping

Keep reusable Quick Pings for people while a form, build, or automation sends its own title and message into the Room.

5 min read