Intempt Docs
GuidesCustomer Data PlatformWebhooks

Twilio webhook

Collect SMS, MMS and WhatsApp delivery, replies and opt-outs from your own Twilio account. Twilio is already an Intempt destination; this page is the source direction.

Research Preview. This connector is rolling out account by account. Ask your Intempt contact to enable it for your workspace.

Two Twilio integrations, two directions. The Twilio destination lets Journeys send SMS through Intempt's Twilio connection and already tracks delivery on those sends. This page is the source: your own Twilio account, sending from your own systems, streaming status callbacks and inbound messages into Intempt. If you only send SMS through Intempt Journeys, you don't need this page.

The Twilio webhook collects messaging activity for numbers you own: sends, deliveries, carrier rejections, WhatsApp and RCS read receipts, inbound replies, quick-reply taps, and STOP and START keywords.

Inbound is the part that matters most. A reply, a STOP, a first message from an unknown number: none of that exists anywhere else in your stack.

Available events

Twilio reports ten outbound statuses on the status callback. Six become events. The other four are intermediate and update the pending send instead of emitting, so SMS volume isn't counted five times per message.

EventDescriptionIntempt event
sentHanded to the carrier. This is the sendmessaged_sms
deliveredCarrier delivery receiptdelivered_sms
undeliveredCarrier accepted, then rejected. The SMS equivalent of a bouncebounced_sms
failedTwilio never reached a carrier: bad From number, geo permissions, balance, malformed bodysms_send_failed
readRead receipt. WhatsApp and RCS onlyread_sms
canceledScheduled message revoked before send. Messaging Service onlysms_send_canceled
queued, accepted, scheduled, sendingIntermediate. Update the pending messaged_smsnone
Inbound messageA person messaged one of your numberssms_inbound_received
Inbound button tapQuick-reply or interactive button on WhatsApp or RCS. Body is the button textclicked_sms
Inbound OptOutType = STOPOpt-out keyword, matched by Twilio Advanced Opt-Outunsubscribed_consent, sms_consent_changed
Inbound OptOutType = STARTOpt-in keyword, matched by Twilio Advanced Opt-Outsubscribed_consent, sms_consent_changed
First inbound from an unknown numberuser_created
Inbound with changed WhatsApp ProfileNameuser_updated

Each event is normalised onto a named stream, so you build segments and journey triggers on the event name rather than on the payload shape.

undelivered and failed stay separate on purpose. undelivered means the number is real enough for a carrier to have tried; retry later, suppress after a run of them. failed means your Twilio configuration or balance is wrong. Folding them together would make a billing problem look like number decay and suppress good numbers.

Opt-out and opt-in are webhook facts only when Advanced Opt-Out is enabled on a Messaging Service. Then Twilio matches the keyword, replies with the confirmation, updates its own block list, and puts OptOutType on the inbound webhook. Intempt records that as consent from the wire.

Without Advanced Opt-Out, Intempt infers consent by matching the inbound Body against the STOP and START keyword families, or from error code 21610 (attempt to send to an unsubscribed recipient) on a later failed callback. Every consent event carries a derivation_basis so an audit can tell which of the three produced it.

Identity

The identity key is the phone number: From on an inbound message, To on a status callback. WhatsApp addresses arrive as whatsapp:+E164; the E.164 number inside is what matches a profile. Twilio has no email, so a Twilio-born profile joins an existing person only if that person already holds the number. Carrier number recycling makes a phone match weaker than an email match; treat a profile that exists on phone alone accordingly.

AccountSid on every callback is your own Twilio account. That's what tells Intempt this event came from the source and not from the Intempt destination.

Endpoint

The collector path for Twilio events is:

https://<your collector host>/webhooks/events/twilio

Twilio doesn't let you add request headers, so the source token travels in the endpoint URL Intempt gives you. The same URL serves both the status callback and the inbound message webhook. MessageSid is the idempotency key.

Historical load

Twilio's historical load runs on the certified Airbyte source source-twilio, using the same account_sid and auth_token you enter above. It loads Messages, Calls, Conversations and Usage records.

Status callbacks and inbound webhooks cover everything after you register them. The two halves are attributed to the same source, so a profile reads as one timeline.

Setup

There are two places to set the URL: the status callback on outbound sends, and the inbound webhook on each number or Messaging Service. Both point at the same Intempt endpoint. Create the source in Intempt first.

Configure Twilio

  1. Create a Twilio source on the Integrations page and copy the endpoint URL.

  2. Status callbacks. If you send through a Messaging Service, open Messaging, Services, your service, Integration, and paste the Intempt endpoint under Delivery Status Callback. If you send from individual numbers with the API, pass the Intempt endpoint as StatusCallback on each send.

  3. Inbound messages. For a Messaging Service, on the same Integration page choose Send a webhook and paste the Intempt endpoint as the Request URL. For an individual number, open Phone Numbers, the number, Messaging Configuration, and paste it under A message comes in.

  4. For consent from the wire, open the Messaging Service, Opt-Out Management, and enable Advanced Opt-Out.

  5. Send a test message from your account and confirm sent and delivered appear in the Intempt event stream. Reply to it and confirm sms_inbound_received follows.

On this page