How to Create a Laravel Notification Channel for WhatsApp with Twilio

November 22, 2019
Written by
Chimezie Enyinnaya
Contributor
Opinions expressed by Twilio contributors are their own

Laravel makes it easy to send notifications in your PHP application. Out of the box it supports various channels such as mail, SMS, Slack, and database. What if we want to send notifications to a different channel aside these ones, say WhatsApp? In this tutorial, I’ll be showing you how to implement WhatsApp notifications in your Laravel app using the Twilio API for WhatsApp.

Tools Needed to Complete This Tutorial

In order to follow this tutorial, you will need the following:

What We’ll Be Building

For the purpose of this tutorial, we’ll be using the concept of an ordering system. For the sake of brevity, we won’t build a fully-featured ordering system, just the part where the system sends out notifications.

Creating a New Laravel Application

Let’s get started by creating a new Laravel application. We’ll be using the Laravel installer mentioned in the prerequisites. Run the following command in your console:

$ laravel new laravel-whatsapp-notification
$ cd laravel-whatsapp-notification

Once the application is created, we need to install the NPM dependencies (because Bootstrap comes pre-packed as an NPM dependency):

$ npm install

Once the dependencies are installed, run:

$ npm run dev

Adding Authentication

Next, let’s add authentication. Starting from Laravel 6, the authentication scaffolding has been extracted into a separate package, so we need to install it with Composer:

$ composer require laravel/ui --dev

Then we can run to get the authentication scaffolding:

$ php artisan ui vue --auth

Since WhatsApp makes use of phone numbers, we need a way to collect users phone numbers in our application. One way to achieve this, is to collect users phone numbers at the point of registration. Let’s update the registration form to include the phone number field. Add the following code inside register.blade.php immediately after the email address field:

// resources/views/auth/register.blade.php

<div class="form-group row">
  <label for="phone_number" class="col-md-4 col-form-label text-md-right">{{ __('Phone Number') }}</label>
  <div class="col-md-6">
    <input id="phone_number" type="text" class="form-control @error('phone_number') is-invalid @enderror" name="phone_number" value="{{ old('phone_number') }}" required autocomplete="phone_number">
    @error('phone_number')
      <span class="invalid-feedback" role="alert">
        <strong>{{ $message }}</strong>
      </span>
    @enderror
  </div>
</div>

Note: Users phone numbers must be in E.164 format.

Laravel registration form

Next, let’s update both the validator() and create() method of the RegisterController as shown below:

// app/Http/Controllers/Auth/RegisterController.php

protected function validator(array $data)
{
  return Validator::make($data, [
    'name' => ['required', 'string', 'max:255'],
    'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
    'phone_number' => ['required', 'string', 'unique:users'],
    'password' => ['required', 'string', 'min:8', 'confirmed'],
  ]);
}


protected function create(array $data)
{
  return User::create([
    'name' => $data['name'],
    'email' => $data['email'],
    'phone_number' => $data['phone_number'],
    'password' => Hash::make($data['password']),
  ]);
}

We added some validation rules for the phone number field and persist the phone number to the database along with the other data that’s being collected.

Lastly, we need to add the phone number field to the list of fields that can be mass assigned inside the User model:

// app/User.php

protected $fillable = [
  'name', 'email', 'password', 'phone_number'
];

Creating the Models and Migrations

We need to also update the users' table migration file that comes with the default Laravel application by adding a new field for the phone number:

// database/migrations/2014_10_12_000000_create_users_table.php

$table->string('phone_number')->unique();

Next, let’s create an Order model and its corresponding migration file:

$ php artisan make:model Order -m

Then, let’s update the migration file as below:

// database/migrations/2019_11_14_123551_create_orders_table.php

Schema::create('orders', function (Blueprint $table) {
  $table->bigIncrements('id');
  $table->string('name');
  $table->integer('amount');
  $table->timestamps();
});

To keep things simple, the orders table will just contain the fields: id, name, amount, and the timestamps.

Before we run the migration let’s make sure we have our database set up. To keep things simple and straightforward, we’ll be making use of SQLite for our database. Update the .env file as below:

// .env

DB_CONNECTION=sqlite
DB_DATABASE=/absolute/path/to/database.sqlite

By default, Laravel will look for a database.sqlite file inside the database directory. To create it:

$ touch database/database.sqlite

Now, we can run the migration:

$ php artisan migrate

Implementing Order Placement

Like I said earlier, we won’t be building a fully-featured ordering system. We’ll only be implementing a mock order. For that, we need to create a factory class for randomly generating fake data. Run the command below:

$ php artisan make:factory OrderFactory

