kstmostofa / laravel-whatsapp
Dual-backend Laravel package for WhatsApp: Meta Cloud API + bundled whatsapp-web.js sidecar. Ships a Livewire/Flux admin UI, queued jobs, webhooks with HMAC, Eloquent models, broadcasting, and CLI lifecycle commands.
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.5|^8.0
- illuminate/console: ^12.0|^13.0
- illuminate/contracts: ^12.0|^13.0
- illuminate/http: ^12.0|^13.0
- illuminate/notifications: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- symfony/process: ^7.0|^8.0
Requires (Dev)
- larastan/larastan: ^3.13
- laravel/pint: ^1.24
- livewire/flux: ^2.0
- livewire/livewire: ^3.4|^4.0
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0
Suggests
- laravel/reverb: ^1.0 β enables live UI updates (incoming messages, QR codes, ack changes) via Laravel broadcasting.
- livewire/flux: ^2.0 β the Flux UI component library the bundled UI is built on.
- livewire/livewire: ^3.4 β enables the bundled admin UI.
Provides
None
Conflicts
None
Replaces
None
README
Dual-backend WhatsApp integration for Laravel β Meta Cloud API (pure PHP) +
whatsapp-web.jssidecar (full personal-account access), with a polished Livewire admin UI and a singleWhatsApp::facade across both.
π Full documentation: kstmostofa.github.io/laravel-whatsapp
What you get
| Layer | What it does |
|---|---|
| Cloud API client | Pure PHP. Templates, media, business profile, phone-number management, several numbers as named accounts, webhooks with HMAC verification. |
| Web sidecar (bundled Node service) | whatsapp-web.js wrapped in a thin HTTP service. Personal-number QR pairing, groups and group admin, status, free-form messages anytime, contact lookup, chat history. |
| Unified facade | WhatsApp::messages()->sendTemplate(...) for Cloud, WhatsApp::web('main')->messages()->sendText(...) for sidecar. WhatsApp::send($to, $body) auto-picks. |
| Livewire + Flux UI | Drop-in admin at /whatsapp β Dashboard, Sessions+QR, Compose, Conversations (chat-bubble UI with media, reply, reactions, forward, edit/delete, search, sound, ack ticks, live updates over broadcasting), Groups with member management, Contacts, Webhooks log with replay, Status. Compose includes a template builder. Works with Tailwind, Bootstrap, or no CSS framework at all. |
| Eloquent models | Opt-in WaSession / WaMessage / WaContact / WaWebhookEvent with separate-DB support via WHATSAPP_DB_CONNECTION. |
| Background bridge | whatsapp:web:listen daemon turns sidecar SSE events into Laravel events. |
| Inbound router | WhatsApp::router()->hears('hi', fn ($m) => $m->reply('Hello!')) β keyword, regex, button and type routes with middleware, for both backends. |
| Notifications | 'whatsapp' notification channel with a WhatsAppMessage builder β text, templates, media, either backend. |
| Bulk sending | WhatsApp::bulk($numbers, $message) β queued, rate limited per sender, opt-out list, job batches. |
| Media + webhook log | Optional copies of media on your own disk before WhatsApp expires it, and a replayable log of raw webhook deliveries. |
| Testing | WhatsApp::fake() records every send from both backends, with assertSentTo() and friends. |
| Health monitoring | /whatsapp/status page + php artisan whatsapp:health [--json] for CI/monitoring. |
Quick install
composer require kstmostofa/laravel-whatsapp php artisan vendor:publish --tag=laravel-whatsapp-config php artisan vendor:publish --tag=laravel-whatsapp-migrations php artisan migrate
For the Cloud API β set in .env:
WHATSAPP_ACCESS_TOKEN=EAAG...permanent-token WHATSAPP_PHONE_NUMBER_ID=123456789012345 WHATSAPP_BUSINESS_ACCOUNT_ID=987654321098765 WHATSAPP_APP_SECRET=your-meta-app-secret WHATSAPP_VERIFY_TOKEN=any-string-you-make-up
For the Web sidecar β pair your phone:
composer require livewire/livewire livewire/flux # for the UI php artisan whatsapp:sidecar:install # one-time, ~600 MB Chrome download php artisan whatsapp:sidecar:start # boots in background php artisan whatsapp:web:listen --all & # SSE β Laravel events (run under Supervisor in prod) # Open http://your-app.test/whatsapp/sessions and click "Start" β scan QR with your phone
Quick example
use Kstmostofa\LaravelWhatsApp\Facades\WhatsApp; use Kstmostofa\LaravelWhatsApp\Jobs\SendMessage; // One-line send β picks backend by recipient shape WhatsApp::send('+9665XXXXXXXX', 'Hello from Laravel'); // β Cloud API WhatsApp::send('966512345678@c.us', 'Hello via personal number'); // β Web sidecar // Templated business message (Cloud API) WhatsApp::messages()->sendTemplate('+9665XXXXXXXX', 'order_ready', 'en_US', [ ['type' => 'body', 'parameters' => [['type' => 'text', 'text' => 'Munir']]], ]); // Personal-number flow (Web sidecar β works for any chat your paired phone can see) WhatsApp::web('main')->groups()->create('Project X', ['9665XXXXXXXX@c.us']); WhatsApp::web('main')->messages()->sendImage('9665XXXXXXXX@c.us', ['url' => 'https://β¦/photo.jpg', 'caption' => 'Hi']); // Many recipients β queued and rate limited WhatsApp::bulk($numbers, 'Store opens at 10 today')->except($optedOut)->dispatch(); // Or queue a single send SendMessage::dispatch('+9665XXXXXXXX', 'Queued hello'); // As a Laravel notification $user->notify(new OrderShipped($order)); // via() => ['whatsapp'], toWhatsApp() => WhatsAppMessage // In tests WhatsApp::fake(); WhatsApp::assertSentTo('+9665XXXXXXXX', fn ($m) => str_contains($m->body, 'shipped')); // Auto-replies β declared once in a service provider WhatsApp::router()->hears('hi|hello', fn ($m) => $m->reply('Hi! How can I help?')); // Inbound β or listen to the raw Laravel events Event::listen(\Kstmostofa\LaravelWhatsApp\Events\Web\MessageReceived::class, function ($event) { Log::info('Got message', ['from' => $event->from(), 'body' => $event->body()]); });
When to use which backend
| Feature | Cloud API | Web sidecar |
|---|---|---|
| Personal-number QR pairing | β | β |
| Send to / receive from groups | β | β |
| Status / Stories | β | β |
| Free-form messages anytime | β (templates outside 24h) | β |
| Approved business templates | β | β |
| Official, no ban risk | β | β (browser automation β ToS gray area) |
| Scalable to millions | β | β οΈ session-bound |
| No extra runtime on host | β | β (Node + Chromium) |
Most apps use both β Cloud API for transactional/template sends at scale, Web sidecar for the features Cloud API doesn't expose.
UI install paths
The admin UI under /whatsapp works on whatever your app already uses:
| Your app uses⦠| Set in .env |
What you get |
|---|---|---|
| Tailwind v4 + Vite | WHATSAPP_UI_CSS_MODE=vite (default) |
Smallest tree-shaken bundle. Add 3 @source lines to your app.css. |
| Anything else (Tailwind v3 / Bootstrap / plain CSS / nothing) | WHATSAPP_UI_CSS_MODE=standalone |
Pre-compiled CSS shipped with the package, served from /whatsapp/_assets/laravel-whatsapp.css (~40 KB gz). No npm/Tailwind needed. Loads only on /whatsapp/* pages β your main app stays untouched. |
| No UI at all | skip composer require livewire/flux |
Headless. Full access to WhatsApp:: facade, Events, Jobs, webhook receiver, all CLI commands. |
Full setup details + dark mode + screenshots β docs site.
Artisan commands
| Command | Purpose |
|---|---|
whatsapp:sidecar:install |
Clone whatsapp-web.js, npm ci, download Chromium |
whatsapp:sidecar:start [--foreground] / :stop / :status |
Lifecycle of the Node process. --foreground is for Supervisor / systemd |
whatsapp:web:listen [session] [--all] |
Long-running: sidecar SSE β Laravel events. --all covers every session in one process |
whatsapp:health [--json] [--exit-code] |
Health probe β pipe into cron / monitoring |
whatsapp:contacts:sync [session] [--all] |
Copy Web contacts into wa_contacts β safe to schedule |
whatsapp:web:sync [session] [--chats=20] [--limit=50] [--chat=β¦] |
Import recent chat history into wa_messages |
whatsapp:webhooks:replay [idβ¦] [--failed] |
Re-dispatch logged webhook deliveries |
whatsapp:media:prune [--days=] |
Delete stored media older than the retention window |
Production checklist
- Set strong
WHATSAPP_WEB_TOKEN(shared secret between PHP and sidecar) - Set
WHATSAPP_APP_SECRET(HMAC for Cloud webhook signatures) - Wrap
/whatsapp/*routes in your own auth middleware:config/laravel-whatsapp.phpβui.middleware - Run
whatsapp:web:listen --allunder Supervisor / systemd - Optional: isolate WA data on a separate DB connection (
WHATSAPP_DB_CONNECTION=whatsapp) - Optional: enable broadcasting (
WHATSAPP_BROADCAST=true) + run Laravel Reverb for instant UI updates
Detailed deployment guide β docs/production.
Roadmap
- Cloud API: messages / templates / media / business profile / phone numbers / webhooks
- Web sidecar: full whatsapp-web.js surface (text, media, groups, contacts, status)
- Livewire + Flux admin UI with dark mode, 3 CSS install paths
- Eloquent persistence with per-package DB connection
- Health page + CLI command + cached snapshots
- Avatar + media proxy with server-side caching
- Bubble actions: edit, delete-for-me, delete-for-everyone, "you deleted this message" placeholder
- Bulk send job with rate limiting
- Laravel notification channel +
WhatsApp::fake() - Chat history import
- Larastan (level 6) + Pint in CI
- Native broadcasting integration with
Echochannel listeners - Template builder in Compose
Status
- 162 tests passing, 470 assertions (testbench + mocked Guzzle + Livewire smoke tests), plus 59 sidecar tests (
npm testinsidecar/) - Compatible with Laravel 12 / 13 β CI runs the suite on PHP 8.2, 8.3 and 8.4 with Laravel 12, and PHP 8.4 with Laravel 13, plus Pint and Larastan (level 6)
- PHP minimum: 8.2 on Laravel 12, 8.4 on Laravel 13 (Symfony 8 transitive)
- Laravel 11 support was dropped in 1.1.0: it is past end of life and has unpatched security advisories
- Verified end-to-end in Laravel 12 for the 1.0 feature set: QR pairing, inbound webhook β DB β UI bubble, edit, delete, sound, attachments, dark-mode toggle. Features added in 1.1 are covered by the automated tests above
License
MIT. See LICENSE.
Contributing
Issues + PRs welcome at github.com/kstmostofa/laravel-whatsapp. Before submitting, run:
composer check # Pint style check, Larastan (level 6), and PHPUnit composer format # fix code style cd sidecar && npm test # sidecar unit tests (no npm install needed)
CI runs the same checks, plus the test suite across PHP 8.2β8.4 and Laravel 12 / 13.
If you change a Blade view, or StandaloneCssCoverageTest fails after a Flux update, rebuild the standalone stylesheet and commit dist/laravel-whatsapp.css:
LARAVEL_WHATSAPP_BUILD_APP=/path/to/laravel-app-with-flux ./dist-src/build.sh