Twilio SMS Integration with Laravel (Complete Guide 2026)

Twilio SMS Integration with Laravel (Complete Guide 2026)
Laravel SMS Team
Laravel SMS Team
June 20, 2026

Twilio is one of the most popular SMS gateways available, offering reliable global SMS delivery, a robust API, and excellent developer tooling. This guide walks through integrating Twilio SMS with your Laravel application from start to finish.

Installing the Twilio SDK

Start by installing the official Twilio PHP SDK via Composer:

composer require twilio/sdk
---

This package provides the `Twilio\Rest\Client` class used to interact with the Twilio API.

## Configuring Twilio Credentials

Add your Twilio credentials to `.env`:

```env
TWILIO_ACCOUNT_SID=your_account_sid
TWILIO_AUTH_TOKEN=your_auth_token
TWILIO_FROM_NUMBER=+15551234567
---

Create a `config/twilio.php` configuration file:

```php
<?php

return [
    'account_sid' => env('TWILIO_ACCOUNT_SID'),
    'auth_token' => env('TWILIO_AUTH_TOKEN'),
    'from_number' => env('TWILIO_FROM_NUMBER'),
];
---

## Creating a Twilio Service Class

Build a dedicated service class to keep your Twilio logic organized:

```php
<?php

namespace App\Services;

use Twilio\Rest\Client;

class TwilioService
{
    protected Client $client;
    protected string $fromNumber;

    public function __construct()
    {
        $this->client = new Client(
            config('twilio.account_sid'),
            config('twilio.auth_token')
        );
        $this->fromNumber = config('twilio.from_number');
    }

    public function sendSms(string $to, string $message): array
    {
        $message = $this->client->messages->create($to, [
            'from' => $this->fromNumber,
            'body' => $message,
        ]);

        return [
            'sid' => $message->sid,
            'status' => $message->status,
            'to' => $message->to,
            'from' => $message->from,
        ];
    }
}
---

## Sending SMS via Twilio

Use the service anywhere in your application:

```php
use App\Services\TwilioService;

$twilio = app(TwilioService::class);
$result = $twilio->sendSms('+15559876543', 'Your verification code is: 8472');

Log::info('SMS sent', ['sid' => $result['sid']]);
---

## Twilio Notification Channel

Laravel's notification system makes SMS delivery clean and testable. Create a notification channel:

```php
<?php

namespace App\Notifications\Channels;

use App\Services\TwilioService;
use Illuminate\Notifications\Notification;

class TwilioSmsChannel
{
    public function __construct(
        protected TwilioService $twilio
    ) {}

    public function send(object $notifiable, Notification $notification): void
    {
        $message = $notification->toTwilioSms($notifiable);

        $this->twilio->sendSms(
            $message->to,
            $message->content
        );
    }
}
---

Define a notification using the channel:

```php
<?php

namespace App\Notifications;

use App\Notifications\Channels\TwilioSmsChannel;
use Illuminate\Notifications\Notification;

class WelcomeSms extends Notification
{
    public function via(object $notifiable): array
    {
        return [TwilioSmsChannel::class];
    }

    public function toTwilioSms(object $notifiable): object
    {
        return (object) [
            'to' => $notifiable->phone,
            'content' => "Welcome to our platform, {$notifiable->name}!",
        ];
    }
}
---

Register the channel in `config/app.php` or a service provider:

```php
use App\Notifications\Channels\TwilioSmsChannel;
use App\Services\TwilioService;

public function register(): void
{
    $this->app->bind(TwilioSmsChannel::class, function ($app) {
        return new TwilioSmsChannel($app->make(TwilioService::class));
    });
}
---

## Receiving SMS Webhooks

Twilio can forward inbound SMS to your Laravel application via webhooks. Create a route and controller:

```php
use App\Http\Controllers\IncomingSmsController;

Route::post('/webhooks/twilio/incoming', [IncomingSmsController::class, 'handle'])
    ->name('webhooks.twilio.incoming');
---

```php
<?php

namespace App\Http\Controllers;

use App\Models\SmsMessage;
use Illuminate\Http\Request;

class IncomingSmsController extends Controller
{
    public function handle(Request $request): \Illuminate\Http\Response
    {
        $validated = $request->validate([
            'From' => 'required|string',
            'Body' => 'required|string',
            'MessageSid' => 'required|string',
            'To' => 'required|string',
        ]);

        SmsMessage::create([
            'from' => $validated['From'],
            'to' => $validated['To'],
            'body' => $validated['Body'],
            'message_sid' => $validated['MessageSid'],
            'direction' => 'inbound',
        ]);

        return response('<Response></Response>', 200)
            ->header('Content-Type', 'text/xml');
    }
}
---

### Setting the Webhook URL in Twilio Console

1. Log into the [Twilio Console](https://console.twilio.com)
2. Navigate to **Phone Numbers > Manage > Active Numbers**
3. Select your number
4. Under **Messaging**, set **A message comes in** to your webhook URL: `https://your-app.com/webhooks/twilio/incoming`
5. Set the HTTP method to `POST`

