amsaid / larawhat
A Laravel package for the WhatsApp Cloud API — send messages, manage templates, handle webhooks, and more.
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.0
- illuminate/contracts: ^11.0 || ^12.0 || ^13.0
- illuminate/http: ^11.0 || ^12.0 || ^13.0
- illuminate/routing: ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^11.0 || ^12.0 || ^13.0
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0 || ^10.0 || ^11.0
- phpunit/phpunit: ^11.0
This package is auto-updated.
Last update: 2026-04-24 10:17:32 UTC
README
A Laravel package for the WhatsApp Cloud API (Meta). Send messages, manage templates, handle incoming webhooks, and control your business profile — all with a clean, fluent Laravel interface.
Requirements
- PHP 8.2+
- Laravel 11.x, 12.x, or 13.x
Installation
composer require amsaid/larawhat
Publish the configuration file:
php artisan vendor:publish --tag=larawhat-config
Configuration
Set these environment variables in your .env file:
WHATSAPP_ACCESS_TOKEN=EAAT... WHATSAPP_PHONE_NUMBER_ID=123456789 WHATSAPP_BUSINESS_ACCOUNT_ID=987654321 WHATSAPP_WEBHOOK_VERIFY_TOKEN=my-verify-token WHATSAPP_API_VERSION=v22.0
| Variable | Description | Required |
|---|---|---|
WHATSAPP_ACCESS_TOKEN |
Permanent or temporary access token from Meta Developer Console | ✅ |
WHATSAPP_PHONE_NUMBER_ID |
ID of the phone number sending messages | ✅ |
WHATSAPP_BUSINESS_ACCOUNT_ID |
WABA ID (needed for templates & phone number management) | For some endpoints |
WHATSAPP_WEBHOOK_VERIFY_TOKEN |
Token to verify your webhook in Meta Console | For webhooks |
WHATSAPP_API_VERSION |
API version (defaults to v22.0) |
❌ |
WHATSAPP_WEBHOOK_PATH |
Webhook URI path (defaults to /api/whatsapp/webhook) |
❌ |
Webhook setup
In the Meta Developer Console, configure your webhook callback URL:
https://your-app.com/api/whatsapp/webhook
Use the same WHATSAPP_WEBHOOK_VERIFY_TOKEN value. The route is auto-registered and handles both the GET verification challenge and POST event delivery.
Usage
Facade
use Larawhat\Facades\Larawhat;
Send a text message
Larawhat::sendText('+1234567890', 'Hello from Laravel!');
With URL preview:
Larawhat::sendText('+1234567890', 'Check this out: https://example.com', previewUrl: true);
Using message objects
use Larawhat\Messages\TextMessage; $message = new TextMessage('Hello via message object!'); $response = Larawhat::sendMessage($message, '+1234567890');
Send media
use Larawhat\Messages\MediaMessage; // By media ID (upload first) Larawhat::sendMessage( MediaMessage::fromId('media-id-here', 'image', 'Nice photo!'), '+1234567890', ); // By URL Larawhat::sendMessage( MediaMessage::fromUrl('https://example.com/photo.jpg', 'image', 'Check this out'), '+1234567890', );
Send a template
use Larawhat\Messages\TemplateMessage; $message = (new TemplateMessage('hello_world', 'en_US')) ->addBodyParameter('John'); Larawhat::sendMessage($message, '+1234567890');
Interactive messages
Buttons:
use Larawhat\Messages\InteractiveMessage; $message = InteractiveMessage::buttons( body: 'Would you like to proceed?', buttons: [ ['id' => 'yes', 'title' => '✅ Yes'], ['id' => 'no', 'title' => '❌ No'], ], header: 'Confirm Action', footer: 'Powered by Larawhat', ); Larawhat::sendMessage($message, '+1234567890');
List:
$message = InteractiveMessage::list( body: 'Select an option:', buttonText: 'View Options', sections: [ [ 'title' => 'Categories', 'rows' => [ ['id' => 'support', 'title' => 'Support', 'description' => 'Get help'], ['id' => 'sales', 'title' => 'Sales', 'description' => 'Talk to sales'], ], ], ], header: 'Menu', footer: 'Choose wisely', ); Larawhat::sendMessage($message, '+1234567890');
Send location
use Larawhat\Messages\LocationMessage; $message = new LocationMessage(48.8566, 2.3522, 'Paris', 'France'); Larawhat::sendMessage($message, '+1234567890');
Send contact
use Larawhat\Messages\ContactsMessage; $message = (new ContactsMessage) ->addPerson('John Doe', '+11234567890', '1234567890'); Larawhat::sendMessage($message, '+1234567890');
Mark message as read
Larawhat::markAsRead('wamid.message-id-here');
Media management
// Upload a file $response = Larawhat::uploadMedia(storage_path('app/photos/photo.jpg'), 'image/jpeg'); $mediaId = $response->json('id'); // Get media metadata $media = Larawhat::getMedia($mediaId); // Download media $file = Larawhat::downloadMedia($mediaId); Storage::put('downloads/photo.jpg', $file->body()); // Delete media Larawhat::deleteMedia($mediaId);
Business profile
// Get profile $profile = Larawhat::businessProfile(); // Update profile Larawhat::updateBusinessProfile([ 'about' => 'We are open 9-5.', 'description' => 'Your friendly neighbourhood business.', 'websites' => ['https://example.com'], ]);
Phone numbers
// List all phone numbers $numbers = Larawhat::phoneNumbers(); // Get specific phone number $number = Larawhat::phoneNumber('123456789');
Message templates
// List templates $templates = Larawhat::templates(); // Create a template Larawhat::createTemplate([ 'name' => 'order_confirmation', 'language' => 'en_US', 'category' => 'UTILITY', 'components' => [ [ 'type' => 'BODY', 'text' => 'Your order {{1}} has been confirmed!', ], ], ]); // Delete a template Larawhat::deleteTemplate('order_confirmation');
Webhook Handling
Built-in route (auto-registered)
The package registers a GET & POST route at /api/whatsapp/webhook (configurable via WHATSAPP_WEBHOOK_PATH).
- GET — handles Meta's verification challenge automatically.
- POST — receives incoming events. The route always returns
200 {"status": "handled"}.
Custom callback handling
Use the Larawhat::webhook() handler to register callbacks. Add this to your AppServiceProvider::boot() or a dedicated service provider:
use Larawhat\Facades\Larawhat; // Must reference the same instance — we use resolve() to avoid boot order issues $this->app->booted(function () { Larawhat::webhook() ->onMessage(function ($webhook) { // Handle incoming message Log::info('Message from: '.$webhook->from()); Log::info('Text: '.$webhook->textBody()); // Auto-reply Larawhat::sendText($webhook->from(), 'Thanks for your message!'); }) ->onStatus(function ($webhook) { foreach ($webhook->statuses() as $status) { Log::info("Message {$status['id']} status: {$status['status']}"); } }) ->onAny(function ($webhook) { Log::debug('Webhook received', $webhook->payload()); }); });
The WebhookRequest API
$webhook->isValid(); // Check if payload is well-formed $webhook->from(); // Sender's phone number $webhook->messageType(); // 'text', 'image', 'interactive', etc. $webhook->textBody(); // Text content (if text message) $webhook->messages(); // Array of all messages $webhook->statuses(); // Array of status updates $webhook->interactiveReply(); // Button/list reply data $webhook->phoneNumberId(); // Phone number that received the message $webhook->payload(); // Raw payload array
Response handling
All API methods that contact Meta return an Illuminate\Http\Client\Response instance. Check success:
$response = Larawhat::sendText('+1234567890', 'Hello!'); if ($response->successful()) { $wamId = $response->json('messages')[0]['id'] ?? null; }
Errors are thrown as exceptions:
Larawhat\Exceptions\AuthenticationException— 401/403Larawhat\Exceptions\RateLimitException— 429Larawhat\Exceptions\LarawhatException— other errors
Testing
composer test
License
The MIT License (MIT). See LICENSE for details.