Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Sdk Laravel Package

twilio/sdk

The official Twilio PHP SDK for sending SMS, voice calls, WhatsApp, and more via Twilio's API. Supports PHP 8.2–8.4 and installs with Composer for quick integration.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and configure

composer require twilio/sdk

The SDK requires PHP 8.2–8.4 — check this before adding it to an older Laravel 9/10 project.

Keep credentials out of config/ and source control. Wire them through config/services.php:

// config/services.php
'twilio' => [
    'sid'   => env('TWILIO_ACCOUNT_SID'),
    'token' => env('TWILIO_AUTH_TOKEN'),
    'from'  => env('TWILIO_FROM_NUMBER'),
],

Where to look first

  1. README.md → "Test your installation" (send an SMS) — that's your smoke test.
  2. The auto-generated library reference (twilio.com/docs/libraries/reference/twilio-php/) — this is the real API surface. The README only covers a handful of resources; everything else (verify, lookups, messaging, incomingPhoneNumbers, studio) is discovered through the generated docs or IDE autocompletion on $client->....
  3. example/ in the repo for OAuth/Orgs patterns.
  4. Twilio's error code dictionary (e.g. 21610) — you will consult this constantly.

First real use case

Bind the client once, then send:

// app/Providers/AppServiceProvider.php
$this->app->singleton(\Twilio\Rest\Client::class, fn () => new \Twilio\Rest\Client(
    config('services.twilio.sid'),
    config('services.twilio.token'),
));

// anywhere
$client->messages->create('+15558675309', [
    'from' => config('services.twilio.from'),
    'body' => 'Your order has shipped.',
]);

Do this, not new Client(...) scattered through controllers — it keeps a single place to swap in region/edge, a custom HTTP client, or a fake in tests.

Implementation Patterns

1. Prefer Laravel's notification channel for plain SMS

Laravel ships a twilio notification channel that already depends on this SDK. If all you need is "send a text," use Notification::route('twilio', $phone)->notify(...) and skip the SDK entirely. Reach for twilio/sdk directly when you need resource operations the channel doesn't expose: scheduling messages, listing/fetching historical messages, Verify, Lookup, subaccounts, Studio flows.

2. Wrap sends in queued jobs with explicit retry semantics

class SendSms implements ShouldQueue
{
    public int $tries = 3;
    public array $backoff = [5, 30, 120];

    public function handle(\Twilio\Rest\Client $client): void
    {
        try {
            $client->messages->create($this->to, [
                'from' => config('services.twilio.from'),
                'body' => $this->body,
                'statusCallback' => route('webhooks.twilio.status'),
            ]);
        } catch (\Twilio\Exceptions\TwilioException $e) {
            if (in_array($e->getCode(), [21610, 21211])) { // unsubscribed / invalid number
                $this->fail($e);   // don't retry permanent failures
                return;
            }
            throw $e;              // let the queue retry transient ones
        }
    }
}

Split Twilio errors into permanent (bad number, opted out, insufficient funds) and transient (5xx, 429, timeouts). Only the latter should retry — the SDK itself does no retrying, so this logic is yours.

3. Generating TwiML from a webhook route

Route::post('/webhooks/twilio/voice', function (\Twilio\Rest\Client $client) {
    $response = new \Twilio\TwiML\VoiceResponse();
    $response->say('Please hold while we connect you.');
    $response->dial($agentNumber, ['timeout' => 20]);

    return response((string) $response, 200, ['Content-Type' => 'text/xml']);
});

MessagingResponse, VoiceResponse, and FaxResponse are the three builders; they chain, and each child element has typed setter methods (prefer $response->play($url, ['loop' => 5]) over hand-writing XML).

4. Validate inbound webhook signatures

Never trust a POST to your webhook route. Use the SDK's validator:

$validator = new \Twilio\Security\RequestValidator(config('services.twilio.token'));

$isValid = $validator->validate(
    $request->header('X-Twilio-Signature'),
    $request->fullUrl(),          // must include query string
    $request->post()              // raw POST params
);

Also register these routes in VerifyCsrfToken::$except (or the web middleware bypass) — Twilio's POSTs don't carry a CSRF token.