This will create a new OrderFactory.php file inside the database/factories directory. Replace the content of the file with the following code:

// database/factories/OrderFactory.php

<?php

use App\Order;
use Faker\Generator as Faker;

$factory->define(Order::class, function (Faker $faker) {
    return [
        'name' => $faker->sentence,
        'amount' => $faker->numberBetween($min = 1000, $max = 9000),
    ];
});

Next, let’s create the UI for our order placement. We’ll add it inside home.blade.php immediately below the “You're logged in!” text:

// resources/views/home.blade.php

<form action="{{ route('order') }}" method="post">
    @csrf
    <button type="submit" class="btn btn-primary">Place Order</button>
</form>

Nothing fancy. Just a simple form with a button, which will submit to a named route called order.

Laravel order button

Next, we need to create the route:

// routes/web.php

Route::post('/order', 'OrderController@store')->name('order')->middleware('auth');

Lastly, let’s create the OrderController the route points to:

$ php artisan make:controller OrderController

Then we’ll add the following code in it:

// app/Http/Controllers/OrderController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class OrderController extends Controller
{
  public function store(Request $request)
  {
    $order = factory(\App\Order::class)->create();


    return redirect()->route('home')->with('status', 'Order Placed!');
  }
}

Here we are using the factory we created earlier to create a new order upon submission. Then we redirect back to the page with a success status message.

Setting Up Twilio Sandbox for WhatsApp

To send messages with WhatsApp in production, we have to wait for WhatsApp to formally approve our account. But, that doesn't mean we have to wait to start building. With Twilio Sandbox for WhatsApp, we can test our app in a developer environment. To make use of the Sandbox we need to first connect to it by sending a WhatsApp message from our device to a number that will be made available to us. To get started, follow the instructions on the Learn tab. You’ll be able to send any message after your account is connected.

Twilio WhatsApp console

Once the sandbox is set up grab your Twilio credentials. We will use these for setting up access to the Twilio PHP SDK. Head over to your Twilio dashboard and copy both your ACCOUNT SID and AUTH TOKEN. Take note of the phone number that is assigned to your sandbox as that’s the number you’ll be sending your notifications from.

Next, let’s create environment variables to hold these details inside .env:

// .env

TWILIO_AUTH_SID=YOUR_TWILIO_AUTH_SID
TWILIO_AUTH_TOKEN=YOUR_TWILIO_AUTH_TOKEN
TWILIO_WHATSAPP_FROM=YOUR_TWILIO_WHATSAPP_FROM

Create a dedicated service configuration for Twilio. Add the code below inside config/services.php:

// config/services.php

'twilio' => [
  'sid' => env('TWILIO_AUTH_SID'),
  'token' => env('TWILIO_AUTH_TOKEN'),
  'whatsapp_from' => env('TWILIO_WHATSAPP_FROM')
],

Overview of Sending Notifications with Twilio API for WhatsApp

Before we dive into sending notifications with the Twilio API for WhatsApp, let’s take a moment to understand how it works. There are two types of messages that can be sent using the Twilio API for WhatsApp: one-way messaging and two-way messaging. Sending notifications falls under one-way messaging and sending conversational messages falls under two-way messaging. To be able to send notification messages, we need to make use of what is called the WhatsApp message template.

A WhatsApp message template is a message format that can be reused to message users once they have opted-in and given your app permission to send them messages. Template messages are used to help maintain high-quality content and avoid spam in the ecosystem.

Template messages use placeholder values that can be replaced with dynamic content when the message is sent. Out of the box, the Twilio WhatsApp sandbox comes with three pre-provisioned templates:

  • Your {{1}} code is {{2}}
  • Your {{1}} appointment is coming up on {{2}}
  • Your {{1}} order of {{2}} has shipped and should be delivered on {{3}}. Details : {{4}}

For the purpose of this tutorial, we’ll be making use of the third template. To learn more and templates and how you can create your own, check out the docs.

Creating a Custom Notification Channel

As it stands, our users can place an order. Now, we need to send them a notification to let them know that their order has been processed. Since we want to send this notification through WhatsApp, we need to create a custom notification channel in Laravel. For this, we’ll be needing two files: WhatsAppChannel.php and WhatsAppMessage.php. The former will contain the implementation for sending a message (notification), while the latter will contain the API for composing a message.

Let’s start by creating a new directory called Channels in our app folder. Inside it, we’ll create another directory called Messages. Inside Messages we’ll create WhatsAppMessage.php and paste the following code in it:

// app/Channels/Messages/WhatsAppMessage.php

<?php


namespace App\Channels\Messages;

class WhatsAppMessage
{
  public $content;
  
