Guide
How Twilio and Salesforce connect: architecture and options
What actually happens between Salesforce and Twilio when a text goes out and a reply comes back, and the security and reliability decisions every integration has to make.
Key takeaways
- Outbound texts are HTTPS callouts from Salesforce to Twilio's REST API, which Salesforce only allows to endpoints an admin has approved.
- Replies and delivery updates arrive as webhooks. Twilio can't log in to Salesforce, so they need a public endpoint, usually an Apex REST class on a Salesforce Site.
- Treat that endpoint as part of the internet: give the guest user the least access possible and check
X-Twilio-Signatureon every request. - Webhooks can arrive twice and out of order, and callouts can time out. Store first, process asynchronously, and make every step idempotent.
- US traffic needs A2P 10DLC registration or toll-free verification. No integration can do that part for you.
The moving parts
Every Salesforce-to-Twilio integration, whether you build it or install it, has the same shape. Messages go out as API calls from Salesforce, and everything that happens afterwards comes back as HTTP requests from Twilio.
- Salesforce record
- Apex callout
- Twilio Messages API
- Carrier
- Customer's phone
- Customer's phone
- Carrier
- Twilio webhook
- Salesforce Site
- Apex handler
| What happens | Direction | Salesforce side | Twilio side |
|---|---|---|---|
| Send a text | Salesforce → Twilio | Apex callout to an approved endpoint | Messages resource of the REST API |
| Receive a reply | Twilio → Salesforce | Public Apex REST endpoint on a Site | Incoming-message webhook on the number or Messaging Service |
| Track delivery | Twilio → Salesforce | The same kind of endpoint | Status callback URL |
| Opt-out keywords | Twilio → Salesforce | Inbound handler recognizes STOP and START | Twilio's own keyword handling, and error 21610 when you send to an opted-out number |
Outbound: Salesforce calls Twilio
To send a message, Salesforce makes an HTTPS POST to Twilio's Messages resource,
/2010-04-01/Accounts/{AccountSid}/Messages.json, authenticated with HTTP Basic auth using your Account SID and
Auth Token, or an API key and secret. The form-encoded body carries the destination (To), the sender
(From, or MessagingServiceSid to let a Messaging Service choose), the Body, optional
MediaUrl values for MMS, and optionally a StatusCallback URL. Twilio answers with a message SID and
an initial status such as queued or accepted. That means Twilio has the message, not that it was
delivered.
What Salesforce requires
- An approved endpoint. Apex can only call hosts an admin has allowed, through a Remote Site Setting or a Named Credential. Named Credentials, with External Credentials, can also hold the secret and add authentication for you, which keeps tokens out of code and custom fields.
- No callouts in the wrong place. Apex can't make a callout directly from a trigger, and it can't make one after uncommitted DML in the same transaction. The usual pattern is to save a record first, then make the callout in a Queueable or future method that allows callouts.
- Respect the limits. Callouts count against per-transaction limits and asynchronous Apex has daily limits, so batch work and back off rather than calling Twilio once per record in a loop.
- Protect the credentials. Whoever can read the Auth Token can send messages and read message logs on your Twilio account. Never log it, never send it to the browser, and restrict who can change it.
Inbound: Twilio calls Salesforce
When a customer texts one of your numbers, Twilio sends an HTTP POST to the webhook URL configured on that number
or its Messaging Service. The request is form-encoded and includes fields such as MessageSid,
AccountSid, From, To, Body and NumMedia, with
MediaUrl0 and friends when pictures are attached. Media arrives as links to files Twilio stores, not as
attachments in the request.
Twilio can't complete an OAuth login, so the endpoint has to be reachable without one. In Salesforce that normally means an
Apex REST class (@RestResource) exposed through a public Salesforce Site or Experience Cloud site. Requests run
as the site's guest user.
Least privilege for the guest user
- Give the guest user access to the one Apex class that handles the webhook, and nothing it doesn't need.
- Salesforce deliberately restricts what guest users can see and do with records. Don't loosen sharing to make the handler work.
- Handlers usually do their few writes in system mode, precisely because the guest user can't. That's safe only if the handler checks everything it receives, starting with the signature.
- Keep the handler short: validate, store, respond. Sites have usage limits, and Twilio records slow or failed webhook responses as errors in your Twilio account.
The response matters less than you might think. For a text, return a quick 200 with an empty TwiML
<Response/> if you don't want Twilio to send an automatic reply.
Status callbacks
If you set a StatusCallback URL, per message or on the Messaging Service, Twilio posts each status change for
outbound messages: queued, sending, sent, delivered, undelivered and
failed, plus a few others for particular features. Failures carry an ErrorCode, such as 30007 when a
carrier filtered the message.
- They can arrive out of order. A sent can land after delivered. Rank the statuses and never move a message backwards.
- They can arrive more than once. Key your updates on the message SID and status so a repeat changes nothing.
- Delivered doesn't mean read. It means the carrier confirmed delivery to the handset, and not every carrier or country reports it.
Messaging Services and registration
A Twilio Messaging Service groups sender numbers and holds settings that apply to all of them, such as webhook URLs, opt-out handling and Smart Encoding. A number can belong to only one Messaging Service at a time. In the US, the Messaging Service is also where registration lands.
- A2P 10DLC. Application-to-person texting from ordinary 10-digit US numbers requires a registered brand and an approved campaign describing your use case, sample messages and how people opt in and out. Numbers are tied to the campaign through a Messaging Service. Unregistered traffic is blocked or filtered.
- Toll-free numbers need toll-free verification before they can send reliably to US and Canadian numbers.
- Short codes have their own application and review process.
Registration is between you, Twilio and the carriers, and approval takes time. Start it early: an integration that works perfectly in a sandbox still can't deliver unregistered US traffic.
Checking X-Twilio-Signature
Because the webhook endpoint is public, anyone who finds the URL can post to it. Twilio signs every webhook request with an
X-Twilio-Signature header so you can tell real requests from forged ones. For a form-encoded request, the
signature is an HMAC-SHA1 of the full request URL followed by each POST parameter name and value, sorted by name, keyed with
your Auth Token and Base64-encoded.
// Illustrative only. Validate X-Twilio-Signature on a form-encoded webhook.
// fullUrl must be exactly the URL Twilio requested, including any query string.
String data = fullUrl;
List<String> names = new List<String>(postParams.keySet());
names.sort(); // by parameter name, as Twilio does
for (String name : names) {
data += name + postParams.get(name);
}
Blob mac = Crypto.generateMac('hmacSHA1', Blob.valueOf(data), Blob.valueOf(authToken));
// equals() is case-sensitive; == on Apex strings is not.
Boolean valid = EncodingUtil.base64Encode(mac).equals(signatureHeader);
if (!valid) {
// Reject the request and record why. Don't process it.
}
- Use the exact URL Twilio called: scheme, host, path and query string. A changed site domain or a trailing slash breaks every signature.
- Reject by default. A missing or wrong signature means the request isn't processed, and the rejection is recorded.
- Check the account too. Compare the request's
AccountSidwith your own as a second check. - Plan for token rotation. When the Auth Token changes, signatures are made with the new one, so update both sides together.
Async processing and idempotency
Networks fail in both directions. A reliable integration assumes it and makes each step safe to repeat.
Inbound: store first, process later
Validate the request, save what arrived (as a record or a platform event), respond, and do the real work asynchronously in a Queueable or an event-triggered process. That keeps the webhook fast, keeps the guest user's footprint small, and gives you a record to reprocess if something downstream fails. Use the message SID as a unique key so a duplicate delivery doesn't create a second message.
Outbound: record the request before the callout
Save the message in Salesforce before calling Twilio, then send it from an asynchronous job. The hard case is a timeout: Twilio may or may not have created the message. Sending again blindly risks texting the customer twice. The safer approach is to mark the outcome as unknown and check with Twilio, for example by looking for a matching message, before deciding to retry. Plan for this yourself rather than assuming the API will deduplicate requests for you.
Rate limits
Twilio queues messages according to each sender's throughput and answers with HTTP 429 when you exceed API concurrency limits. Treat a 429 as "try again later", with back-off, not as a failure.
Idempotency keys for callers
Record-triggered automation can run more than once for the same business event, for example when a record is saved twice.
Give each message request a key that describes the intent, such as an order ID plus -shipped, and ignore
repeats of the same key.
Build or buy
A one-way notification from a single flow is a reasonable thing to build. Two-way conversations, shared inboxes and governance are where the work grows. Either choice can be right; it depends on scope and on who will maintain it.
| Consideration | Build it yourself | Install a packaged app |
|---|---|---|
| First message | Quick for a simple one-way send | Install, connect Twilio, work through setup |
| Webhooks and signatures | You design, secure and maintain the Site and handler | Provided; check how the vendor secures it |
| Statuses, timeouts and retries | You build the state machine and reconciliation | Provided; ask what happens on a timeout |
| User interface | Record component, inbox and templates in LWC | Provided |
| Opt-outs and access | You design the data model and the checks | Provided; check scope and enforcement |
| Fit to your process | Exactly what you build | The vendor's model, extended through its Flow actions or API |
| Ongoing cost | Developer time and maintenance, plus Twilio | Any license fee for the app, plus Twilio |
Questions worth asking any vendor, or yourself before building:
- Does message data leave Salesforce for a third-party server, or does Salesforce talk to Twilio directly?
- Whose Twilio account sends the messages, and who is billed?
- How is the webhook secured, and what access does the guest user get?
- What happens when a callout times out? Can a customer receive a message twice?
- Are opt-outs checked again right before a scheduled or queued message is sent?
Where ConnectSMS fits
ConnectSMS is a managed package that implements this architecture inside your org. It's built on the Salesforce Platform, and Salesforce connects directly to your own Twilio account, with no ConnectSMS servers in between. The only callout endpoints it uses are Twilio's.
- Credentials are verified with Twilio before they're saved, and the Auth Token is never shown in the browser or written to logs.
- Inbound uses a public Salesforce Site whose guest user gets the ConnectSMS Webhook Guest permission set, which grants access only to the webhook class. Signature checking has Enforce, Monitor and Off modes; Enforce is the default and is required before sending can be turned on. Requests must also match your Account SID.
- Outbound messages are recorded in Salesforce before ConnectSMS calls Twilio. A timeout or server error marks the message Unknown, ConnectSMS checks with Twilio, and it becomes the matching status or Failed; it's never resent automatically. Status callbacks only move a message forward, and a 429 sends it back to the queue with back-off.
- Idempotency keys on the Flow actions, Apex API and REST calls stop duplicate sends.
- Messaging Services sync from Twilio, and each number must belong to exactly one. US numbers still need an approved A2P 10DLC campaign or verified toll-free status, which you arrange with Twilio.
How the ConnectSMS Twilio integration works, reliability in detail, and the developer reference.