## Verifying Twilio Webhook Signatures

Always verify incoming webhooks to ensure they're genuinely from Twilio:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Twilio\Security\RequestValidator;

class IncomingSmsController extends Controller
{
    public function handle(Request $request): \Illuminate\Http\Response
    {
        $validator = new RequestValidator(config('twilio.auth_token'));

        $signature = $request->header('X-Twilio-Signature');
        $url = $request->fullUrl();
        $params = $request->all();

        if (!$validator->validate($signature, $url, $params)) {
            abort(401, 'Invalid webhook signature');
        }

        // Process the message...
    }
}
---

You can also register middleware to handle validation globally:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Twilio\Security\RequestValidator;

class VerifyTwilioWebhook
{
    public function handle(Request $request, Closure $next): mixed
    {
        $validator = new RequestValidator(config('twilio.auth_token'));

        if (!$validator->validate(
            $request->header('X-Twilio-Signature'),
            $request->fullUrl(),
            $request->all()
        )) {
            abort(401, 'Invalid webhook signature');
        }

        return $next($request);
    }
}
---

## Delivery Status Callbacks

Twilio can send delivery receipts via a separate webhook:

```php
Route::post('/webhooks/twilio/status', [DeliveryStatusController::class, 'handle'])
    ->name('webhooks.twilio.status');
---

```php
<?php

namespace App\Http\Controllers;

use App\Models\SmsMessage;
use Illuminate\Http\Request;

class DeliveryStatusController extends Controller
{
    public function handle(Request $request): \Illuminate\Http\Response
    {
        $validated = $request->validate([
            'MessageSid' => 'required|string',
            'MessageStatus' => 'required|string',
            'ErrorCode' => 'nullable|string',
        ]);

        SmsMessage::where('message_sid', $validated['MessageSid'])
            ->update(['status' => $validated['MessageStatus']]);

        return response()->json(['status' => 'ok']);
    }
}
---

## Testing the Twilio Integration

### Unit Tests

```php
<?php

namespace Tests\Unit;

use App\Services\TwilioService;
use Mockery\MockInterface;
use Tests\TestCase;

class TwilioServiceTest extends TestCase
{
    public function test_sends_sms_successfully(): void
    {
        $this->mock(TwilioService::class, function (MockInterface $mock) {
            $mock->shouldReceive('sendSms')
                ->once()
                ->with('+15559876543', 'Test message')
                ->andReturn([
                    'sid' => 'SM123',
                    'status' => 'queued',
                    'to' => '+15559876543',
                    'from' => '+15551234567',
                ]);
        });

        $service = app(TwilioService::class);
        $result = $service->sendSms('+15559876543', 'Test message');

        $this->assertEquals('queued', $result['status']);
    }
}
---

### Feature Tests for Webhooks

```php
<?php

namespace Tests\Feature;

use App\Models\SmsMessage;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class IncomingSmsWebhookTest extends TestCase
{
    use RefreshDatabase;

    public function test_handles_incoming_sms(): void
    {
        $response = $this->postJson('/webhooks/twilio/incoming', [
            'From' => '+15559876543',
            'Body' => 'Hello, this is a test reply.',
            'MessageSid' => 'SM' . str_repeat('0', 32),
            'To' => '+15551234567',
        ]);

        $response->assertStatus(200);

        $this->assertDatabaseHas('sms_messages', [
            'from' => '+15559876543',
            'body' => 'Hello, this is a test reply.',
            'direction' => 'inbound',
        ]);
    }
}
---

## Troubleshooting Common Issues

| Issue | Likely Cause | Solution |
|-------|-------------|----------|
| "Account not authorized" | Wrong SID/token | Verify `TWILIO_ACCOUNT_SID` and `TWILIO_AUTH_TOKEN` in `.env` |
| Invalid `From` number | Unverified sender | Verify the number in Twilio Console for trial accounts |
| Webhook returns 401 | Signature validation failed | Double-check `TWILIO_AUTH_TOKEN` matches the one in your account |
| SMS not delivered | Carrier filtering | Check delivery status via webhook; consider message content |
| Trial account limits | Trial restrictions | Upgrade to a paid Twilio account |

## Related Articles

- [Building a Bulk SMS Platform with Laravel](/blog/building-bulk-sms-platform-laravel)
- [SMS Gateway Integration in Laravel (Multi-Provider Guide)](/blog/laravel-sms-gateway-integration)
- [Laravel Custom SMS Channel for Notifications](/blog/laravel-custom-sms-channel)