  public function content($content)
  {
    $this->content = $content;

    return $this;
  }
}

This is a simple class that contains a $content property and setter (content method) for setting the value of the content property. Whatever data is passed to the method will be the content of the notification we’ll be sending.

Next, let’s create the WhatsAppChannel.php file directly inside the Channels directory and paste the following code in it:

// app/Channels/WhatsAppChannel.php

<?php
namespace App\Channels;

use Illuminate\Notifications\Notification;
use Twilio\Rest\Client;

class WhatsAppChannel
{
    public function send($notifiable, Notification $notification)
    {
        $message = $notification->toWhatsApp($notifiable);


        $to = $notifiable->routeNotificationFor('WhatsApp');
        $from = config('services.twilio.whatsapp_from');


        $twilio = new Client(config('services.twilio.sid'), config('services.twilio.token'));


        return $twilio->messages->create('whatsapp:' . $to, [
            "from" => 'whatsapp:' . $from,
            "body" => $message->content
        ]);
    }
}

Every notification channel class must have a send() method, which will contain the actual implementation for sending the notification. It must accept two arguments: a $notifiable and a $notification. The former is a trait that any model sending notifications needs to use, while the latter is an instance of the actual notification (which we’ll create shortly).

Using the notification instance, we call the toWhatsApp() method (which we’ll create later on) and pass it to $notifiable. Here $message will be an instance of WhatsAppMessage and will contain the notification we want to send.

To get the user’s phone number we want to send the notification to, we call a routeNotificationFor() method on the $notification trait. Under the hood, Laravel will look for a routeNotificationForWhatsApp() method on the model using the trait.

Lastly, we create a new instance of the Twilio PHP SDK which we use to send the notification. Notice we have to prefix both the phone numbers (to and from) with WhatsApp.

Since we are making use of the Twilio PHP SDK, we need to make sure we have it installed:

$ composer require twilio/sdk

Remember I said we need to have a routeNotificationForWhatsApp() method on the model using the Notifiable trait? In our case, that is the User model:

// app/User.php

public function routeNotificationForWhatsApp()
{
  return $this->phone_number;
}

We are simply returning the phone number field, since that is the field holding our users’ phone numbers.

Now, we can create a notification that will make use of our custom notification channel:

$ php artisan make:notification OrderProcessed

This will create a new OrderProcessed.php file inside app/Notifications. Open it up and replace it content with:

// app/Notifications/OrderProcessed.php

<?php

namespace App\Notifications;

use App\Channels\Messages\WhatsAppMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use App\Channels\WhatsAppChannel;
use App\Order;


class OrderProcessed extends Notification
{
  use Queueable;


  public $order;
  
  public function __construct(Order $order)
  {
    $this->order = $order;
  }
  
  public function via($notifiable)
  {
    return [WhatsAppChannel::class];
  }
  
  public function toWhatsApp($notifiable)
  {
    $orderUrl = url("/orders/{$this->order->id}");
    $company = 'Acme';
    $deliveryDate = $this->order->created_at->addDays(4)->toFormattedDateString();


    return (new WhatsAppMessage)
        ->content("Your {$company} order of {$this->order->name} has shipped and should be delivered on {$deliveryDate}. Details: {$orderUrl}");
  }
}

Inside the via() method, we are telling Laravel that we want to make use of our custom channel. Inside the toWhatsApp() method is where we will compose the message to send. We create and return a new instance of WhatsAppMessage while setting the content of the notification by calling the content() method. Notice we are making use of the WhatsApp message template we talked about earlier.

Finally, let’s update the store method of the OrderController.php to include the code that will fire the notification:

// app/Http/Controllers/OrderController.php

// add this at the top of the class
use App\Notifications\OrderProcessed;


public function store(Request $request)
{
  $order = factory(\App\Order::class)->create();


  $request->user()->notify(new OrderProcessed($order));


  return redirect()->route('home')->with('status', 'Order Placed!');
}

Testing Our Application

Now, let’s test what we’ve been building so far. First, let’s make sure our app is running:

$ php artisan serve

Create an account with the same phone number you added to your Sandbox. You should get a message similar to the one below once you click the “Place Order” button.

WhatsApp order notification

Conclusion

In this tutorial, we have learned how to send notifications in a Laravel application through WhatsApp. You are not limited to sending just notifications, as you can send conversational messages to WhatsApp using the Twilio API for WhatsApp.

You can find the complete source for this tutorial on GitHub.

Chimezie Enyinnaya is a Software Developer and Instructor

Website: https://tutstack.io

Twitter: https://twitter.com/ammezie

Email: meziemichael@gmail.com

GitHub: https://github.com/ammezie