Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Whatsapp Laravel Package

teodoriu/laravel-whatsapp

A Laravel package for integrating WhatsApp messaging into your application, with a simple API for sending messages, handling sessions, and managing webhook events so you can wire WhatsApp notifications into existing PHP workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin. This is a 4-star, single-maintainer package sitting on a customer-facing path — pin the exact version in composer.json rather than floating on ^1.1, and commit composer.lock.

composer require teodoriu/laravel-whatsapp:1.1.0
php artisan vendor:publish --tag=whatsapp-config   # tag name may differ; check README
php artisan config:cache

Where to look first, in this order:

  1. config/whatsapp.php after publishing — this is the real API surface. It tells you the env var names, whether the Graph API version is configurable, and whether phone_number_id is global or per-connection. Trust this file over the README.
  2. README.md — the description promises a fluent interface, notification support, and webhooks, but gives no method signatures. Confirm the actual class names before writing code against them.
  3. src/ — the package is small enough to read end-to-end in one sitting. Do it. Specifically check: how access tokens are attached, whether HTTP calls go through Illuminate\Support\Facades\Http (this determines whether Http::fake() works in your tests, and it almost certainly does), and how errors are surfaced (exception vs. response object).
  4. CHANGELOG / the v1.1.0 diff — the release notes are a bare link to a compare URL with no feature list or migration notes. A minor bump implies additive-only, but read the diff before upgrading from 1.0.9.

First use case: send one pre-approved template.

// .env — names per your published config
WHATSAPP_TOKEN=EAAG...            // system-user token, not the 24h temporary one
WHATSAPP_PHONE_NUMBER_ID=1234567890
WHATSAPP_VERIFY_TOKEN=some-random-string
WHATSAPP_API_VERSION=v21.0
// tinker
app(WhatsApp::class)->send(/* template message, per README */);

Meta rejects business-initiated free-form text outside the 24-hour customer service window, so a template (the default hello_world is fine for smoke-testing) is the correct first request. Log the returned wamid — you will need it later.

Implementation Patterns

Wrap the send call behind your own interface on day one. This is the single highest-leverage decision given the maintenance risk. It costs an hour and makes the package swappable.

interface WhatsAppChannelContract {
    public function sendTemplate(string $to, string $template, array $params, string $lang = 'en_US'): string; // returns wamid
    public function sendText(string $to, string $body): string;
}

final class PackageWhatsAppChannel implements WhatsAppChannelContract { /* delegates */ }
// bind in a service provider
$this->app->bind(WhatsAppChannelContract::class, PackageWhatsAppChannel::class);

Drive it through Laravel Notifications. This is the idiomatic fit and the reason to prefer this package over raw HTTP. Implement toWhatsApp() on a Notification class, register the channel in via(), and you get queueing, retries, and ShouldQueue backoff for free — plus you can add an email/SMS fallback channel in the same via() array.

final class OrderShipped extends Notification implements ShouldQueue {
    public function via($notifiable): array { return ['whatsapp']; }
    public function toWhatsApp($notifiable) {
        return WhatsAppMessage::template('order_shipped')
            ->to($notifiable->phone_e164)
            ->parameters([$notifiable->order->tracking_number]);
    }
}

Keep the phone number as a separate, normalized column (+ stripped, E.164, country code included). Don't reuse a free-text phone field that users typed in.

Webhooks: split verification from processing. Meta's GET handshake must echo hub_challenge when hub_verify_token matches. The POST payload must be acknowledged with a 200 fast — Meta retries aggressively on non-2xx and will disable the webhook after sustained failures. So:

Route::get('/webhooks/whatsapp', [WebhookController::class, 'verify']);
Route::post('/webhooks/whatsapp', [WebhookController::class, 'receive']);
public function receive(Request $request) {
    DB::table('whatsapp_webhook_events')->insertOrIgnore([  // dedupe on message id
        'external_id' => $request->input('entry.0.changes.0.value.messages.0.id'),
        'payload'     => json_encode($request->all()),
    ]);
    ProcessWhatsAppWebhook::dispatch($request->all())->onQueue('webhooks');
    return response()->noContent();
}

Persist-then-dispatch gives you replayability when your handler has a bug, and the insertOrIgnore on the wamid protects against the duplicate deliveries Meta makes routinely.

Test with Http::fake(). Because the package likely uses Laravel's HTTP client, you can assert the outbound payload shape without hitting the network:

Http::fake(['graph.facebook.com/*' => Http::response(['messages' => [['id' => 'wamid.test']]], 200)]);

Assert on Http::assertSent() rather than on the package's return value where possible — it keeps your tests honest about what actually goes over the wire.

Gotchas and Tips

  • Business-initiated vs. user-initiated. Free-form messages only work inside 24 hours of the user's last inbound message. Everything else must be a template with an approved name + language code. Design your sendText() wrapper so it throws loudly rather than silently failing, and route anything time-delayed through templates. This is the pitfall that burns teams in week two.
  • Language code is part of the template identity. order_shipped / en_US and en are different keys. Pin language codes in config, not scattered across call sites.
  • Tokens. The token in the Meta dashboard's quick-start is temporary (24h) and will silently expire in production. Use a System User permanent token. Store it in env, never in config committed to git, and never log it — scrub Authorization headers in your HTTP logging middleware.
  • config:cache kills env() calls. If the package reads env() outside the config file, your cached deploy will break in a way that's invisible locally. Grep src/ for env( before shipping.
  • Error mapping is likely thin. Graph API returns structured errors (code, subcode, error_data.details) — some are permanent (invalid number, template not found, unapproved template) and some transient (rate limit, 500). Wrap sends and classify: retry transient, dead-letter permanent, and never let a bad recipient break the whole queue batch.
  • Media is two-step. Sending an image or PDF means uploading to /media first and referencing the returned media id, or passing a public HTTPS URL. Don't assume a simple attach() exists.
  • Webhook signature verification. Meta signs POSTs with X-Hub-Signature-256. Check whether the package verifies it; if not, add middleware that does, using your app secret. An unverified webhook endpoint is an open write path into your app.
  • Extension points. Publish the config and override the container binding rather than editing vendor files. If you need header injection or retry/backoff, decorating your WhatsAppChannelContract implementation is cleaner than forking.
  • Maintenance risk is real. One maintainer, four stars, a changelog that's an empty compare link. Mitigations: pin the version, read the diff before any bump, keep email/SMS live as fallbacks in via(), and monitor for abandoned-package signals quarterly. If WhatsApp becomes revenue-critical, revisit with a supported BSP — the interface wrapper makes that a swap, not a rewrite.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox