---
name: twilio-salesforce
description: Build a production-grade Twilio to Salesforce integration covering architecture, the step-by-step build with Apex code, field mapping, API and governor limits, security, monitoring, and the pitfalls to design out. Use when connecting, building, or debugging a Twilio and Salesforce integration.
---

# Twilio to Salesforce integration

> Cloudsheer's delivery playbook for Twilio on Salesforce, distilled from 5 client projects and 28 delivery tasks. Apply it as the reference approach when building this integration.

## Overview

Twilio SMS and telephony triggered from Salesforce. We have shipped it across 6 client projects and 28 build tasks.

The value is that the action happens automatically from the record your team already works in, with the result tracked back in Salesforce.

We build it the Salesforce-native way: a Connected App and Named Credentials so no secrets ever live in code, field mappings that respect your data model, and record-triggered automation that does the work.

Every Twilio build is delivered by a senior Salesforce architect on a fixed price, tested end to end in a sandbox, deployed to your org, and backed by 30 days of hypercare. You own the result: documented, source-controlled, and free of black-box middleware lock-in.

## Integration facts

**Connects via:**
- Twilio for Salesforce managed package (AppExchange) with Lightning SMS components (1:1 SMS, SMS Inbox, SMS Campaign)
- An Apex helper library that calls the Twilio REST API for custom SMS/Voice
- A Twilio Send SMS invocable action usable from Flow

**Package:** Twilio for Salesforce

**Authentication:** HTTP Basic auth to Twilio using Account SID + Auth Token (or API Key SID/Secret); credentials stored in package config / Named Credentials, secured via Named Credential to api.twilio.com

**API type:** REST+Webhooks

**API base:** `https://api.twilio.com/2010-04-01`

**Key endpoints:**
- `/Accounts/{AccountSid}/Messages.json (send/receive SMS)`
- `/Accounts/{AccountSid}/Calls.json (voice)`
- `Messaging Services (MG SID) for A2P sending`
- `StatusCallback + inbound message webhooks`

**Webhook and platform events:**
- `Inbound message received`
- `Message status callback: queued / sent / delivered / undelivered / failed`

**Official docs:** https://www.twilio.com/docs/salesforce

## Prerequisites

- A Salesforce edition with API access (Enterprise, Unlimited, or Developer)
- A dedicated sandbox to build and test in
- A Twilio account on a plan with API access
- System Administrator access on both systems
- A dedicated integration user with a minimum-access permission set
- Agreement on the objects, fields, and sync direction for the Twilio data

## Architecture

Data flows left to right through four lanes:

1. **Sources:** Salesforce record, Flow / Apex trigger
2. **Integration layer:** Payload build, Twilio API call, Status write-back
3. **Salesforce:** message, Related records, Reports
4. **Outcomes:** Action done automatically, Status on the record, No app-switching

## How it works at runtime

1. **Trigger in Salesforce** `[In Salesforce]`: A record change or a button starts the Twilio action.
   - Note: `A record-triggered flow or a Quick Action fires the process.`
2. **Payload is built** `[In Salesforce]`: A flow or Apex assembles the request and maps the Salesforce fields.
   - Note: `Serialized with JSON.serialize; the callout is queued to run asynchronously.`
3. **Call Twilio** `[In transit]`: The request is sent to Twilio.
   - Note: `HTTPS callout via callout:NamedCredential over SMS API, with no secrets in code.`
4. **Result written back** `[In Salesforce]`: Twilio performs the action and the status is written back.
   - Note: `Response parsed; status and external ids stored on the record for audit.`

## Step-by-step build

### Step 1: Plan the integration and prerequisites

Before any code, we lock down access and design how Twilio and Salesforce will talk.

- A Salesforce edition with API access (Enterprise, Unlimited, or Developer) and a dedicated sandbox track
- A Twilio account on a plan with API access, plus admin rights on both systems
- A dedicated Salesforce integration user with a minimum-access permission set, never a personal admin login
- Decide direction (inbound, outbound, or bidirectional) and cadence (real-time callouts vs scheduled batch)
- Budget the daily API request allocation and per-transaction callout limits up front

### Step 2: Register the app and scope OAuth in Twilio

We create the connection on the Twilio side and collect exactly the access Salesforce needs.

- Create an OAuth app or scoped API token in Twilio to get the Client ID and Client Secret
- Grant the minimum OAuth scopes required, and note the API base URL and version
- Whitelist the Salesforce callback URL if the tool uses the authorization-code flow

### Step 3: Store secrets with External and Named Credentials

We use the modern Salesforce auth stack, so no secret ever lives in code or metadata.

- Create an External Credential (OAuth 2.0 or custom) with a named principal
- Create a Named Credential pointing at the Twilio base URL and enable the generated authorization header
- Grant the External Credential through a permission set, so only the integration user can call out
- This replaces legacy Remote Site Settings and hard-coded tokens entirely

> **Pro tip: Named Credentials, not code** Named Credentials keep the secret and endpoint out of your Apex and metadata, so nothing sensitive ships in a deployment or lands in version control.

### Step 4: Design the data model and external IDs

We make Twilio records land cleanly on the right Salesforce object with no duplicates.

- Map every Twilio field to the message and related objects, documenting type, picklist values, and record types
- Add a unique, external-id, case-insensitive field on each object as the match key
- Define owner assignment, required-field defaults, and relationship lookups

### Step 5: Build inbound ingestion

If Twilio data flows in, we ingest it safely and idempotently.

- Expose an Apex REST resource (@RestResource) or subscribe to Twilio's push or Platform Events
- Authenticate and parse the payload, then Database.upsert on the external id in bulk
- Make it idempotent, so a retried or duplicate payload never creates a second record

Reference implementation (`snippets/InboundRecordApi.cls`):

```apex
@RestResource(urlMapping='/inbound/records/*')
global with sharing class InboundRecordApi {
  @HttpPost
  global static void upsertRecords() {
    List<Row> items = (List<Row>) JSON.deserialize(
      RestContext.request.requestBody.toString(), List<Row>.class);

    List<MyObject__c> rows = new List<MyObject__c>();
    for (Row r : items) {
      rows.add(new MyObject__c(External_Id__c = r.id, Name = r.name, Amount__c = r.amount));
    }
    upsert rows External_Id__c;         // bulk + idempotent on the external id
    RestContext.response.statusCode = 200;
  }
  global class Row { global String id; global String name; global Decimal amount; }
}
```

### Step 6: Build outbound sync

If Salesforce drives Twilio, we call out the right way.

- A record-triggered flow to invocable Apex, or an Apex trigger handing off to a Queueable
- Triggers cannot call out synchronously, so the HTTP callout runs asynchronously
- Serialize with JSON.serialize and call Twilio via callout:NamedCredential, handling every status code

Reference implementation (`snippets/SyncToServiceQueueable.cls`):

```apex
public class SyncToServiceQueueable implements Queueable, Database.AllowsCallouts {
  private List<Id> ids;
  public SyncToServiceQueueable(List<Id> ids) { this.ids = ids; }

  public void execute(QueueableContext ctx) {
    for (MyObject__c rec : [SELECT Id, Name, External_Id__c FROM MyObject__c WHERE Id IN :ids]) {
      HttpRequest req = new HttpRequest();
      req.setEndpoint('callout:Service_NC/v1/records'); // secret lives in the Named Credential
      req.setMethod('POST');
      req.setHeader('Content-Type', 'application/json');
      req.setBody(JSON.serialize(new Map<String,Object>{
        'externalId' => rec.External_Id__c, 'name' => rec.Name }));
      HttpResponse res = new Http().send(req);
      if (res.getStatusCode() != 200) ErrorLog.capture(rec.Id, res);
    }
  }
}
```

### Step 7: Engineer for scale and governor limits

We build it to survive real volume, not just a demo.

- Bulkify everything: no SOQL, DML, or callouts inside loops (100 SOQL and 150 DML per transaction)
- Use Queueable, Batchable, or Scheduled Apex for volume, and chain jobs for large syncs
- Add retry with backoff and a dead-letter Error_Log__c record for anything that fails

Reference implementation (`snippets/NightlySyncBatch.cls`):

```apex
global class NightlySyncBatch implements Database.Batchable<SObject>, Database.AllowsCallouts {
  global Database.QueryLocator start(Database.BatchableContext bc) {
    return Database.getQueryLocator([SELECT Id, External_Id__c FROM MyObject__c WHERE Needs_Sync__c = true]);
  }
  global void execute(Database.BatchableContext bc, List<MyObject__c> scope) {
    ServiceClient.sync(scope);       // one callout per 200-record chunk stays under limits
  }
  global void finish(Database.BatchableContext bc) { /* chain the next job or log the run */ }
}
```

> **Watch out: governor limits** Salesforce caps SOQL, DML, and callouts per transaction. Bulkify everything and move volume to Queueable or Batch Apex, or the integration will fail at scale.

### Step 8: Lock down security and compliance

We give the integration exactly the access it needs and nothing more.

- A least-privilege permission set, field-level security, and sharing for the integration user
- Rotate secrets on a schedule, and add Shield Platform Encryption for sensitive fields where required

### Step 9: Test like production

We prove it works before it ships.

- Apex tests with Test.setMock(HttpCalloutMock) covering success, failure, and a 200-record bulk case
- At least 75 percent coverage, plus sandbox UAT and a parallel run against the live system

Reference implementation (`snippets/SyncToServiceTest.cls`):

```apex
@IsTest
private class SyncToServiceTest {
  private class Mock implements HttpCalloutMock {
    public HttpResponse respond(HttpRequest req) {
      HttpResponse res = new HttpResponse();
      res.setStatusCode(200); res.setBody('{"ok":true}');
      return res;
    }
  }
  @IsTest static void syncsInBulk() {
    Test.setMock(HttpCalloutMock.class, new Mock());
    List<MyObject__c> recs = new List<MyObject__c>();
    for (Integer i = 0; i < 200; i++)
      recs.add(new MyObject__c(Name = 'Row ' + i, External_Id__c = 'EXT-' + i));
    insert recs;

    Test.startTest();     // proves the callout is bulk-safe under governor limits
    System.enqueueJob(new SyncToServiceQueueable(new List<Id>(new Map<Id,MyObject__c>(recs).keySet())));
    Test.stopTest();
  }
}
```

### Step 10: Deploy, monitor, and hand over

We ship it safely and keep it healthy.

- Deploy via change sets or an SFDX and CI pipeline, and assign the permission sets
- Turn on monitoring and alerting on the Error Log, and optionally Event Monitoring
- Hand over with 30 days of hypercare and failure alerting

## Data model

| Object | Purpose | Key fields |
| --- | --- | --- |
| `message` | The primary Salesforce record Twilio data maps onto. | `External_Id__c, Name, Status` |
| `Account` | Matched or created for the customer or company behind the record. | `Name, External_Id__c` |
| `Error_Log__c (custom)` | Captures every request, response, and failure so anything can be replayed. | `Payload__c, Status__c, Related_Id__c` |

Salesforce objects typically in play: `Contact`, `Lead`, `Campaign`, `Task/Activity`, `custom SMS message objects in the package`

## Field mapping (example)

| Twilio | Salesforce | Notes |
| --- | --- | --- |
| Salesforce message | `Twilio record` | Direction: Salesforce to Twilio |
| Record id | `Twilio external reference` | Stored back on the record |
| Key fields | `Twilio fields` | Mapped per template |
| Status | `Twilio status` | Written back on completion |
| Created / updated at | `LastModifiedDate` | Enables delta sync and audit |
| Owner or rep | `message.OwnerId` | Assignment rules or a default owner |

Tailor the full mapping to the org. Always upsert on an external-id field so retries are idempotent.

## API and rate limits

### Twilio-specific

- Salesforce: 100 callouts per transaction, 10s per-callout timeout, 6MB heap, daily API caps by edition
- Twilio default long-code throughput ~1 message/second; higher via short code or Messaging Service
- US A2P 10DLC campaign registration required for long-code SMS or messages get filtered

### Salesforce platform

