izzuddinmohsin / laravel-whatsapp
A Laravel connector for WhatsApp via Evolution API. Simple, clean, and framework-agnostic within Laravel ecosystem.
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/izzuddinmohsin/laravel-whatsapp
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
- illuminate/events: ^10.0|^11.0|^12.0
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^8.0|^9.0|^10.0
- pestphp/pest: ^2.0|^3.0
README
A simple, clean Laravel package to connect with WhatsApp via Evolution API. Works with Blade, Livewire, Inertia.js — any Laravel stack.
Disclaimer: This package uses unofficial WhatsApp APIs via Evolution API. WhatsApp/Meta may ban numbers that use unofficial APIs. Use at your own risk and consider the official WhatsApp Cloud API for production business use.
Features
- Send messages: text, image, video, audio, document, location, contact, buttons, list
- Receive messages via webhooks with event-driven architecture
- Full instance management (create, delete, restart, connect, logout)
- Database storage for instances, messages, and webhook logs
- Queue support for async message sending and webhook processing
- Laravel Notifications channel integration
- Multi-instance support for multiple WhatsApp accounts
- Cleanup command for old data retention
- Reusable trait for any service class
- Configurable storage (can run lightweight without database)
- Retry logic on API calls
Requirements
- PHP 8.1+
- Laravel 10, 11, or 12
- A running Evolution API instance
Installation
composer require izzuddinmohsin/laravel-whatsapp
Publish the config:
php artisan vendor:publish --tag=whatsapp-config
Publish and run migrations (optional but recommended):
php artisan vendor:publish --tag=whatsapp-migrations php artisan migrate
Add to your .env:
EVOLUTION_API_URL=http://localhost:8080 EVOLUTION_API_KEY=your-api-key EVOLUTION_INSTANCE=your-instance-name
Note: If you don't need database storage, set
WHATSAPP_STORE_MESSAGES=falseandWHATSAPP_STORE_WEBHOOKS=falsein your.env. You can skip the migration publish step entirely.
Quick Start
Sending Messages
use IzzuddinMohsin\LaravelWhatsApp\Facades\WhatsApp; // Text message WhatsApp::to('60123456789')->text('Hello from Laravel!'); // Image with caption WhatsApp::to('60123456789')->image('https://example.com/photo.jpg', 'Check this out!'); // Document WhatsApp::to('60123456789')->document('https://example.com/invoice.pdf', 'invoice.pdf', 'Your invoice'); // Audio WhatsApp::to('60123456789')->audio('https://example.com/audio.mp3'); // Video WhatsApp::to('60123456789')->video('https://example.com/video.mp4', 'Watch this'); // Location WhatsApp::to('60123456789')->location(3.1390, 101.6869, 'KL Tower', 'Kuala Lumpur'); // Contact card WhatsApp::to('60123456789')->contact('Ali Ahmad', '60198765432', 'Syarikat ABC');
Interactive Messages
// Buttons WhatsApp::to('60123456789')->buttons( title: 'Choose an option', description: 'What would you like to do?', buttons: [ ['id' => 'order', 'text' => 'Track Order'], ['id' => 'support', 'text' => 'Get Support'], ['id' => 'info', 'text' => 'More Info'], ] ); // List message WhatsApp::to('60123456789')->list( title: 'Our Menu', description: 'Choose from our selections', buttonText: 'View Menu', sections: [ [ 'title' => 'Drinks', 'rows' => [ ['title' => 'Teh Tarik', 'description' => 'RM 3.00', 'rowId' => 'teh_tarik'], ['title' => 'Kopi O', 'description' => 'RM 2.50', 'rowId' => 'kopi_o'], ], ], ] );
Instance Management
Basic Operations
// Check connection status $info = WhatsApp::instanceInfo(); echo $info->status; // 'open', 'close', etc. echo $info->phoneNumber; echo $info->isConnected(); // true/false // Get QR code for pairing $qrCode = WhatsApp::qrCode(); // base64 image // Check if number exists on WhatsApp $exists = WhatsApp::isOnWhatsApp('60123456789'); // true/false // Logout WhatsApp::logout();
Full Instance CRUD
// Create a new instance on Evolution API WhatsApp::createInstance([ 'name' => 'marketing', 'reject_call' => true, 'msg_call' => 'Sorry, we cannot take calls.', 'groups_ignore' => true, 'always_online' => true, 'read_messages' => true, ]); // Delete instance WhatsApp::instance('marketing')->deleteInstance(); // Restart instance WhatsApp::instance('marketing')->restartInstance(); // Fetch all instances from Evolution API $instances = WhatsApp::fetchInstances(); // Get connected instances from database $connected = WhatsApp::getConnectedInstances();
Webhook Management
// Set webhook URL for current instance WhatsApp::setWebhook('https://your-app.com/whatsapp/webhook'); // Set with specific events WhatsApp::setWebhook('https://your-app.com/whatsapp/webhook', [ 'MESSAGES_UPSERT', 'MESSAGES_UPDATE', 'CONNECTION_UPDATE', ]); // Get current webhook configuration $config = WhatsApp::getWebhook();
Additional API Methods
// Get profile picture $url = WhatsApp::getProfilePicture('60123456789'); // Fetch messages from a chat $messages = WhatsApp::fetchMessages('60123456789@s.whatsapp.net', 50); // Update instance settings WhatsApp::updateSettings([ 'rejectCall' => true, 'alwaysOnline' => true, ]);
Multi-Instance Support
// Switch instance at runtime WhatsApp::instance('marketing')->to('60123456789')->text('Promo!'); WhatsApp::instance('support')->to('60123456789')->text('Ticket resolved'); // Each call is immutable — won't affect other calls $marketing = WhatsApp::instance('marketing'); $support = WhatsApp::instance('support'); $marketing->to('60123456789')->text('From marketing'); $support->to('60123456789')->text('From support');
Receiving Messages (Webhooks)
The package automatically registers a webhook route at /whatsapp/webhook (configurable).
1. Point Evolution API to your webhook
In your Evolution API instance settings, set the webhook URL to:
https://your-app.com/whatsapp/webhook
Or set it programmatically:
WhatsApp::setWebhook('https://your-app.com/whatsapp/webhook');
2. Listen to events
Create a listener in your app:
// app/Listeners/HandleWhatsAppMessage.php namespace App\Listeners; use IzzuddinMohsin\LaravelWhatsApp\Events\WhatsAppMessageReceived; use IzzuddinMohsin\LaravelWhatsApp\Facades\WhatsApp; class HandleWhatsAppMessage { public function handle(WhatsAppMessageReceived $event): void { $message = $event->message; // Access message properties echo $message->from; // '60123456789' echo $message->text; // 'Hello!' echo $message->type; // 'conversation', 'imageMessage', etc. echo $message->pushName; // Sender's name echo $message->isGroup; // true/false echo $message->isText(); // true/false echo $message->isMedia(); // true/false // Auto-reply example if ($message->isText() && str_contains(strtolower($message->text), 'hello')) { WhatsApp::to($message->from)->text("Hi {$message->pushName}!"); } } }
3. Register the listener
// app/Providers/EventServiceProvider.php (Laravel 10) // or bootstrap/app.php (Laravel 11+) use IzzuddinMohsin\LaravelWhatsApp\Events\WhatsAppMessageReceived; use IzzuddinMohsin\LaravelWhatsApp\Events\WhatsAppMessageStatusUpdated; use IzzuddinMohsin\LaravelWhatsApp\Events\WhatsAppConnectionStatusChanged; use IzzuddinMohsin\LaravelWhatsApp\Events\InstanceConnected; use IzzuddinMohsin\LaravelWhatsApp\Events\InstanceDisconnected; use IzzuddinMohsin\LaravelWhatsApp\Events\QrCodeUpdated; // Laravel 11+ Event::listen(WhatsAppMessageReceived::class, HandleWhatsAppMessage::class); Event::listen(WhatsAppMessageStatusUpdated::class, HandleStatusUpdate::class); Event::listen(WhatsAppConnectionStatusChanged::class, HandleConnectionChange::class); Event::listen(InstanceConnected::class, HandleInstanceConnected::class); Event::listen(InstanceDisconnected::class, HandleInstanceDisconnected::class); Event::listen(QrCodeUpdated::class, HandleQrCodeUpdated::class);
Available Events
| Event | Description | Payload |
|---|---|---|
WhatsAppMessageReceived |
New message received | $message (IncomingMessage), $rawPayload |
WhatsAppMessageStatusUpdated |
Status changed (sent/delivered/read) | $messageId, $status, $from, $instanceName |
WhatsAppConnectionStatusChanged |
Instance connected/disconnected | $status, $instanceName, $rawPayload |
InstanceConnected |
Instance successfully connected | $instance (WhatsappInstance model) |
InstanceDisconnected |
Instance disconnected | $instance, $reason |
QrCodeUpdated |
New QR code generated | $instance, $qrCodeData |
Queue Support
Enable async message sending and webhook processing:
WHATSAPP_QUEUE_ENABLED=true WHATSAPP_QUEUE_CONNECTION=redis WHATSAPP_QUEUE_NAME=whatsapp
When enabled:
- Outgoing messages are dispatched via
SendMessageJob(3 retries, 10s backoff) - Incoming webhooks are processed via
ProcessWebhookJob(3 retries, 30s backoff) - Messages return immediately with
status: 'queued'
Run the queue worker:
php artisan queue:work --queue=whatsapp
Database Storage
When migrations are published and run, the package stores:
whatsapp_instances table
Tracks all WhatsApp instances with their connection status, settings, and QR codes.
whatsapp_messages table
Logs all incoming and outgoing messages with status tracking (sent_at, delivered_at, read_at).
whatsapp_webhooks table
Logs all webhook events with processing status and error tracking.
Disabling Storage
For lightweight usage without database:
WHATSAPP_STORE_MESSAGES=false WHATSAPP_STORE_WEBHOOKS=false
Using the Trait
Add WhatsApp messaging to any class:
use IzzuddinMohsin\LaravelWhatsApp\Concerns\CanSendWhatsappMessage; class OrderService { use CanSendWhatsappMessage; public function notifyCustomer(Order $order): void { $this->sendWhatsappText( to: $order->customer_phone, message: "Your order #{$order->id} is ready!", ); $this->sendWhatsappDocument( to: $order->customer_phone, url: $order->invoice_url, filename: "invoice-{$order->id}.pdf", caption: 'Here is your invoice', ); } public function sendPromo(): void { // Send via specific instance $this->sendWhatsappImage( to: '60123456789', url: 'https://example.com/promo.jpg', caption: 'Special offer!', instanceName: 'marketing', ); } }
Available trait methods:
sendWhatsappText($to, $message, $instanceName)sendWhatsappImage($to, $url, $caption, $instanceName)sendWhatsappVideo($to, $url, $caption, $instanceName)sendWhatsappAudio($to, $url, $instanceName)sendWhatsappDocument($to, $url, $filename, $caption, $instanceName)sendWhatsappLocation($to, $lat, $lng, $name, $address, $instanceName)sendWhatsappContact($to, $fullName, $phone, $org, $instanceName)hasWhatsappInstance()— check if any instance is connectedgetConnectedWhatsappInstances()— get all connected instances
Laravel Notifications
Use WhatsApp as a Laravel notification channel:
// app/Notifications/OrderShipped.php namespace App\Notifications; use Illuminate\Notifications\Notification; use IzzuddinMohsin\LaravelWhatsApp\Notifications\WhatsAppChannel; use IzzuddinMohsin\LaravelWhatsApp\Notifications\WhatsAppMessage; class OrderShipped extends Notification { public function via($notifiable): array { return [WhatsAppChannel::class]; } public function toWhatsApp($notifiable): WhatsAppMessage { return WhatsAppMessage::create() ->text("Hi {$notifiable->name}! Your order #{$this->order->id} has been shipped!"); } }
Notification Message Types
// Text WhatsAppMessage::create()->text('Hello!'); // Image WhatsAppMessage::create()->image('https://example.com/photo.jpg', 'Caption'); // Video WhatsAppMessage::create()->video('https://example.com/video.mp4', 'Caption'); // Audio WhatsAppMessage::create()->audio('https://example.com/audio.mp3'); // Document WhatsAppMessage::create()->document('https://example.com/file.pdf', 'file.pdf', 'Your document'); // Location WhatsAppMessage::create()->location(3.1390, 101.6869, 'KL Tower', 'Kuala Lumpur'); // Contact WhatsAppMessage::create()->contact('Ali Ahmad', '60198765432', 'Syarikat ABC');
Add the route to your User model (or any notifiable):
// app/Models/User.php public function routeNotificationForWhatsapp(): ?string { return $this->phone_number; // e.g., '60123456789' }
Send it:
$user->notify(new OrderShipped($order));
Data Cleanup
Clean up old webhooks and messages:
# Use config defaults (30 days webhooks, 90 days messages) php artisan whatsapp:cleanup # Custom retention php artisan whatsapp:cleanup --webhooks-days=7 --messages-days=30 # Preview what would be deleted php artisan whatsapp:cleanup --dry-run
Schedule it in your routes/console.php or app/Console/Kernel.php:
// Laravel 11+ Schedule::command('whatsapp:cleanup')->daily();
Configuration
Full configuration reference:
# Driver WHATSAPP_DRIVER=evolution # Evolution API Connection EVOLUTION_API_URL=http://localhost:8080 EVOLUTION_API_KEY=your-api-key EVOLUTION_INSTANCE=default EVOLUTION_API_TIMEOUT=30 EVOLUTION_API_RETRY=3 EVOLUTION_API_RETRY_SLEEP=100 # Webhook WHATSAPP_WEBHOOK_URL= # Auto-detected from APP_URL if empty WHATSAPP_WEBHOOK_PATH=/whatsapp/webhook WHATSAPP_WEBHOOK_SECRET= # Optional webhook secret WHATSAPP_WEBHOOK_BASE64=true # Instance Defaults WHATSAPP_DEFAULT_INSTANCE= # Default instance ID WHATSAPP_INTEGRATION=WHATSAPP-BAILEYS WHATSAPP_REJECT_CALL=false WHATSAPP_MSG_CALL= WHATSAPP_GROUPS_IGNORE=false WHATSAPP_ALWAYS_ONLINE=false WHATSAPP_READ_MESSAGES=false WHATSAPP_READ_STATUS=false WHATSAPP_SYNC_FULL_HISTORY=false # Queue WHATSAPP_QUEUE_ENABLED=false WHATSAPP_QUEUE_CONNECTION= # null = default connection WHATSAPP_QUEUE_NAME=whatsapp # Storage WHATSAPP_STORE_MESSAGES=true WHATSAPP_STORE_WEBHOOKS=true # Cleanup Retention WHATSAPP_CLEANUP_WEBHOOKS_DAYS=30 WHATSAPP_CLEANUP_MESSAGES_DAYS=90 # Media WHATSAPP_MEDIA_DISK=public WHATSAPP_MEDIA_DIRECTORY=whatsapp-media WHATSAPP_MEDIA_MAX_SIZE=16384 # 16MB in KB # Cache WHATSAPP_CACHE_ENABLED=true WHATSAPP_CACHE_TTL=60
Enums
The package provides enums for type-safe usage:
use IzzuddinMohsin\LaravelWhatsApp\Enums\MessageTypeEnum; use IzzuddinMohsin\LaravelWhatsApp\Enums\MessageStatusEnum; use IzzuddinMohsin\LaravelWhatsApp\Enums\MessageDirectionEnum; use IzzuddinMohsin\LaravelWhatsApp\Enums\ConnectionStatusEnum; // Message types MessageTypeEnum::TEXT; // 'text' MessageTypeEnum::IMAGE; // 'image' MessageTypeEnum::VIDEO; // 'video' MessageTypeEnum::AUDIO; // 'audio' MessageTypeEnum::DOCUMENT; // 'document' MessageTypeEnum::LOCATION; // 'location' MessageTypeEnum::CONTACT; // 'contact' // Message statuses MessageStatusEnum::PENDING; // 'pending' MessageStatusEnum::SENT; // 'sent' MessageStatusEnum::DELIVERED; // 'delivered' MessageStatusEnum::READ; // 'read' MessageStatusEnum::FAILED; // 'failed' // Directions MessageDirectionEnum::INCOMING; // 'incoming' MessageDirectionEnum::OUTGOING; // 'outgoing' // Connection status ConnectionStatusEnum::OPEN; // 'open' ConnectionStatusEnum::CLOSE; // 'close' ConnectionStatusEnum::CONNECTING; // 'connecting'
Models
Access stored data via Eloquent models:
use IzzuddinMohsin\LaravelWhatsApp\Models\WhatsappInstance; use IzzuddinMohsin\LaravelWhatsApp\Models\WhatsappMessage; use IzzuddinMohsin\LaravelWhatsApp\Models\WhatsappWebhook; // Instances $instance = WhatsappInstance::where('name', 'default')->first(); $instance->isConnected(); // true/false $instance->messages; // HasMany relationship $instance->webhooks; // HasMany relationship // Messages $messages = WhatsappMessage::where('phone', '60123456789') ->where('direction', 'incoming') ->latest() ->get(); // Update message status $message->markAsSent(); $message->markAsDelivered(); $message->markAsRead(); // Webhooks $pending = WhatsappWebhook::pending()->get(); $failed = WhatsappWebhook::failed()->get(); $messageEvents = WhatsappWebhook::byEvent('messages.upsert')->get();
Error Handling
use IzzuddinMohsin\LaravelWhatsApp\Exceptions\WhatsAppException; try { WhatsApp::to('60123456789')->text('Hello!'); } catch (WhatsAppException $e) { Log::error('WhatsApp send failed: ' . $e->getMessage()); }
Architecture
Your App
-> WhatsApp Facade
-> WhatsAppManager (fluent API, queue support, storage)
-> EvolutionApiDriver (HTTP client, retry logic)
-> Evolution API Server -> WhatsApp
Evolution API Server
-> POST /whatsapp/webhook
-> WebhookController (verify secret, store webhook)
-> ProcessWebhookJob (queue or sync)
-> Store messages, update statuses
-> Fire Laravel events
-> Your listeners
Roadmap
This package is built with a driver-based architecture, making it easy to swap or add new WhatsApp providers without changing your application code.
Planned Drivers
| Driver | Status | Description |
|---|---|---|
evolution |
Available | Evolution API (unofficial, self-hosted) |
meta |
Planned | WhatsApp Cloud API (official, Meta-hosted) |
Switching Drivers (Future)
When the Meta driver is available, switching will be as simple as:
# .env WHATSAPP_DRIVER=meta META_WHATSAPP_TOKEN=your-token META_PHONE_NUMBER_ID=your-phone-id META_BUSINESS_ID=your-business-id META_API_VERSION=v21.0
Your application code stays the same — WhatsApp::to('...')->text('...') works regardless of which driver is active.
Planned Features
- Meta WhatsApp Cloud API driver
- Template message support (for Meta Cloud API)
- Media upload to local storage with base64 conversion
- Rate limiting for outgoing messages
- Message scheduling (send later)
- Conversation/thread tracking
- Bulk messaging with progress tracking
Contributions for any of these are welcome!
Testing
composer test
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for details.
Security
If you discover a security vulnerability, please email izzuddinmohsin.work@gmail.com instead of opening an issue.
License
The MIT License (MIT). Please see License File for more information.