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

v1.0.0 2026-02-17 06:16 UTC

This package is auto-updated.

Last update: 2026-02-17 06:21:20 UTC


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=false and WHATSAPP_STORE_WEBHOOKS=false in 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 connected
  • getConnectedWhatsappInstances() — 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.