- Salesforce caps API calls per 24 hours by edition and license count. We budget the daily allocation up front so the integration never starves other tools.
- Per transaction, Apex allows 100 SOQL queries, 150 DML statements, and 100 callouts. We bulkify everything and move volume async to stay well under them.
- Twilio enforces its own rate limits. We honour them, and back off with jitter on HTTP 429 responses instead of hammering the API.
- A synchronous callout can run for up to 120 seconds. Anything longer runs in Queueable or Batch Apex, never inline.

## Security checklist

- Secrets stored in Named Credentials and permission sets, never in code or metadata
- A least-privilege integration user, with field-level security and sharing scoped tight
- All traffic over TLS, with signature verification on inbound events
- Shield Platform Encryption available for sensitive fields
- A full audit trail: every request and response logged for traceability
- Every automation runs as a dedicated integration user, so actions are attributable and revocable
- Sandbox-first delivery and change-set deployment keep production changes reviewed and controlled

## Monitoring and reliability

- Every request and response is logged to a custom Error Log object, tagged with the related record id.
- Failed calls retry with exponential backoff; anything still failing lands in a dead-letter queue for review.
- Idempotency keys guarantee a retried or duplicate event never double-posts a record.
- A dashboard surfaces failures, latency, and volume so problems are caught before users notice.
- Optional email or Slack alerts fire on repeated failures or a stalled sync.

## Testing and deployment

- Apex unit tests with HttpCalloutMock cover the success path, failure handling, and a 200-record bulk case, at 75 percent or higher coverage.
- The full flow is validated in a sandbox against real sample data and the edge cases that matter.
- A parallel run reconciles the integration against your live system before cutover.
- Everything deploys through change sets or an SFDX and CI pipeline, under version control.
- Permission sets, sharing, and Named Credentials are configured in production, then we run 30 days of monitored hypercare.

## Pitfalls to design out

- **Duplicate records on retry:** Upsert on a unique external-id field so retried payloads are idempotent.
- **Hitting governor limits at volume:** Bulkify and move work to Queueable or Batch Apex; never call out inside a loop.
- **Callouts failing when a token expires:** Use Named Credentials so Salesforce refreshes the OAuth token automatically.
- **No visibility when it breaks:** We log every call and surface failures on a dashboard with alerts, so an issue never goes unnoticed.
- **Reporting drifts from reality:** External-id keys and a delta timestamp keep Salesforce and the source reconciled, so reports stay trustworthy.

### Twilio-specific gotchas

- Bundled components cover SMS only; Voice and other Twilio products require custom Apex via the helper class
- Twilio supports only the latest package version, so upgrades are effectively mandatory
- Callout and heap limits make high-volume bulk SMS from one transaction fragile; use batch/queueable Apex

## FAQ

**How do you authenticate Twilio with Salesforce?**

We connect Twilio using named credentials and API keys and store every secret in Salesforce Named Credentials with a permission set, so nothing is hard-coded or shipped in metadata.

**Does the Twilio integration handle bulk volume?**

Yes. All Apex is bulkified, volume moves to Queueable or Batch Apex, and we respect the Salesforce governor limits (SOQL, DML, and callout caps per transaction).

**How do you prevent duplicate records?**

We upsert on a unique external-id field, so a retried or duplicate payload is idempotent and never creates a second message.

**How is the integration tested and deployed?**

Apex tests with HttpCalloutMock cover the success, failure, and a 200-record bulk case (75 percent plus coverage). We deploy via change sets or an SFDX and CI pipeline.

**What happens if Twilio or Salesforce is briefly down?**

Failed calls retry with backoff and land in an Error Log object with alerting, so nothing is lost and any event can be replayed.

**Inbound, outbound, or both?**

We build whichever direction you need: an Apex REST endpoint for inbound, record-triggered flows or Queueable callouts for outbound, or both for a two-way sync.

---

Maintained by [Cloudsheer](https://www.cloudsheer.com). Full illustrated guide: [Twilio technical guide](https://www.cloudsheer.com/integrations/twilio/technical-guide). Want it built for you at a fixed price? [Book a free 30-minute call](https://cal.com/cloudsheer-consulting/30min?overlayCalendar=true).
