igedeon / laravel-whatsapp
Librería Laravel para enviar y recibir mensajes WhatsApp Cloud API (multi-número)
Installs: 33
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 0
Forks: 1
Open Issues: 0
pkg:composer/igedeon/laravel-whatsapp
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.8
- illuminate/database: ^12.28.1
- illuminate/http: ^12.28.1
- illuminate/support: ^12.28.1
Requires (Dev)
- laravel/boost: ^1.6
- laravel/pint: ^1.25
- orchestra/testbench: ^10.6.0
- pestphp/pest: ^3.8
- phpstan/phpstan: ^2.1.27
- rector/rector: ^2.2.4
This package is auto-updated.
Last update: 2025-11-10 15:25:33 UTC
README
Laravel WhatsApp Cloud API (Multi App / Multi WABA / Multi Number)
Library to integrate WhatsApp Cloud API into Laravel applications.
Features
- Multiple Meta Apps, Business Accounts (WABAs), Access Tokens, and Phone Numbers
- Centralized global webhook with multi-secret signature verification
- Persistence for contacts, messages, templates, media, access tokens, meta apps & errors
- Media upload & download (images, documents, audio, video, stickers)
- Optional queues for media download & mark-as-read jobs
- Configurable immediate mark-as-read behavior
- Interactive configuration command (
php artisan whatsapp:configure) to validate and persist Meta App, Access Token, WABA subscription & data - Template message support (Meta-approved templates)
Business Account, Meta App, Access Token & Template Capabilities
- MetaApp: Stores
meta_app_id,name,app_secret,verify_token. Supports multiple apps simultaneously. Webhook GET verification and POST signature validation now search across ALL stored apps. - AccessToken: Stores long-lived token (
access_token), metadata (whatsapp_id,name,expires_at) and links to aMetaApp. Historical tokens can be retained; sending logic uses the latest attached token per Business Account. - BusinessAccount: Represents a WhatsApp Business Account (WABA) including profile data, currency, timezone, template namespace, subscribed apps, phone numbers and templates. Access tokens are a many-to-many relation via
whatsapp_business_tokens. - Template: Stores approved message templates (name, language, category, components). Associated to Business Accounts; used for outbound template messages.
- Phone Numbers: Synced into
whatsapp_api_phone_numberswith throughput level, webhook configuration, quality rating, etc.
Models live in src/Models/MetaApp.php, AccessToken.php, BusinessAccount.php, Template.php, plus related factories and migrations.
Installation
Recommended end-to-end setup (no manual asset publishing required for basic usage):
- Install the package:
composer require igedeon/laravel-whatsapp
- Run migrations (if your application persists WhatsApp data locally):
php artisan migrate
- Configure your first Meta App / WABA with the interactive wizard:
php artisan whatsapp:configure
It will prompt for: Access Token, Meta App ID, App Secret, Verify Token, WABA ID and will:- Validate token identity (
/me) - Validate the Meta App
- Check required granular scopes
- Subscribe the WABA using
subscribe_url(APP_URL+/whatsapp/webhook) - Persist
MetaApp,AccessToken,BusinessAccountand link them via pivot
- Validate token identity (
- Start sending messages using the models (
Contact,ApiPhoneNumber,WhatsAppMessage). - Re-run
whatsapp:configureanytime to rotate tokens or add more Meta Apps.
Notes:
- Manual publishing of config/migrations is no longer required for the core flow.
- Ensure
APP_URLis correctly set before running the configuration command.
Optional: Publish or Force (only if you need local customization)
php artisan whatsapp:install --force # Overwrite published config/migrations php artisan whatsapp:install --no-config # Skip config publish php artisan whatsapp:install --no-migrations
Use these flags only if you want local copies to modify.
Webhook Security: Multi App Signature & Verification
VerifyMetaSignature loads all app_secret values from whatsapp_meta_apps and accepts a request if ANY HMAC matches the X-Hub-Signature-256 header.
Initial GET verification (hub.challenge) succeeds if the provided hub_verify_token matches any stored verify_token.
Disable middleware for local tests:
$this->withoutMiddleware(\LaravelWhatsApp\Http\Middleware\VerifyMetaSignature::class);
Signature generation in tests:
$signature = hash_hmac('sha256', $rawBody, $metaApp->app_secret);
Header:
X-Hub-Signature-256: sha256=<signature>
If none match, request is rejected (401) and logged.
Environment & Config (config/whatsapp.php)
| Variable | Required | Default | Description |
|---|---|---|---|
APP_URL |
Yes | - | Used to build subscribe_url (/whatsapp/webhook). |
WHATSAPP_GRAPH_VERSION |
No | v24.0 |
Graph API version for Cloud API calls. |
WHATSAPP_BASE_URL |
No | https://graph.facebook.com |
Graph API base URL. |
WHATSAPP_DOWNLOAD_DISK |
No | local |
Disk used for downloaded media. |
WHATSAPP_QUEUE_CONNECTION |
No | sync |
Queue connection for jobs. |
WHATSAPP_MEDIA_DOWNLOAD_QUEUE |
No | default |
Queue for DownloadMedia job. |
WHATSAPP_MARK_AS_READ_QUEUE |
No | default |
Queue for MarkAsRead job. |
WHATSAPP_MARK_MESSAGES_AS_READ_IMMEDIATELY |
No | false |
Dispatch read job immediately on inbound messages. |
Notes:
- App secrets & verify tokens now reside in DB (
whatsapp_meta_apps), not env. - Access tokens reside in DB (
whatsapp_access_tokens). Rotate viawhatsapp:configure. - At least one
ApiPhoneNumberwith a realphone_number_idis required for outbound messaging. - If only one phone number exists,
WhatsAppMessagemay auto-select it when omitted.
Minimal .env:
APP_URL="https://example.com" WHATSAPP_DOWNLOAD_DISK=public
Sending Messages (WhatsAppMessage)
Basic flow:
- Create or fetch
Contact(wa_id= number without+, with country code). - Ensure an
ApiPhoneNumberexists and is linked to aBusinessAccountwith at least oneAccessToken. - Instantiate
WhatsAppMessage, callinitMessage()with type & content. - Call
send().
Supported MessageType enum values: text, image, video, audio, document, sticker, location, contacts, button, interactive, reaction, order, template.
Generic content structure:
[ 'body' => 'Test message', // OR for media after upload: 'id' => 'MEDIA_ID' ]
Payload automatically sent:
{
"messaging_product": "whatsapp",
"to": "<wa_id>",
"type": "text|image|...",
"text|image|video|...": { }
}
Example: Send a Text Message
use LaravelWhatsApp\Models\Contact; use LaravelWhatsApp\Models\ApiPhoneNumber; use LaravelWhatsApp\Models\WhatsAppMessage; use LaravelWhatsApp\Enums\MessageType; $from = ApiPhoneNumber::first(); // or locate by phone_number_id $contact = Contact::firstOrCreate([ 'api_phone_id' => $from->id, 'wa_id' => '5215512345678', ], ['name' => 'Juan Perez']); $message = new WhatsAppMessage(); $message->initMessage( type: MessageType::TEXT, to: $contact, from: $from, contentProps: ['body' => 'Hello! This is a test message.'] ); $message->send();
Example: Send a Template Message
use LaravelWhatsApp\Services\WhatsAppMessageService; use LaravelWhatsApp\Models\Contact; use LaravelWhatsApp\Models\ApiPhoneNumber; $from = ApiPhoneNumber::first(); $contact = Contact::firstOrCreate([ 'api_phone_id' => $from->id, 'wa_id' => '5215512345678' ]); $service = app(WhatsAppMessageService::class); $components = [ [ 'type' => 'body', 'parameters' => [ ['type' => 'text', 'text' => 'Juan'], ['type' => 'text', 'text' => 'Order #1234'], ] ], [ 'type' => 'button', 'sub_type' => 'url', 'index' => 0, 'parameters' => [ ['type' => 'text', 'text' => '1234'] ] ] ]; $service->sendTemplateMessage( to: $contact, templateName: 'order_followup', languageCode: 'es_CO', components: $components );
Expected API structure:
{
"messaging_product": "whatsapp",
"to": "5215512345678",
"type": "template",
"template": {
"name": "order_followup",
"language": { "code": "es_CO" },
"components": []
}
}
Valid components: header, body, footer, button.
Media Upload & Send Image
use LaravelWhatsApp\Models\Contact; use LaravelWhatsApp\Models\ApiPhoneNumber; use LaravelWhatsApp\Models\MediaElement; use LaravelWhatsApp\Models\MessageTypes\Image; $from = ApiPhoneNumber::first(); $contact = Contact::firstOrCreate([ 'api_phone_id' => $from->id, 'wa_id' => '5215512345678' ]); $media = MediaElement::create([ 'api_phone_number_id' => $from->id, ]); $media->upload(storage_path('app/example-image.jpg')); $imageMessage = Image::createFromId( to: $contact, from: $from, mediaId: $media->wa_media_id, caption: 'Product photo' ); $imageMessage->send();
Mark Incoming Message as Read
use LaravelWhatsApp\Services\WhatsAppMessageService; use LaravelWhatsApp\Models\WhatsAppMessage; $service = app(WhatsAppMessageService::class); $incoming = WhatsAppMessage::find(123); $service->markAsRead($incoming);
Media Download Job
use LaravelWhatsApp\Jobs\DownloadMedia; use LaravelWhatsApp\Models\MediaElement; $media = MediaElement::find(55); DownloadMedia::dispatch($media); // queue from config
Event: WhatsAppMessageReceived
Fired on each inbound message. Publish config to override listener.
'listeners' => [ 'whatsapp_message_received' => \LaravelWhatsApp\Listeners\HandleWhatsAppMessageReceived::class, ];
Implement your own listener for custom logic (queueable, heavy processing, etc.).
Tests
Run Pest:
composer test
License
MIT
Laravel WhatsApp Cloud API (Multi Number)
Library to integrate WhatsApp Cloud API into Laravel applications. Features:
- Support for multiple numbers (Business Phone Numbers)
- Persistence for contacts, messages, conversations, and errors
- Download and upload of media (images, documents, audio, video, stickers)
- Optional queue handling for sending and downloading media
- Utilities to mark messages as read
Business Account and Template Capabilities
This package provides advanced support for managing WhatsApp Business Accounts and Message Templates:
-
BusinessAccount model: Allows you to represent and manage WhatsApp Business Accounts, including business profile data, currency, timezone, access tokens, and relationships to phone numbers and templates. You can synchronize business account data from Meta, access related phone numbers, and manage templates directly from your Laravel application.
-
Template model: Lets you define, store, and use WhatsApp message templates, including their name, language, category, subcategory, and components. Templates can be associated with business accounts and used for sending Meta-approved template messages.
-
Phone number management: Each business account can have multiple associated phone numbers, with support for verification status, quality rating, throughput level, and webhook configuration.
-
Sending messages and templates: You can send regular and template-based WhatsApp messages using the relationships between contacts, business accounts, phone numbers, and templates. The service layer handles payload construction and API communication.
-
Synchronization and automation: Business account and template data can be synchronized from Meta using built-in service methods, keeping your local database up to date with the latest account and template information.
-
Extensible relationships: All models are designed to be extensible, allowing you to override, customize, and add business logic as needed for your application.
Models are located in src/Models/BusinessAccount.php, src/Models/Template.php, and related files. Factories and migrations are provided for easy setup and testing.
Recommended Usage: Automatic WABA Sync
The recommended approach is to create a BusinessAccount record with your WhatsApp Business Account (whatsapp_id) and its access token. The package will then automatically retrieve and synchronize all related information from Meta:
- Phone numbers: All verified phone numbers associated with the WABA are fetched and stored locally.
- Templates: All approved message templates for the WABA are retrieved and stored.
- Business profile: Name, currency, timezone, and other metadata are kept up to date.
This is done using the getFromMeta() method on the BusinessAccount model:
$waba = BusinessAccount::create([ 'whatsapp_id' => 'YOUR_WABA_ID', 'access_token' => 'YOUR_LONG_LIVED_TOKEN', ]); $waba->getFromMeta(); // Synchronizes phone numbers, templates, and profile info
After this, you can use the package's models and relationships to send messages, manage templates, and interact with all WhatsApp resources for your business account.
Core concepts
flowchart TB
%% ====== YOUR ORGANIZATION ======
subgraph ORG["The Little Chicks Company"]
direction TB
%% ====== META APP ======
subgraph META["Meta App: WhatsApp AI"]
direction TB
CONFIG["Configures Webhook URL using verify_token and app_secret"]
end
%% ====== BACKEND ======
subgraph BACKEND["Laravel Multitenant Backend"]
direction TB
subgraph BACK_VERIFY["Verification"]
VERIFY["• Responds to Meta GET\n• Validates verify_token
• Returns hub.challenge"]
end
subgraph BACK_EVENTS["Event Reception"]
EVENTS["• Receives POST from Meta App
• Validates X-Hub-Signature-256
• Processes incoming messages"]
end
subgraph BACK_SEND["Message Sending"]
SEND["• Uses client's Access Token"]
end
end
end
%% ====== CLIENTS ======
subgraph CLIENTS["Clients"]
direction TB
subgraph CLIENT1["Portfolio: Company 1"]
direction TB
WABA1["WhatsApp Account (WABA)"]
SYSUSER1["System User (Client BM)"]
TOKEN1["Access Token"]
PHONE11["+57 3001111111 The Good Eats"]
WABA1 --> PHONE11
SYSUSER1 --> TOKEN1
end
subgraph CLIENT2["Portfolio: Company 2"]
direction TB
WABA2["WhatsApp Account (WABA)"]
SYSUSER2["System User (Client BM)"]
TOKEN2["Access Token"]
PHONE21["+57 3002222222 Desert Refreshments"]
WABA2 --> PHONE21
SYSUSER2 --> TOKEN2
end
end
%% Event flow (always through the configured App)
WABA1 -. "Generates events (messages, statuses)" .-> CONFIG
WABA2 -. "Generates events (messages, statuses)" .-> CONFIG
CONFIG --"Sends to configured Webhook" --> BACK_EVENTS
%% Initial webhook verification
CONFIG -- Occurs only once during setup --> BACK_VERIFY
%% Outgoing messaging (from Backend to Graph API)
TOKEN1 -. "Bearer token (Graph API)" .-> BACK_SEND
TOKEN2 -. "Bearer token (Graph API)" .-> BACK_SEND
BACK_SEND -. "POST /{phone_number_id}/messages" .-> WABA1
BACK_SEND -. "POST /{phone_number_id}/messages" .-> WABA2
Loading
-
Business Portfolio
The Business Portfolio is the set of companies managed in Meta Business. Here, system users are created and long-lived access tokens are obtained to send WhatsApp messages. Apps that will use the API are also linked in the portfolio. -
WhatsApp Business Accounts (WABA)
A Business Portfolio can have multiple WhatsApp Business Accounts. Each account represents a company or business unit that manages its own numbers and messages. -
Associated Phone Numbers
Each WhatsApp Business Account can have several verified phone numbers. These numbers are used to send and receive messages through the API. -
Global Webhook
The App defines a single global webhook that receives all events and messages from different numbers and accounts. This webhook is configured in Meta and acts as the entry point to process incoming messages, notifications, and errors.
Installation
composer require igedeon/laravel-whatsapp
Publish configuration and migrations (manual method):
php artisan vendor:publish --provider="LaravelWhatsApp\\WhatsAppServiceProvider" --tag=whatsapp-config php artisan vendor:publish --provider="LaravelWhatsApp\\WhatsAppServiceProvider" --tag=whatsapp-migrations php artisan migrate
Or publish everything in one step:
php artisan vendor:publish --provider="LaravelWhatsApp\\WhatsAppServiceProvider" --tag=whatsapp
Webhook Security: Signature Verification
The VerifyMetaSignature middleware validates the authenticity of received webhooks using the X-Hub-Signature-256 header and the secret configured in whatsapp.app_secret. If the signature is invalid or the secret is missing, the request will be rejected (401) or an exception will be thrown.
For local testing, you can disable the middleware using:
$this->withoutMiddleware(\LaravelWhatsApp\Http\Middleware\VerifyMetaSignature::class);
For integration tests, make sure to generate the signature using:
$signature = hash_hmac('sha256', $rawBody, config('whatsapp.app_secret'));
And send the header:
X-Hub-Signature-256: sha256=<signature>
If the secret is not configured, an exception will be thrown to prevent processing insecure webhooks.
Extending the Contact and ApiPhoneNumber Models
You can extend the Contact and ApiPhoneNumber models to add custom logic or attributes. Set the model class in the config file:
// config/whatsapp.php 'contact_model' => \App\Models\CustomContact::class, 'apiphone_model' => \App\Models\CustomApiPhoneNumber::class,
Both models can be overridden to customize relationships, validations, or methods. The package will use the configured class in all internal processes.
Example of an extended model:
namespace App\Models; use LaravelWhatsApp\Models\Contact; use LaravelWhatsApp\Models\ApiPhoneNumber; class CustomContact extends Contact { // Add your methods or properties here } class CustomApiPhoneNumber extends ApiPhoneNumber { // Add your methods or properties here }
Remember to run php artisan config:cache if you use config caching.
Quick Install Command
The package provides a command to simplify installation:
php artisan whatsapp:install # Publishes config and migrations php artisan whatsapp:install --migrate # Publishes and runs migrations php artisan whatsapp:install --force # Forces overwrite of already published files
Available flags:
--force: overwrites existing files.--no-config: does not publish the config file.--no-migrations: does not publish migrations.--migrate: runs migrations immediately.
Advanced examples:
php artisan whatsapp:install --no-config --migrate php artisan whatsapp:install --no-migrations
Environment Variables and Configuration (config/whatsapp.php)
| Variable | Required | Default | Description |
|---|---|---|---|
WHATSAPP_VERIFY_TOKEN |
Optional (Webhook only) | - | Token to validate Meta's webhook verification. Use if you expose a verification endpoint. |
WHATSAPP_APP_SECRET |
Optional | - | App Secret for webhook signature validation. |
WHATSAPP_GRAPH_VERSION |
No | v24.0 |
Graph API version used to build Cloud API URLs. Update when Meta releases new features. |
WHATSAPP_BASE_URL |
No | https://graph.facebook.com |
Base host for Graph API. Change only for testing or mocks. |
WHATSAPP_DOWNLOAD_DISK |
No | local |
Laravel disk (see filesystems.php) where downloaded files are stored. E.g., public, s3. |
WHATSAPP_QUEUE_CONNECTION |
No | sync |
Queue connection (see queue.php). E.g., redis, database, sqs. |
WHATSAPP_MEDIA_DOWNLOAD_QUEUE |
No | default |
Name of the queue for the DownloadMedia Job. |
WHATSAPP_MARK_AS_READ_QUEUE |
No | default |
Name of the queue for the MarkAsRead Job. |
WHATSAPP_EXPIRE_MEDIA_DAYS |
No | 15 |
Number of days after which uploaded media is considered expired. |
Notes:
- For outgoing messages, you need at least one record in the
whatsapp_api_phone_numberstable (see migration) with its realphone_number_idobtained from the Meta panel. - If there is only one
ApiPhoneNumberrecord, theWhatsAppMessagemodel will try to use it automatically when you callinitMessage()without passing a number.
Minimal .env example:
WHATSAPP_APP_SECRET="d33555772181cc8eda34866603d86c77" WHATSAPP_DOWNLOAD_DISK=public
Main Model for Sending: WhatsAppMessage
Basic flow to send a message:
- Create (or retrieve) the recipient
Contact(wa_idfield = number without +, with country code). - Create or retrieve the
ApiPhoneNumberyou send from (if you don't have a default configured). - Instantiate
WhatsAppMessage, initialize it withinitMessage()passing type and content. - Call
send()on the model (delegates toWhatsAppMessageService).
Supported types (enum MessageType): text, image, video, audio, document, sticker, location, contacts, button, interactive, reaction, order. Some require specific structure in content.
New supported type: template for Meta-approved template messages. Use a payload with template => [ name, language[code], components[] ].
Generic structure of content sent to the API:
[ // For text 'body' => 'Test message', // For image/video/document/audio (after uploading media) use 'id' of the media // 'id' => 'MEDIA_ID' ]
The service automatically builds the payload as:
{
"messaging_product": "whatsapp",
"to": "<wa_id>",
"type": "text|image|...",
"text|image|video|...": { ... content ... }
}
Example: Send a Text Message
use LaravelWhatsApp\Models\Contact; use LaravelWhatsApp\Models\ApiPhoneNumber; use LaravelWhatsApp\Models\WhatsAppMessage; use LaravelWhatsApp\Enums\MessageType; // 1. Get sending number $from = ApiPhoneNumber::where('phone_number_id', env('WHATSAPP_DEFAULT_API_PHONE_NUMBER_ID'))->first(); // 2. Get or create contact $contact = Contact::firstOrCreate([ 'api_phone_id' => $from->id, 'wa_id' => '5215512345678', // Destination number without '+' ], [ 'name' => 'Juan Perez' ]); // 3. Create message instance $message = new WhatsAppMessage(); $message->initMessage( type: MessageType::TEXT, to: $contact, from: $from, // Can be omitted if default is configured contentProps: [ 'body' => 'Hello! This is a test message.' ] ); // 4. Send $message->send();
Example: Send a Template Message
use LaravelWhatsApp\Services\WhatsAppMessageService; use LaravelWhatsApp\Models\Contact; use LaravelWhatsApp\Models\ApiPhoneNumber; $from = ApiPhoneNumber::first(); $contact = Contact::firstOrCreate([ 'api_phone_id' => $from->id, 'wa_id' => '5215512345678' ]); $service = app(WhatsAppMessageService::class); $components = [ [ 'type' => 'body', 'parameters' => [ ['type' => 'text', 'text' => 'Juan'], ['type' => 'text', 'text' => 'Order #1234'], ] ], [ 'type' => 'button', 'sub_type' => 'url', 'index' => 0, 'parameters' => [ ['type' => 'text', 'text' => '1234'] // token for dynamic URL ] ] ]; $service->sendTemplateMessage( to: $contact, templateName: 'order_followup', languageCode: 'es_CO', components: $components );
Expected structure sent to the Cloud API:
{
"messaging_product": "whatsapp",
"to": "5215512345678",
"type": "template",
"template": {
"name": "order_followup",
"language": { "code": "es_CO" },
"components": [
{ "type": "body", "parameters": [ {"type":"text","text":"Juan"}, {"type":"text","text":"Order #1234"} ] },
{ "type": "button", "sub_type": "url", "index": 0, "parameters": [ {"type":"text","text":"1234"} ] }
]
}
}
Valid components: header, body, footer, button. See official documentation for advanced parameters (e.g., images in header, quick_reply buttons, etc.).
Response and Storage
After sending, wa_message_id is saved in the corresponding column and the record is persisted in whatsapp_messages.
Example: Upload Media and Send an Image
To send an image, first upload it using a MediaElement, then use the media_id to create the message:
use LaravelWhatsApp\Models\Contact; use LaravelWhatsApp\Models\ApiPhoneNumber; use LaravelWhatsApp\Models\MediaElement; use LaravelWhatsApp\Models\MessageTypes\Image; $from = ApiPhoneNumber::where('phone_number_id', env('WHATSAPP_DEFAULT_API_PHONE_NUMBER_ID'))->first(); $contact = Contact::firstOrCreate([ 'api_phone_id' => $from->id, 'wa_id' => '5215512345678' ]); // Create media element and upload file $media = MediaElement::create([ 'api_phone_number_id' => $from->id, ]); $uploadResponse = $media->upload(storage_path('app/example-image.jpg')); // Create and send image message using the media ID $imageMessage = Image::createFromId( to: $contact, from: $from, mediaId: $media->wa_media_id, caption: 'Product photo' // Optional ); $imageMessage->send();
Mark an Incoming Message as Read
use LaravelWhatsApp\Services\WhatsAppMessageService; use LaravelWhatsApp\Models\WhatsAppMessage; $service = app(WhatsAppMessageService::class); $incoming = WhatsAppMessage::find(123); // Previously stored message with direction INCOMING $service->markAsRead($incoming);
Media Download
When an incoming message contains media, a record is created in whatsapp_media_elements and the DownloadMedia Job can be executed.
use igedeon\LaravelWhatsApp\Jobs\DownloadMedia; use LaravelWhatsApp\Models\MediaElement; $media = MediaElement::find(55); DownloadMedia::dispatch($media); // Uses queue configured in WHATSAPP_MEDIA_DOWNLOAD_QUEUE
The download uses the disk configured in WHATSAPP_DOWNLOAD_DISK and saves the file with a unique name.
Event: Message Reception (WhatsAppMessageReceived)
The package fires an event every time a message is received via webhook:
LaravelWhatsApp\\Events\\WhatsAppMessageReceived
Event properties:
$message(WhatsAppMessage): The message record.$media(MediaElement|null): Associated media if applicable and already downloaded.$mediaDownloaded(bool):trueif the event was fired after media download;falseif the message had no media.
Flow:
- Incoming message WITHOUT media: event is fired immediately after persisting the message.
- Incoming message WITH media:
DownloadMediais queued. Event is fired ONLY after download completes.
Default Listener
The package includes a reference listener:
LaravelWhatsApp\\Listeners\\HandleWhatsAppMessageReceived
This listener only does Log::info(...). You can replace it by publishing the package config.
Publish config if you haven't already:
php artisan vendor:publish --provider="LaravelWhatsApp\\WhatsAppServiceProvider" --tag=config
In config/whatsapp.php you'll find:
'listeners' => [ 'whatsapp_message_received' => \\LaravelWhatsApp\\Listeners\\HandleWhatsAppMessageReceived::class, ],
Use Your Own Listener
Create your class:
namespace App\\Listeners; use LaravelWhatsApp\\Events\\WhatsAppMessageReceived; class MyListener { public function handle(WhatsAppMessageReceived $event): void { if ($event->mediaDownloaded) { // Process already downloaded media } else { // Process message without media } } }
Edit config/whatsapp.php:
'listeners' => [ 'whatsapp_message_received' => App\\Listeners\\MyListener::class, ],
Listener in Queue
use Illuminate\\Contracts\\Queue\\ShouldQueue; use Illuminate\\Queue\\InteractsWithQueue; class MyListener implements ShouldQueue { use InteractsWithQueue; public function handle(WhatsAppMessageReceived $event): void { // Heavy task here } }
Make sure to configure QUEUE_CONNECTION appropriately.
Multiple Listeners
You can manually register multiple listeners (if you want separate logic) in your AppServiceProvider:
use Illuminate\\Support\\Facades\\Event; use LaravelWhatsApp\\Events\\WhatsAppMessageReceived; Event::listen(WhatsAppMessageReceived::class, [ App\\Listeners\\MyListener::class, App\\Listeners\\OtherListener::class, ]);
Tests
Add tests in tests/ and run Pest:
composer test
License
MIT