WhatsApp integration in Laravel has always forced developers to pick a side.
Meta's WhatsApp Business Cloud API is the official, stable, billed-per-conversation route. It's reliable and policy-safe — but it can only message users who messaged you first inside the last 24 hours. Outside that window, sends are restricted to pre-approved template messages. There's no support for groups, no Status / Stories, no free-form outbound conversations.
whatsapp-web.js fills those gaps. It drives a headless Chromium that speaks the same WebSocket protocol as the WhatsApp Web app, so it can do anything a logged-in user can. But it lives in Node.js, sits outside Meta's blessed API, and the existing PHP bridges tend to be fragile or coupled to abandoned forks.
Most Laravel WhatsApp packages on Packagist pick one of those two worlds. A new release tries to unify them.
Introducing kstmostofa/laravel-whatsapp
kstmostofa/laravel-whatsapp is a Laravel package (supporting Laravel 11, 12, and 13) that ships both backends behind a single facade, plus a Livewire/Flux admin UI that mounts at /whatsapp out of the box.
The three components:
Pure-PHP Cloud API client — Guzzle-based. Covers templates, media, business profile, phone-number management, plus a webhook receiver that verifies Meta's HMAC SHA-256 signature.
Bundled ~300-line Node sidecar wrapping
whatsapp-web.js— runs on the same server as Laravel, exposes a small HTTP API, gives access to the features Cloud API doesn't.Drop-in Livewire + Flux admin UI at
/whatsapp— dashboard, QR pairing, compose, conversations (chat-bubble interface), groups, contacts, webhooks log, health page. Light + dark mode included.
The architecture splits responsibilities cleanly:
┌─────────────────────────────────────────────────────┐
│ Laravel app │
│ │
│ WhatsApp::send('+9665XXX', 'Hi') │
│ │ │
│ ├──→ Cloud API (PHP, Guzzle) → graph.fb.com │
│ │ │
│ └──→ WebClient (HTTP) → 127.0.0.1:3000 │
│ │ │
└───────────────────────────────────────┼─────────────┘
▼
┌───────────────────────┐
│ Sidecar (Node, ~300) │
│ whatsapp-web.js × N │
│ Puppeteer / Chromium │
└───────────────────────┘
▲
│ WebSocket
▼
WhatsApp serversThe facade autoroutes based on the recipient's shape: a phone number goes to the Cloud API, a WhatsApp internal ID (…@c.us for users, …@g.us for groups) goes to the Web sidecar. An explicit backend override is available when needed:
use Kstmostofa\LaravelWhatsApp\Facades\WhatsApp;
// Routes to Cloud API (phone number recipient)
WhatsApp::send('+9665XXXXXXXX', 'Hello');
// Routes to Web sidecar (WhatsApp internal ID)
WhatsApp::send('9665XXXXXXXX@c.us', 'Hello from a personal number');
// Force a specific backend + session
WhatsApp::send('+9665XXXXXXXX', 'Force Web', 'web', 'main');For anything beyond plain text — templates, media, groups, status — the corresponding resource is exposed directly:
// Cloud API: parameterised template
WhatsApp::messages()->sendTemplate('+9665XXX', 'order_ready', 'en_US', [
['type' => 'body', 'parameters' => [['type' => 'text', 'text' => 'Munir']]],
]);
// Web sidecar: create a group and message it
$group = WhatsApp::web('main')->groups()->create('Project X', [
'9665XXX@c.us', '9665YYY@c.us',
]);
WhatsApp::web('main')
->messages()
->sendText($group['gid']['_serialized'], 'Welcome aboard');
// Web sidecar: post to Status / Stories (Cloud API can't do this at all)
WhatsApp::web('main')->status()->sendText('New feature live', [
'backgroundColor' => '#075E54',
'font' => 2,
]);The dual-backend design lets developers use the right tool for each job — Cloud API for high-volume transactional sends (OTPs, order updates, broadcasts to consenting recipients), and the Web sidecar for personal-number scenarios that no other PHP package handles cleanly.
Inbound messages — two paths, one event API
Cloud API delivers events via webhooks. The package registers POST /webhooks/whatsapp automatically, verifies Meta's X-Hub-Signature-256 HMAC, and fires typed Laravel events:
use Kstmostofa\LaravelWhatsApp\Events\MessageReceived;
Event::listen(MessageReceived::class, function ($event) {
Log::info('Inbound from '.$event->from().': '.$event->text());
});The Web sidecar emits a Server-Sent Events stream per session. An artisan command consumes the stream and dispatches matching events on the PHP side:
php artisan whatsapp:web:listen mainThe event class shapes mirror each other (Events\Web\MessageReceived, MessageAck, QrGenerated, Disconnected, SessionReady), so listeners look nearly identical regardless of source.
Set WHATSAPP_PERSIST_INCOMING=true and a built-in listener writes everything to a wa_messages table — including delivery acknowledgements, which the UI uses to render proper single, double, and blue-tick badges.
The bundled admin UI
Most WhatsApp packages stop at the SDK layer. This one ships the admin UI too.
/whatsapp auto-registers (gateable via your own auth middleware in production) and includes:
Dashboard — sidecar health, message volume by direction, recent activity table
Sessions — start new sessions, scan QRs via a modal that auto-updates through
qr → authenticating → ready, stop or destroy sessionsCompose — send text, image, document, or template; backend selectable or auto-routed
Conversations — WhatsApp-style chat-bubble interface with lazy-loaded avatars, inline image previews, audio player, paperclip attachments, edit, delete-for-everyone, ack ticks, sound notifications, smart auto-scroll. The message query is an indexed
(session_id, chat_id) ORDER BY id DESC LIMIT Nscan, so it stays fast even with millions of rowsGroups, Contacts, Webhooks log, Health — the standard admin views
Built on Livewire and Flux UI with three CSS install paths so it works in any host application:
Headless mode is fully supported — uninstalling livewire/livewire makes the UI disappear, while the facade, events, jobs, and webhook receiver keep working unchanged.
Notable implementation details
A few decisions that aren't obvious from the README:
A ~300 LOC sidecar instead of an open-wa/openwa dependency. The entire surface area needed is a thin HTTP wrapper around whatsapp-web.js. Keeping the sidecar small enough to read in one sitting makes it auditable, easy to patch when WhatsApp Web's internals shift, and free of transitive dependencies on abandoned forks.
Detached process spawn that doesn't leak file descriptors. Spawning a long-running Node process from PHP is its own discipline. The sidecar uses a nohup-inside-subshell wrapper with stdout/stderr redirected to log files and stdin closed, so shell_exec returns immediately rather than blocking on shared pipes. On macOS, nohup forks rather than execs — meaning the shell's $! is the wrapper PID, not Node's. The sidecar writes its real PID to a file via SIDECAR_PID_FILE on boot, and PHP polls for it before declaring success.
SSE consumed via cURL streaming, not fopen. PHP's fopen('http://…') buffers indefinite responses, which means it can't consume an SSE stream. The listener uses curl_setopt($ch, CURLOPT_WRITEFUNCTION, ...) to process chunks as they arrive, with exponential backoff on dropped streams.
Separate database connection is a real, fully-wired option. Set WHATSAPP_DB_CONNECTION=whatsapp and WaSession, WaMessage, WaContact resolve the connection at runtime via a UsesWhatsAppConnection trait. Different driver too — your main app on MySQL, WhatsApp data on Postgres. Migrations honor the same configuration, calling Schema::connection(...) at migration time.
Webhook fails closed. If signature verification is enabled (the default) but WHATSAPP_APP_SECRET is unset, the middleware returns 503 service misconfigured and logs an error — never silently accepts unverified payloads.
5-minute install
composer require kstmostofa/laravel-whatsapp
php artisan vendor:publish --tag=laravel-whatsapp-config
php artisan vendor:publish --tag=laravel-whatsapp-migrations
php artisan migrateThen take one of the two paths:
Cloud API — set Meta credentials in .env:
WHATSAPP_PHONE_NUMBER_ID=...
WHATSAPP_BUSINESS_ACCOUNT_ID=...
WHATSAPP_ACCESS_TOKEN=... # permanent System User token
WHATSAPP_APP_SECRET=...
WHATSAPP_VERIFY_TOKEN=... Point Meta's webhook UI at https://yourapp.com/webhooks/whatsapp using the same verify token.
Web sidecar — install and start the Node service:
WHATSAPP_WEB_ENABLED=true
WHATSAPP_WEB_TOKEN=$(openssl rand -hex 24)
php artisan whatsapp:sidecar:install
php artisan whatsapp:sidecar:startVisit /whatsapp/sessions, click Start, scan the QR with a phone. Pairing typically completes within 10 seconds.
The full reference lives at https://kstmostofa.github.io/laravel-whatsapp/.
Compatibility
The package's composer.json declares support for:
Laravel 11.x, 12.x, 13.x
Livewire 3.x, 4.x
PHP 8.2+ (Laravel 11/12) or 8.4+ (Laravel 13, due to a transitive Symfony 8 dependency)
The CI matrix runs phpunit across the cartesian product on each push. 46 tests / 133 assertions cover the service provider, message router, recipient parsing, Cloud client + error handling, Web client, webhook flow, models, and Livewire smoke tests.
Roadmap
Planned in upcoming releases:
Template builder UI (currently API-only)
Bulk-send wizard with rate-limit visualization
Multi-tenant gates baked into the UI middleware
A Laravel Echo channel helper for the live-update events
Caveats worth knowing
The Web sidecar drives WhatsApp via browser automation. Meta's Terms of Service technically prohibit this. Account bans happen if volume is pushed or automated patterns get aggressive. For production-grade marketing or transactional sends, the Cloud API is the safer route. The sidecar shines for human-driven, personal-number scenarios.
Each Web session keeps a Puppeteer Chromium instance running. Budget 1–2 GB RAM per active session in production. Running 50 sessions on a 1 GB VPS will OOM.
The bundled UI defaults to
webmiddleware only. That's fine for local development, but the service provider logs a warning at boot whenAPP_ENV=productionand no auth middleware is configured. Wrap the routes in['web', 'auth', 'can:manage-whatsapp']or similar before going live.
Resources
Packagist: https://packagist.org/packages/kstmostofa/laravel-whatsapp
Documentation: https://kstmostofa.github.io/laravel-whatsapp/
Issues / feature requests: https://github.com/kstmostofa/laravel-whatsapp/issues
Installation:
composer require kstmostofa/laravel-whatsappkstmostofa/laravel-whatsapp is maintained by Md Mostafijur Rahman and released under the MIT license. Contributions and bug reports are welcome via the GitHub repository.









