gowa-php / laravel
Laravel integration for GOWA — GowaClient Facade, Notification Channel, Webhook routing, and Eloquent models
Requires
- php: ^8.3
- gowa-php/sdk: ^1.5
- illuminate/contracts: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.95
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- pestphp/pest: ^3.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-11 18:05:01 UTC
README
gowa-php/laravel
Laravel integration for GOWA — Facade, Notification Channel, Webhook routing, and Eloquent models
🇧🇷 Para ler a documentação em Português, acesse README.pt.md.
⚡ Acknowledgments & Dependencies
This package interacts with the Go backend ecosystem created by the open-source community:
- whatsmeow — The underlying Go library created by Tulir Asokan that reverse-engineers the WhatsApp Web Multi-Device WebSocket protocol and Signal encryption.
- go-whatsapp-web-multidevice (GOWA) — The lightweight REST API wrapper created by Aldino Kemal exposing
whatsmeowover HTTP and Webhooks. - gowa-php/sdk — The underlying PHP SDK for GOWA.
Requirements
- PHP >= 8.3
- Laravel 10, 11, 12, or 13
gowa-php/sdk^1.5- A running instance of the GOWA (go-whatsapp-web-multidevice) REST API server (
GOWA_BASE_URL)
Installation
composer require gowa-php/laravel
The service provider and Gowa facade are registered automatically via Laravel's package discovery.
Publish the config file:
php artisan vendor:publish --tag=gowa-config
Publish and run the migrations (optional if using Driver-Only mode):
php artisan vendor:publish --tag=gowa-migrations php artisan migrate
Configuration
GOWA_BASE_URL=https://gowa.yourcompany.com GOWA_USERNAME=admin GOWA_PASSWORD=secret GOWA_TIMEOUT=15 GOWA_STATELESS=false GOWA_DEFAULT_DEVICE_ID=my-default-device-uuid GOWA_WEBHOOK_SECRET=your_hmac_secret GOWA_WEBHOOK_PATH=webhooks/gowa GOWA_AUTO_SYNC_INBOUND=true GOWA_AUTO_SYNC_OUTBOUND=true GOWA_WEBHOOK_RECORD_CALLS=true GOWA_LOG_WEBHOOKS=false
GOWA_WEBHOOK_SECRETis required to receive webhooks. The GOWA server signs every delivery withX-Hub-Signature-256, using the device's own secret when it has one and its globalWHATSAPP_WEBHOOK_SECRETotherwise. This package mirrors that order:gowa_instances.webhook_secretfirst, thengowa.webhook.secret. With no secret on either side the signature cannot be verified and the request is rejected with403-- an unsigned webhook is never accepted.
Usage
Fluent Messaging (Recommended)
Send messages with an expressive fluent interface:
use Gowa\Laravel\Facades\Gowa; // Send plain text Gowa::to('5511999998888')->text('Hello from Laravel!')->send(); // Specify a sender device (optional; defaults to config or first connected instance) Gowa::from('device-id')->to('5511999998888')->text('Hello from specific instance!')->send(); // Reply / quote a previous message Gowa::to($phone)->replyTo($messageId)->text('Replying to your message...')->send();
Media & Laravel Storage Attachments
Seamlessly attach media from URLs, local paths, streams, or Laravel Storage Disks (S3, MinIO, Public, Local):
// Image (from URL or local file path) Gowa::to($phone)->image('https://example.com/banner.png', 'Promotional offer!')->send(); // Document directly from Laravel Storage Disk (e.g. Amazon S3) via streaming Gowa::to($phone) ->disk('s3') ->document('invoices/2026/inv_1092.pdf', filename: 'Invoice.pdf', caption: 'Your monthly invoice') ->send(); // You can also pass the disk inline Gowa::to($phone)->image('banners/promo.jpg', caption: 'Summer sale', disk: 'public')->send(); // Video & Audio Gowa::to($phone)->video('videos/demo.mp4', 'Product Demo')->send(); Gowa::to($phone)->audio('podcasts/episode1.mp3')->send(); // Voice note / PTT (Push-To-Talk audio) Gowa::to($phone)->voice('voice_notes/memo.ogg')->send(); // Sticker (WebP) Gowa::to($phone)->sticker('stickers/thumbs_up.webp')->send();
Rich Messages: Locations, Contacts, Polls, Links & Reactions
// Geolocation (Latitude & Longitude) Gowa::to($phone)->location(-23.55052, -46.633309)->send(); // Contact vCard Gowa::to($phone)->contact('Jane Doe', '5511988887777')->send(); // Interactive Poll Gowa::to($phone) ->poll('What is the best meeting time?', ['Morning (9am)', 'Afternoon (2pm)', 'Evening (6pm)'], maxSelections: 1) ->send(); // Link with rich preview Gowa::to($phone)->link('https://antigravity.google', 'Antigravity AI Platform')->send(); // Emoji Reaction to a message Gowa::to($phone)->reaction($messageId, '🔥')->send();
Direct Message Actions
// Mark message as read / played Gowa::to($phone)->markRead($messageId, withTyping: false); Gowa::to($phone)->markPlayed($audioMessageId); // Revoke (delete for everyone) or Star Gowa::to($phone)->revoke($messageId); Gowa::to($phone)->star($messageId);
Notification Channel
Implement toGowa() on your notification and routeNotificationForGowa() on your notifiable. GowaMessage supports all fluent media and storage methods:
use Gowa\Laravel\Notifications\GowaChannel; use Gowa\Laravel\Notifications\GowaMessage; use Illuminate\Notifications\Notification; class OrderInvoiceNotification extends Notification { public function __construct(public Order $order) {} public function via(mixed $notifiable): array { return [GowaChannel::class]; } public function toGowa(mixed $notifiable): GowaMessage { return GowaMessage::create() ->disk('s3') ->document("invoices/{$this->order->id}.pdf", filename: 'Invoice.pdf', caption: "Here is your invoice for order #{$this->order->id}!"); } } // On your User model: public function routeNotificationForGowa(): string { return $this->phone_number; // e.g. '5511999998888' }
Webhook Events & Automatic Database Sync
The package registers a POST route at {GOWA_WEBHOOK_PATH}/{deviceId} automatically. It verifies the HMAC signature using the webhook_secret stored on the GowaInstance model, then dispatches typed Laravel events.
Automatic Database Sync (GOWA_WEBHOOK_AUTO_SYNC=true)
When enabled (default), the package automatically:
- Creates / updates
GowaConversationwith the sender details. - Inserts incoming messages into
GowaMessage(with directioninboundand statusdelivered). - Updates
GowaMessagedelivery and read receipts (delivered_at,read_at, statusread) upon receiving ack webhooks. - Records outbound messages when using
Gowa::to()->send(). - Listeners implement
ShouldQueue— processing executes asynchronously on your configured Laravel queue worker or synchronously (sync). - Every accepted delivery is written to
gowa_webhook_callsby the controller before the events are dispatched, with the request URL and headers (minusauthorization,cookieandproxy-authorization). If one of the package's sync listeners throws, that row is flipped toprocessed = falseand the exception is stored, so a failure is visible in the table and not only infailed_jobs.
Custom Event Listeners
You can also listen to typed events in your application:
use Gowa\Laravel\Webhook\Events\GowaMessageReceived; use Gowa\Laravel\Webhook\Events\GowaMessageAck; use Gowa\Laravel\Webhook\Events\GowaWebhookReceived; // Any incoming webhook (before type-specific events) Event::listen(GowaWebhookReceived::class, function (GowaWebhookReceived $event) { // $event->deviceId is always the device the delivery was addressed to. // $event->instanceId is null when that device has no row in gowa_instances. Log::info('GOWA webhook', [ 'event' => $event->event->value, 'device' => $event->deviceId, 'instance' => $event->instanceId, ]); }); // Incoming message Event::listen(GowaMessageReceived::class, function (GowaMessageReceived $event) { $message = $event->message; // Gowa\Sdk\Webhook\Dto\IncomingMessage // process custom logic or trigger AI agent... }); // Message read/delivered acknowledgement Event::listen(GowaMessageAck::class, function (GowaMessageAck $event) { $ack = $event->ack; // Gowa\Sdk\Webhook\Dto\IncomingAck }); // Tip: If your listener catches an exception and wants to flag the webhook audit row as failed: use Gowa\Laravel\Models\GowaWebhookCall; try { // custom processing... } catch (\Throwable $e) { GowaWebhookCall::markFailed($event->webhookCallId, $e); throw $e; }
Stateless Mode (Driver-Only / No Migrations)
If your application already has its own database structure, or if you prefer to use this package purely as a WhatsApp API client and webhook event dispatcher without creating package database tables, enable Stateless mode:
GOWA_STATELESS=true GOWA_DEFAULT_DEVICE_ID=your-default-device-uuid GOWA_WEBHOOK_SECRET=your_hmac_secret
When GOWA_STATELESS=true is enabled:
- No migrations loaded: The package will not register or run its migrations (
gowa_instances,gowa_conversations,gowa_messages,gowa_webhook_calls). - No package DB queries: Outbound sending (
Gowa::to()) and Notifications (GowaChannel) execute purely via HTTP without querying or updating package tables. - Stateless Webhooks: Inbound webhook requests are verified directly using your global
GOWA_WEBHOOK_SECRET. - Event-Driven Custom Persistence: The package dispatches standard Laravel events (
GowaMessageReceived,GowaMessageAck,GowaWebhookReceived), allowing you to handle persistence directly in your application models.
Handling Inbound Media, Documents, Audio & Location
When a user sends an image, video, voice note, document, or location, GowaMessageReceived provides convenient helper methods:
use App\Models\ChatMessage; use Gowa\Laravel\Facades\Gowa; use Gowa\Laravel\Webhook\Events\GowaMessageReceived; use Gowa\Sdk\Dto\EventPayload; use Gowa\Sdk\Dto\LiveLocationPayload; use Gowa\Sdk\Dto\OrderPayload; use Gowa\Sdk\Dto\PollPayload; use Gowa\Sdk\Webhook\Dto\IncomingMessage; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Log; Event::listen(GowaMessageReceived::class, function (GowaMessageReceived $event) { // Basic message information $type = $event->message->type; // 'text', 'image', 'video', 'audio', 'document', 'location' $body = $event->message->body; // Text body or media caption $sender = $event->message->phone; // Phone number without suffix $senderName = $event->message->senderName; // Media Handling (Images, Videos, Audio, Documents, Stickers) if ($event->isMedia()) { $mediaUrl = $event->mediaUrl(); // Public download URL from GOWA server $mediaMime = $event->mediaMime(); // e.g. 'application/pdf', 'image/jpeg' $filename = $event->mediaFilename(); // Original filename (for documents) $isVoice = $event->isVoiceNote(); // true for WhatsApp voice notes (PTT) // Optionally download the file directly to your application's Storage if ($mediaUrl) { $extension = pathinfo($filename ?? '', PATHINFO_EXTENSION); $safeExt = preg_match('/^[a-zA-Z0-9]{1,10}$/', $extension) ? ".{$extension}" : ''; $destination = storage_path("app/whatsapp/{$event->message->id}{$safeExt}"); Gowa::downloadMedia($mediaUrl, $destination); } } // Location Handling (Static GPS or Realtime Live Location) if ($event->isLocation()) { $dto = $event->locationCoordinates(); // Gowa\Sdk\Dto\LocationPayload ($dto->latitude, $dto->longitude) if ($event->isLiveLocation()) { $live = $event->liveLocation(); // Gowa\Sdk\Dto\LiveLocationPayload ($live->speedInMps, $live->accuracyInMeters) } } // Structured Polls, Events, and Orders (SDK v1.5.0 typed DTOs) if ($event->isPoll()) { $poll = $event->poll(); // Gowa\Sdk\Dto\PollPayload ($poll->question, $poll->options) } if ($event->isEvent()) { $calEvent = $event->eventData(); // Gowa\Sdk\Dto\EventPayload ($calEvent->name, $calEvent->startTime) } if ($event->isOrder()) { $order = $event->order(); // Gowa\Sdk\Dto\OrderPayload ($order->orderTitle, $order->itemCount) } // Fluent Message Routing (SDK v1.5.0): $event->message ->whenText(fn(string $text) => ChatMessage::create(['body' => $text])) ->whenLiveLocation(fn(LiveLocationPayload $loc) => Delivery::updatePosition($loc->latitude, $loc->longitude)) ->whenPoll(fn(PollPayload $poll) => Log::info("Poll: {$poll->question}")) ->whenEvent(fn(EventPayload $ev) => Log::info("Event: {$ev->name}")) ->whenOrder(fn(OrderPayload $ord) => Log::info("Order: {$ord->orderTitle}")) ->otherwise(fn(IncomingMessage $msg) => Log::info("Other type: {$msg->type}")); // Save directly to your application's own Eloquent model: ChatMessage::create([ 'device_id' => $event->deviceId, 'message_id' => $event->message->id, 'sender' => $sender, 'sender_name' => $senderName, 'type' => $type, 'body' => $body, 'media_url' => $event->mediaUrl(), 'media_mime' => $event->mediaMime(), 'latitude' => $event->locationCoordinates()?->latitude, 'longitude' => $event->locationCoordinates()?->longitude, ]); });
You can also check whether stateless mode is active via Gowa::isStateless().
Eloquent Models
use Gowa\Laravel\Models\GowaInstance; // Find instance and verify it's connected $instance = GowaInstance::where('device_id', 'my-device')->firstOrFail(); $instance->status->isConnected(); // bool // Build a GowaClient scoped to this instance $client = $instance->client(); $client->sendText('5511999998888', 'Hello!'); // Access conversations and messages $instance->conversations()->with('messages')->get();
Swapping Models
Point the config to your own model classes (useful when adding custom columns or relations):
// config/gowa.php 'models' => [ 'instance' => App\Models\WhatsappInstance::class, 'conversation' => App\Models\WhatsappConversation::class, 'message' => App\Models\WhatsappMessage::class, 'webhook_call' => App\Models\WhatsappWebhookCall::class, ],
Teams Support
Enable multi-tenant scoping by adding a team_id column to migrations:
GOWA_TEAMS=true GOWA_TEAM_FOREIGN_KEY=team_id
Publish and re-run migrations after enabling this setting.
Upgrading to v1.1.0
Breaking Changes & Migration Steps
GOWA_WEBHOOK_SECRETis now mandatory: All webhook deliveries must be signed via HMAC-SHA256. If a device has no device-specificwebhook_secret, it falls back to the globalgowa.webhook.secret. Unsigned webhook requests will receive403 Forbidden(or404if the device is not registered in the database and no global secret is set).- Publish the Audit Table Migration: A new migration (
000004_create_gowa_webhook_calls_table.php) records all webhook deliveries and failure states. Run:php artisan vendor:publish --tag=gowa-migrations php artisan migrate
- Webhook Event Signatures: The event constructors (
GowaWebhookReceived,GowaMessageReceived,GowaMessageAck,GowaMessageReaction) now accept?int $instanceIdandstring $deviceId. If your application instantiates these events manually in tests, update calls to provide the$deviceId. - Configuration Update: Re-publish configuration if updating from v1.0:
php artisan vendor:publish --tag=gowa-config --force
Running Tests
By default, tests run using SQLite in-memory without requiring any external services:
composer test # or explicitly: composer test:sqlite
To run tests against MySQL and PostgreSQL using Docker:
# Start MySQL and PostgreSQL containers docker compose up -d # Run test suites against specific database drivers composer test:mysql composer test:pgsql
⚠️ Disclaimer & Terms of Use
This software is an open-source library created for educational, research, and testing laboratory purposes.
- Third-Party Terms of Service: Users of this library are solely responsible for complying with WhatsApp's Terms of Service, Meta's Platform Policies, and the terms of any third-party services utilized.
- Automated Messaging & Policy Compliance: Automated or unauthorized messaging may violate platform terms. Users must ensure strict compliance with applicable privacy laws (e.g., GDPR, LGPD), user consent requirements, and platform guidelines.
- No Warranty & Liability: This software is provided "as is", without warranty of any kind, express or implied. The authors and contributors assume no liability for any account bans, data loss, service interruptions, or misuse of this library.
License
MIT — see LICENSE.