5. Pagination: stream() by default, never bare read()

foreach ($client->messages->stream(['to' => $number], 1000, 200) as $msg) {
    // lazy, page-at-a-time
}

read() eagerly materialises everything and will blow up memory on an account with a million messages. Manual paging via ->page() with nextPage() is only worth it if you're resuming across job batches.

6. Multi-tenant with subaccounts

$sub = $client->api->v2010->accounts->create(['friendlyName' => $tenant->name]);
$tenantClient = new \Twilio\Rest\Client($sub->sid, $sub->authToken);

Persist subaccount SIDs/tokens per tenant and instantiate a scoped client — never filter by AccountSid on a shared client.

7. Testing without hitting Twilio

The SDK uses CurlClient, not Guzzle, so Http::fake() does not intercept it. Inject a custom HTTP client implementing Twilio\Http\Client (see advanced-examples/custom-http-client.md) and bind it in the test container to return canned JSON. Alternatively use Twilio's own test credentials (AC... with the magic test token) locally.

Gotchas and Tips

Log level leaks your auth token. setLogLevel('debug') (or TWILIO_LOG_LEVEL=debug) dumps request headers, including the Basic-auth header containing your token. Only enable it in local environments, and never with TWILIO_LOG_LEVEL set in a .env that reaches production.

Signature validation fails behind a load balancer. The validator signs the exact URL Twilio called. If you're behind a proxy, $request->fullUrl() may report http:// instead of https://, or the wrong host. Fix with TrustProxies and URL::forceScheme('https'), and confirm the URL Twilio logged in the console matches exactly.

lastRequest / lastResponse are your first debugging stop.

$client->setLogLevel('debug');
// after a failed call:
logger()->error('twilio', [
    'status' => $client->lastResponse?->statusCode,
    'body'   => $client->lastResponse?->body,
]);

They're populated on the client instance, so log them inside the same request scope — a cached singleton means these reflect the most recent call, including calls from other code paths.

Phone numbers must be E.164. +15558675309, not (555) 867-5309 or 5558675309. If you accept user input, normalise before calling the API, or use the Lookup API ($client->lookups->v2->phoneNumbers($n)->fetch(['type' => 'carrier'])) to validate and format. Trial accounts can only message verified numbers — expect 21608 until you upgrade.

from vs messagingServiceSid. Use a Messaging Service SID rather than a hardcoded from when you have multiple numbers — you get automatic number pooling, sticky-sender behaviour, and opt-out handling. Hardcoding from is the single most common cause of "deliverability is bad."

Config-driven region/edge silently changes the hostname. Passing 'au1' plus setEdge('sydney') rewrites api.twilio.com → api.sydney.au1.twilio.com. If you set TWILIO_REGION globally and then wonder why one region's requests 404, this is why. Also note the constructor's third argument is the HTTP client, not the region — order is ($sid, $token, $httpClient, $region).

read()/stream() limits behave differently from what you'd expect. The limit argument caps total records returned; pageSize caps per-request page size (max 1000). Omitting limit means "everything," which is almost never what you want in a web request.

No idempotency keys. Retrying a messages->create() after a timeout can send a duplicate SMS. Where duplicate delivery matters, persist your own outbound record with a unique constraint before calling Twilio, and reconcile via the statusCallback webhook rather than blind retries.

The SDK classes are generated. Don't subclass Twilio\Rest\Client or patch anything under src/Twilio/Rest — it's regenerated with every release and your changes vanish. Extend at the boundaries instead: a repository/service around the client, or a custom Twilio\Http\Client.

Upgrade discipline matters. The SDK follows a modified SemVer and the generated resource layer changes shape between minor versions. Pin it, read VERSIONS.md on bump, and run your integration tests against the test credentials before shipping.

Content-Type on TwiML responses. Returning a JSON response from a route that builds TwiML is a silent failure — Twilio reads the body as XML and errors with "Invalid TwiML." Return the string with Content-Type: text/xml.

Rate limits are per-number and per-account, not per-SDK. Long messages (more than one segment) count as multiple messages against MPS. Queue and throttle at the Laravel side; the SDK will happily fire 500 concurrent requests and get you 429s.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log