laravel-whatsapp maintained by ozkanozcan
Laravel WhatsApp Notifier
A clean, zero-dependency WhatsApp Business notification channel for Laravel 10, 11, 12, and 13.
Send text messages, approved templates, images, documents, audio, and video directly from your Laravel application via the WhatsApp Business Cloud API.
Table of Contents
- Requirements
- Installation
- Step 1 — Create a Meta Developer App
- Step 2 — Get Your Phone Number ID & Access Token
- Step 3 — Configure the Package
- Basic Usage
- Laravel Notifications
- Facade Usage
- WhatsappMessage Reference
- Template Messages
- Media Messages
- Artisan Command
- Language Files
- Configuration Reference
- Error Handling
- Testing
- Changelog
- Contributing
- License
Requirements
| Dependency | Version |
|---|---|
| PHP | ^8.2 |
| Laravel | 10.x / 11.x / 12.x / 13.x |
| Meta WhatsApp Business Account | Required |
Installation
Install the package via Composer:
composer require ozkanozcan/laravel-whatsapp
Laravel's auto-discovery will register the service provider and Whatsapp facade automatically.
Publish the configuration file:
php artisan vendor:publish --tag=whatsapp-config
Publish language files (optional — required only to customise messages):
php artisan vendor:publish --tag=whatsapp-lang
Step 1 — Create a Meta Developer App
- Go to Meta for Developers and log in.
- Click My Apps → Create App.
- Select Business as the app type.
- Enter a name and click Create App.
- On the app dashboard, click Add Product and select WhatsApp.
- Click Set up on the WhatsApp product card.
Step 2 — Get Your Phone Number ID & Access Token
Phone Number ID
- In your app dashboard, navigate to WhatsApp → API Setup.
- You will see a Phone number ID under the "From" section — copy this value.
This is a numeric ID (e.g.
123456789012345), not the actual phone number.
Add it to your .env:
WHATSAPP_PHONE_NUMBER_ID=123456789012345
Access Token
For development (temporary token — expires in 24 hours):
On the API Setup page, you will find a temporary access token. Copy it for quick testing.
For production (permanent System User token — recommended):
- Go to Meta Business Manager.
- Navigate to Settings → System Users.
- Create a System User and assign the WhatsApp app with
whatsapp_business_messagingpermission. - Click Generate Token and copy the token.
Add it to your .env:
WHATSAPP_ACCESS_TOKEN=EAAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Default Recipient (optional)
Set a default phone number to receive messages when no specific recipient is configured:
WHATSAPP_TO=+905551234567
Important: Phone numbers must be in E.164 format:
+[country_code][number](e.g.+905551234567). No spaces, dashes, or parentheses.
Step 3 — Configure the Package
Your .env file should contain at minimum:
WHATSAPP_PHONE_NUMBER_ID=123456789012345
WHATSAPP_ACCESS_TOKEN=EAAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
WHATSAPP_TO=+905551234567
Verify your setup with the built-in Artisan command:
php artisan whatsapp:test
Basic Usage
Send a simple text message
use OzkanOzcan\LaravelWhatsapp\WhatsappBot;
use OzkanOzcan\LaravelWhatsapp\WhatsappMessage;
$bot = app(WhatsappBot::class);
$bot->sendMessage(
'+905551234567',
WhatsappMessage::create('Hello from Laravel! 🚀')->preview(false)
);
Send a plain string
$bot->sendMessage('+905551234567', 'Hello World!');
Laravel Notifications
This is the recommended way to use the package in Laravel applications.
1. Add routing to your notifiable model
// app/Models/User.php
use OzkanOzcan\LaravelWhatsapp\WhatsappChannel;
use OzkanOzcan\LaravelWhatsapp\WhatsappMessage;
class User extends Authenticatable
{
public function routeNotificationForWhatsapp(): ?string
{
// Return the user's WhatsApp phone number stored in the database,
// or fall back to the application-wide default.
return $this->whatsapp_phone ?? config('whatsapp.to');
}
}
2. Create a notification class
php artisan make:notification OrderShipped
// app/Notifications/OrderShipped.php
namespace App\Notifications;
use Illuminate\Notifications\Notification;
use OzkanOzcan\LaravelWhatsapp\WhatsappChannel;
use OzkanOzcan\LaravelWhatsapp\WhatsappMessage;
class OrderShipped extends Notification
{
public function __construct(private readonly Order $order) {}
public function via(object $notifiable): array
{
return [WhatsappChannel::class];
}
public function toWhatsapp(object $notifiable): WhatsappMessage
{
return WhatsappMessage::create(
"📦 *Order Shipped!*\n\n" .
"Order: #{$this->order->id}\n" .
"Customer: {$this->order->customer_name}\n" .
"Total: \${$this->order->total}"
)->preview(false);
}
}
3. Dispatch the notification
// Send to a specific user
$user->notify(new OrderShipped($order));
// Send to multiple users
Notification::send(User::all(), new OrderShipped($order));
Facade Usage
The Whatsapp facade is available after auto-discovery:
use OzkanOzcan\LaravelWhatsapp\WhatsappFacade as Whatsapp;
use OzkanOzcan\LaravelWhatsapp\WhatsappMessage;
// Send a text message
Whatsapp::sendMessage(
'+905551234567',
WhatsappMessage::create('🔔 New user registered!')->preview(false)
);
// Send a template message
Whatsapp::sendTemplate(
'+905551234567',
'order_shipped',
'tr',
[
[
'type' => 'body',
'parameters' => [
['type' => 'text', 'text' => '#1234'],
],
],
]
);
// Send an image
Whatsapp::sendImage(
'+905551234567',
'https://example.com/photo.jpg',
'Check out this photo!'
);
// Send a document
Whatsapp::sendDocument(
'+905551234567',
'https://example.com/report.pdf',
'Monthly Report',
'monthly-report-2024.pdf'
);
// Send audio
Whatsapp::sendAudio('+905551234567', 'https://example.com/voice.mp3');
// Send video
Whatsapp::sendVideo('+905551234567', 'https://example.com/video.mp4', 'Product demo');
// Raw API call
Whatsapp::request('POST', 'https://graph.facebook.com/v20.0/PHONE_ID/messages', [
'messaging_product' => 'whatsapp',
'to' => '+905551234567',
'type' => 'text',
'text' => ['body' => 'Hello!'],
]);
WhatsappMessage Reference
use OzkanOzcan\LaravelWhatsapp\WhatsappMessage;
$message = WhatsappMessage::create('Your text here')
// ── Content ──────────────────────────────────────
->text('Override text') // Set message body
->content('Alias for text()') // Alias
// ── Behaviour ────────────────────────────────────
->preview(false) // Disable link preview (default: enabled)
// ── Recipient override ───────────────────────────
->to('+905551234567') // Override recipient for this message
// ── Template mode ────────────────────────────────
->template('template_name', 'tr', $components); // Switch to template message
Template Messages
WhatsApp enforces a 24-hour customer service window: you can only send free-form text messages to users who messaged you within the last 24 hours. Outside that window, you must use a pre-approved message template.
Creating a Template
- Go to Meta Business Manager → WhatsApp Manager → Message Templates.
- Click Create Template and follow the approval process.
- Once approved, use the template name in your code.
Sending a Template Message
// Simple template with no variables
WhatsappMessage::create()->template('hello_world', 'en');
// Template with body parameters
WhatsappMessage::create()->template('order_shipped', 'tr', [
[
'type' => 'body',
'parameters' => [
['type' => 'text', 'text' => '#1234'],
['type' => 'text', 'text' => 'John Doe'],
],
],
]);
// Template with header image + body + buttons
WhatsappMessage::create()->template('promo_offer', 'en', [
[
'type' => 'header',
'parameters' => [
['type' => 'image', 'image' => ['link' => 'https://example.com/promo.jpg']],
],
],
[
'type' => 'body',
'parameters' => [
['type' => 'text', 'text' => '50%'],
],
],
[
'type' => 'button',
'sub_type' => 'quick_reply',
'index' => '0',
'parameters' => [
['type' => 'payload', 'payload' => 'PROMO_YES'],
],
],
]);
Media Messages
use OzkanOzcan\LaravelWhatsapp\WhatsappFacade as Whatsapp;
// Image
Whatsapp::sendImage('+905551234567', 'https://example.com/image.jpg', 'Optional caption');
// Document
Whatsapp::sendDocument(
'+905551234567',
'https://example.com/file.pdf',
'Invoice',
'invoice-2024-001.pdf'
);
// Audio
Whatsapp::sendAudio('+905551234567', 'https://example.com/audio.mp3');
// Video
Whatsapp::sendVideo('+905551234567', 'https://example.com/video.mp4', 'Product walkthrough');
Note: Media files must be hosted at a publicly accessible HTTPS URL. Alternatively, you can upload them to the WhatsApp Media API and use the returned media ID instead of a URL.
Artisan Command
Test your WhatsApp configuration without writing any code:
# Basic test — sends to WHATSAPP_TO from config
php artisan whatsapp:test
# Override recipient phone number
php artisan whatsapp:test --to=+905551234567
# Send a custom message
php artisan whatsapp:test --message="Hello from artisan!"
Language Files
The package ships with en and tr language files.
Publish them to customise or add new locales:
php artisan vendor:publish --tag=whatsapp-lang
Files will be placed at lang/vendor/whatsapp/{locale}/whatsapp.php.
To use translations in your own code:
trans('whatsapp::whatsapp.missing_token');
trans('whatsapp::whatsapp.api_error', ['code' => 429, 'description' => 'Too Many Requests']);
To add a new locale (e.g. German), create:
lang/vendor/whatsapp/de/whatsapp.php
and add the same keys as the en file.
Configuration Reference
After publishing the config (php artisan vendor:publish --tag=whatsapp-config), you can fine-tune config/whatsapp.php:
| Key | Default | Description |
|---|---|---|
phone_number_id |
'' |
Phone Number ID from Meta Business Platform |
access_token |
'' |
Permanent System User or temporary access token |
to |
'' |
Default recipient phone number (E.164 format) |
api_url |
Meta Graph API v20.0 | Override for custom API versions |
timeout |
30 |
HTTP read timeout (seconds) |
connect_timeout |
10 |
HTTP connect timeout (seconds) |
retry.times |
3 |
Retry count on HTTP 429 |
retry.sleep_ms |
1000 |
Delay between retries (ms) |
proxy |
null |
HTTP proxy URL |
logging |
false |
Log every sent message |
Error Handling
use OzkanOzcan\LaravelWhatsapp\Exceptions\WhatsappApiException;
use OzkanOzcan\LaravelWhatsapp\Exceptions\WhatsappChannelException;
use OzkanOzcan\LaravelWhatsapp\WhatsappFacade as Whatsapp;
use OzkanOzcan\LaravelWhatsapp\WhatsappMessage;
try {
Whatsapp::sendMessage(
'+905551234567',
WhatsappMessage::create('Hello!')->preview(false)
);
} catch (WhatsappChannelException $e) {
// Configuration error (missing token, missing phone_number_id, missing recipient)
logger()->error('WhatsApp config error: ' . $e->getMessage());
} catch (WhatsappApiException $e) {
if ($e->isRateLimit()) {
// HTTP 429 — sending too many messages
logger()->warning('WhatsApp rate limit hit.');
} elseif ($e->isInvalidToken()) {
// HTTP 401 — token is invalid or expired
logger()->error('WhatsApp token is invalid. Regenerate from Meta Business Manager.');
} elseif ($e->isInvalidPhoneNumber()) {
// Error 130472 — recipient number is not valid
logger()->warning('Invalid WhatsApp phone number.');
} elseif ($e->isPhoneNumberNotOnWhatsapp()) {
// Error 131030 — number is valid but not registered on WhatsApp
logger()->warning('Phone number is not on WhatsApp.');
} elseif ($e->isOutsideMessageWindow()) {
// Error 131047 — 24-hour window expired, use a template instead
logger()->warning('WhatsApp 24-hour window expired. Sending template instead...');
Whatsapp::sendTemplate('+905551234567', 'your_template_name', 'tr');
} else {
logger()->error("WhatsApp API [{$e->getWhatsappErrorCode()}]: {$e->getWhatsappDescription()}");
}
}
Testing
composer install
vendor/bin/phpunit
In your own application tests, you can mock WhatsappBot to avoid real API calls:
use OzkanOzcan\LaravelWhatsapp\WhatsappBot;
use OzkanOzcan\LaravelWhatsapp\WhatsappMessage;
$this->mock(WhatsappBot::class)
->shouldReceive('sendMessage')
->once()
->with('+905551234567', Mockery::type(WhatsappMessage::class))
->andReturn(['messages' => [['id' => 'wamid.test123']]]);
$user->notify(new OrderShipped($order));
Changelog
See CHANGELOG.md for a history of changes.
Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Commit your changes following Conventional Commits
- Push and open a Pull Request against
development
Please make sure all tests pass before submitting a PR.
License
MIT © Özkan Özcan — Özcan Teknoloji
See LICENSE for full details.