itsmedudes/laravel-whatsapp
Production-ready Laravel client for WhatsApp Business via Meta Graph. Send messages with a fluent payload builder, support multi-tenant access tokens, automatic retries and logging, plus webhook signature verification. Includes publishable config and migrations.
Installation Run:
composer require itsmedudes/laravel-whatsapp
php artisan vendor:publish --tag=meta-config --tag=meta-migrations
php artisan migrate
config/meta.php exists and update credentials (API keys, URLs, etc.).First Use Case: Sending a Text Message Inject the client into a controller/service:
use LaravelWhatsapp\WhatsAppBusinessClient;
public function sendWelcomeMessage($userId, $phoneNumberId)
{
$client = app(WhatsAppBusinessClient::class)->forUser($userId);
$payload = \LaravelWhatsapp\WhatsApp\MessagePayloadBuilder::text('+1234567890', 'Welcome!');
$client->sendMessage($phoneNumberId, $payload);
}
Key Files to Review
config/meta.php: API keys, endpoints, and retry logic.database/migrations/: Token storage tables (e.g., whatsapp_tokens).app/Providers/: Service provider for binding the client.Multi-Tenant Token Management
forUser($userId) to scope tokens per tenant:
$client = app(WhatsAppBusinessClient::class)->forUser($userId);
whatsapp_tokens table (auto-created via migrations).Message Payloads
MessagePayloadBuilder::text($to, 'Hello!')MessagePayloadBuilder::image($to, 'path/to/image.jpg')MessagePayloadBuilder::interactive() for buttons/quick replies.$payload = MessagePayloadBuilder::template($to, 'template_name', ['var1' => 'value']);
Webhook Handling
public function handle($request, Closure $next)
{
$verifier = new \LaravelWhatsapp\WebhookVerifier();
if (!$verifier->verifySignature($request->getContent(), $request->header('X-Hub-Signature-256'))) {
abort(403);
}
return $next($request);
}
Route::post('/whatsapp/webhook', [WhatsAppWebhookController::class, 'handle']);
Retry Logic
config/meta.php:
'retry' => [
'max_attempts' => 3,
'delay' => 1000, // ms
],
Logging
config/meta.php:
'logging' => [
'enabled' => true,
'channel' => 'whatsapp',
],
storage/logs/laravel-whatsapp.log.Queue Delayed Messages Use Laravel Queues to defer non-critical messages:
dispatch(new SendWhatsAppMessageJob($userId, $phoneNumberId, $payload))->delay(now()->addMinute());
Phone Number Management
phone_number_id (from Meta Business Manager) in your users table.$phoneNumberId = $client->getPhoneNumberId('+1234567890');
Testing
$this->app->instance(WhatsAppBusinessClient::class, Mockery::mock(WhatsAppBusinessClient::class));
Multi-Environment Tokens
config/meta.php per environment (e.g., .env variables):
'tokens' => [
'sandbox' => env('WHATSAPP_SANDBOX_TOKEN'),
'production' => env('WHATSAPP_PROD_TOKEN'),
],
Token Expiry
TokenRefreshFailed exceptions.config/meta.php has correct token_refresh_url and credentials.Webhook Verification Failures
X-Hub-Signature-256 is missing or invalid, the webhook will be rejected.$expected = hash_hmac('sha256', $payload, config('meta.webhook_secret'));
Rate Limits
429 Too Many Requests by:
delay in config/meta.php.Phone Number ID Mismatch
phone_number_id (e.g., sandbox vs. production) will fail silently.$client->sendMessage($phoneNumberId, $payload)->then(function ($response) use ($phoneNumberId) {
\Log::info("Sent to ID: $phoneNumberId", ['response' => $response]);
});
Template Messages
400 Bad Request with template_not_approved means you need to resubmit the template.Enable Debug Logging
Set 'logging' => ['enabled' => true] and check:
tail -f storage/logs/laravel-whatsapp.log
Inspect HTTP Requests Use Laravel’s HTTP client middleware to log requests:
$client->getHttpClient()->middleware(function ($request) {
\Log::debug('WhatsApp Request', ['url' => $request->url(), 'data' => $request->data()]);
});
Test with Postman Manually test API endpoints using Meta’s API reference to isolate issues.
Common Errors
| Error | Cause | Solution |
|---|---|---|
Invalid token |
Expired or wrong token | Refresh token or check config/meta.php |
Phone number not configured |
Missing phone_number_id |
Verify ID in Meta Business Manager |
Webhook signature mismatch |
Incorrect webhook_secret |
Regenerate secret in Meta Dashboard |
Template not found |
Unapproved template namespace | Resubmit template in Meta |
Custom Payload Builders
Extend MessagePayloadBuilder for domain-specific messages:
class OrderUpdatePayloadBuilder extends MessagePayloadBuilder
{
public static function build($to, $order)
{
return static::interactive($to)
->addButton('View Order', "https://example.com/orders/$order->id")
->addSection('Status: ' . $order->status);
}
}
Event Dispatching Trigger Laravel events after sending/receiving messages:
$client->sendMessage($phoneNumberId, $payload)->then(function () {
event(new WhatsAppMessageSent($userId, $payload));
});
Custom Retry Logic Override the retry strategy in a service provider:
$this->app->bind(WhatsAppBusinessClient::class, function ($app) {
$client = new WhatsAppBusinessClient(config('meta'));
$client->setRetryStrategy(new CustomRetryStrategy());
return $client;
});
Webhook Decoupling Use Laravel Events to decouple webhook handling:
// In WebhookController
event(new WhatsAppWebhookReceived($payload));
// Listen in EventServiceProvider
WhatsAppWebhookReceived::listen(function ($event) {
// Process message, e.g., update user's last_message_at
});
Multi-Language Support Dynamically set language in payloads:
$payload = MessagePayloadBuilder::text($to, 'Hola!')
->setLanguage('
How can I help you explore Laravel packages today?