PHPackages                             blissjaspis/laravel-whatsapp-cloud-api - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. blissjaspis/laravel-whatsapp-cloud-api

ActiveLibrary[API Development](/categories/api)

blissjaspis/laravel-whatsapp-cloud-api
======================================

Laravel package for interacting to whatsapp cloud api

v1.2.0(1mo ago)0100MITPHPPHP ^8.2CI passing

Since May 21Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/blissjaspis/laravel-whatsapp-cloud-api)[ Packagist](https://packagist.org/packages/blissjaspis/laravel-whatsapp-cloud-api)[ RSS](/packages/blissjaspis-laravel-whatsapp-cloud-api/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (4)Dependencies (13)Versions (5)Used By (0)

Laravel Whatsapp Cloud API
==========================

[](#laravel-whatsapp-cloud-api)

[![Tests](https://github.com/blissjaspis/laravel-whatsapp-cloud-api/actions/workflows/tests.yml/badge.svg)](https://github.com/blissjaspis/laravel-whatsapp-cloud-api/actions/workflows/tests.yml)[![Latest Version on Packagist](https://camo.githubusercontent.com/1a94219004529e45b956072376cccdd859ca7a28f7dd9a0204bed5380b2bcdf5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f626c6973736a61737069732f6c61726176656c2d77686174736170702d636c6f75642d6170692e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/blissjaspis/laravel-whatsapp-cloud-api)[![Total Downloads](https://camo.githubusercontent.com/8693b3e06f91282345ba9d9aa678fc55b3f2b3009a9ec3916565c243d9956d20/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f626c6973736a61737069732f6c61726176656c2d77686174736170702d636c6f75642d6170692e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/blissjaspis/laravel-whatsapp-cloud-api)

Laravel package to interact with the [WhatsApp Cloud API](https://developers.facebook.com/docs/whatsapp/cloud-api/).

- **Outbound** — send messages, media, templates, mark as read, and structured Graph API errors (`sendOrFail()`).
- **Inbound** — webhook verification, payload parsing, inbound media helpers, delivery failure details, optional idempotency (you provide routes and business logic).

Requirements
------------

[](#requirements)

- PHP 8.2 or higher
- Laravel 11, 12 or 13

Installation
------------

[](#installation)

```
composer require blissjaspis/laravel-whatsapp-cloud-api:^1.2
```

Publish the configuration file:

```
php artisan vendor:publish --provider="BlissJaspis\\WhatsappCloudApi\\WhatsappServiceProvider" --tag="config"
```

Use **[Outbound](#outbound)** to send messages, **[Inbound](#inbound)** to receive webhooks, or both. Each section below includes its own configuration and examples.

Outbound
--------

[](#outbound)

Send messages and call the Graph API.

### Configuration

[](#configuration)

Each **connection** is one Meta app. Credentials and Graph API version sit on the connection; phone numbers are nested under `phones`.

```
WHATSAPP_DEFAULT=default
WHATSAPP_VERSION_SDK=v25.0
WHATSAPP_PHONE_NUMBER_ID=
WHATSAPP_ACCESS_TOKEN=
WHATSAPP_WEBHOOK_APP_SECRET=
WHATSAPP_WEBHOOK_VERIFY_TOKEN=
```

VariableDescription`WHATSAPP_DEFAULT`Default target: connection name or `connection.phone` (e.g. `default.sales`)`WHATSAPP_VERSION_SDK`Graph API version for the `default` connection`WHATSAPP_PHONE_NUMBER_ID`Phone number ID for the `default` connection (`phones.main`)`WHATSAPP_ACCESS_TOKEN`System user access token for the `default` connectionDefine connections in `config/whatsapp-cloud-api.php`:

```
'default' => env('WHATSAPP_DEFAULT', 'default'),

'connections' => [
    'default' => [
        'version_sdk' => env('WHATSAPP_VERSION_SDK', 'v25.0'),
        'access_token' => env('WHATSAPP_ACCESS_TOKEN'),
        'webhook_app_secret' => env('WHATSAPP_WEBHOOK_APP_SECRET'),
        'webhook_verify_token' => env('WHATSAPP_WEBHOOK_VERIFY_TOKEN'),
        'default_phone' => 'main',
        'phones' => [
            'main' => ['phone_number_id' => env('WHATSAPP_PHONE_NUMBER_ID')],
            'sales' => ['phone_number_id' => env('WHATSAPP_SALES_PHONE_NUMBER_ID')],
        ],
    ],
    'brand_b' => [
        'version_sdk' => env('WHATSAPP_BRAND_B_VERSION_SDK', 'v25.0'),
        'access_token' => env('WHATSAPP_BRAND_B_ACCESS_TOKEN'),
        'webhook_app_secret' => env('WHATSAPP_BRAND_B_WEBHOOK_APP_SECRET'),
        'phones' => [
            'support' => ['phone_number_id' => env('WHATSAPP_BRAND_B_PHONE_NUMBER_ID')],
        ],
    ],
],
```

Send from a connection or a specific phone:

```
Whatsapp::connection('default.sales')
    ->message()
    ->to('6281234567890')
    ->body(Text::message('Hello from sales')->build())
    ->send();

Whatsapp::connection('brand_b')->media()->upload($path, Media::ImageJPEG);
Whatsapp::connection('default.sales')->readMessage('wamid.xxx');
```

`Whatsapp::message()` without `connection()` uses the target named in `WHATSAPP_DEFAULT` (default: `default`).

### Validate configuration

[](#validate-configuration)

List connections and spot missing tokens or phone number IDs:

```
php artisan whatsapp:connections
php artisan whatsapp:connections --validate   # exit code 1 when invalid
```

Optional boot-time checks (reports to logs, does not stop the app):

```
WHATSAPP_VALIDATE_ON_BOOT=true
```

### Core pattern

[](#core-pattern)

All outbound messages follow the same pattern:

```
Whatsapp::message()
    ->to($phoneNumber)
    ->body($messageBuilder->build())
    ->send(); // Illuminate\Http\Client\Response
```

Use `->sendOrFail()` instead of `->send()` when you want a `WhatsappApiException` on Graph API errors (see [Graph API errors](#graph-api-errors)).

Use the `Whatsapp` facade or resolve `BlissJaspis\WhatsappCloudApi\Whatsapp` from the container.

### Graph API errors

[](#graph-api-errors)

Failed requests return a normal `Response` from `send()`. For structured Meta error fields, use `sendOrFail()` or `WhatsappApiException::throwIfFailed()`:

```
use BlissJaspis\WhatsappCloudApi\Exceptions\WhatsappApiException;
use BlissJaspis\WhatsappCloudApi\Facades\Whatsapp;
use BlissJaspis\WhatsappCloudApi\Support\Text;

try {
    Whatsapp::connection('default')
        ->message()
        ->to('6281234567890')
        ->body(Text::message('Hello')->build())
        ->sendOrFail();
} catch (WhatsappApiException $e) {
    // $e->getMessage()
    // $e->getCode() — HTTP status (400, 401, …)
    // $e->apiCode, $e->errorSubcode, $e->type, $e->fbtraceId, $e->error
}

$response = Whatsapp::message()->to('628...')->body(...)->send();
WhatsappApiException::throwIfFailed($response);
```

Media and read receipts use the same `*OrFail()` helpers:

```
use BlissJaspis\WhatsappCloudApi\Enums\Media;
use BlissJaspis\WhatsappCloudApi\WhatsappRead;

$upload = Whatsapp::connection('default')->media()->uploadOrFail('/path/to/file.jpg', Media::ImageJPEG);

(new WhatsappRead('default.sales'))->sendOrFail('wamid.xxx');
```

#### Phone numbers

[](#phone-numbers)

The package passes the recipient number to the API as-is. Format the number in your application before calling `to()`. WhatsApp expects digits only with the country code included and no `+` prefix (e.g. `6281234567890`).

#### Text messages

[](#text-messages)

```
use BlissJaspis\WhatsappCloudApi\Facades\Whatsapp;
use BlissJaspis\WhatsappCloudApi\Support\Text;

$response = Whatsapp::message()
    ->to('6281234567890')
    ->body(Text::message('Hello from Laravel!')->build())
    ->sendOrFail();

$messageId = $response->json('messages.0.id');
```

Disable link preview:

```
Text::message('Visit https://example.com')->disableLinkPreview()->build();
```

#### Replying to a message

[](#replying-to-a-message)

```
Whatsapp::message()
    ->to('6281234567890')
    ->replyTo('wamid.xxx')
    ->body(Text::message('Thanks for your message!')->build())
    ->send();
```

#### Media messages

[](#media-messages)

Media builders accept an uploaded media ID (`asset`) or a public HTTPS URL (`url`).

#### Image

[](#image)

```
use BlissJaspis\WhatsappCloudApi\Support\Image;

// By URL
Whatsapp::message()
    ->to('6281234567890')
    ->body(Image::media('https://example.com/image.jpg', 'url')->build())
    ->send();

// By media ID with caption
Whatsapp::message()
    ->to('6281234567890')
    ->body(Image::media('your-media-id')->caption('Your caption')->build())
    ->send();
```

#### Document

[](#document)

```
use BlissJaspis\WhatsappCloudApi\Support\Document;

Whatsapp::message()
    ->to('6281234567890')
    ->body(
        Document::media('https://example.com/document.pdf', 'url')
            ->filename('invoice.pdf')
            ->caption('Your invoice')
            ->build()
    )
    ->send();
```

#### Audio

[](#audio)

```
use BlissJaspis\WhatsappCloudApi\Support\Audio;

Whatsapp::message()
    ->to('6281234567890')
    ->body(Audio::media('your-media-id')->build())
    ->send();
```

#### Video

[](#video)

```
use BlissJaspis\WhatsappCloudApi\Support\Video;

Whatsapp::message()
    ->to('6281234567890')
    ->body(Video::media('your-media-id')->caption('Optional caption')->build())
    ->send();
```

#### Sticker

[](#sticker)

```
use BlissJaspis\WhatsappCloudApi\Support\Sticker;

Whatsapp::message()
    ->to('6281234567890')
    ->body(Sticker::media('your-media-id')->build())
    ->send();
```

#### Template messages

[](#template-messages)

```
use BlissJaspis\WhatsappCloudApi\Support\Template;

// Static template (no variables)
Whatsapp::message()
    ->to('6281234567890')
    ->body(Template::name('hello_world')->lang('en')->build())
    ->send();

// Template with variables (fluent components)
use BlissJaspis\WhatsappCloudApi\Support\Template\BodyComponent;
use BlissJaspis\WhatsappCloudApi\Support\Template\ButtonComponent;
use BlissJaspis\WhatsappCloudApi\Support\Template\HeaderComponent;

Whatsapp::message()
    ->to('6281234567890')
    ->body(
        Template::name('order_update')
            ->lang('en')
            ->components([
                HeaderComponent::text('Your order'),
                BodyComponent::make()->text('John')->text('#12345'),
                ButtonComponent::url(0, 'https://example.com/orders/12345'),
            ])
            ->build()
    )
    ->send();
```

`BodyComponent` also supports `currency()` and `dateTime()` for non-text variables. Use raw `components([...])` arrays for uncommon template layouts.

Optional category when your WhatsApp Business setup requires it:

```
Template::name('promo_offer')->lang('en')->category('marketing')->build();
```

#### Location

[](#location)

```
use BlissJaspis\WhatsappCloudApi\Support\Location;

Whatsapp::message()
    ->to('6281234567890')
    ->body(
        Location::mark()
            ->latitude(-6.2088)
            ->longitude(106.8456)
            ->name('Jakarta')
            ->address('Indonesia')
            ->build()
    )
    ->send();
```

#### Location request

[](#location-request)

```
use BlissJaspis\WhatsappCloudApi\Support\LocationRequest;

Whatsapp::message()
    ->to('6281234567890')
    ->body(LocationRequest::message('Please share your location.')->build())
    ->send();
```

#### Interactive messages

[](#interactive-messages)

List message with fluent action helpers:

```
use BlissJaspis\WhatsappCloudApi\Support\Interactive;
use BlissJaspis\WhatsappCloudApi\Support\Interactive\ListAction;
use BlissJaspis\WhatsappCloudApi\Support\Interactive\ReplyButtonAction;

Whatsapp::message()
    ->to('6281234567890')
    ->body(
        Interactive::list()
            ->body('Choose an option')
            ->header('Menu')
            ->action(
                ListAction::make('View options')
                    ->section('Section 1')
                    ->row('row_1', 'Option 1', 'First option')
            )
            ->build()
    )
    ->send();

// Reply buttons (up to 3)
Whatsapp::message()
    ->to('6281234567890')
    ->body(
        Interactive::button()
            ->body('Do you agree?')
            ->action(
                ReplyButtonAction::make()
                    ->reply('yes', 'Yes')
                    ->reply('no', 'No')
            )
            ->build()
    )
    ->send();
```

Fluent entry points for other interactive types (pass a raw `action` array for the payload):

```
Interactive::flow()->body('Complete the form')->action([/* flow action */])->build();
Interactive::productList()->body('Browse')->action([/* catalog */])->build();
Interactive::catalog()->body('View catalog')->action([/* catalog_message */])->build();
```

#### Contacts and reactions

[](#contacts-and-reactions)

```
use BlissJaspis\WhatsappCloudApi\Support\Contacts;
use BlissJaspis\WhatsappCloudApi\Support\Reaction;

Whatsapp::message()
    ->to('6281234567890')
    ->body(Contacts::data([/* WhatsApp contacts array */])->build())
    ->send();

Whatsapp::message()
    ->to('6281234567890')
    ->body(Reaction::messageId('wamid.xxx')->emoji('👍')->build())
    ->send();
```

#### Media upload, retrieve, and delete

[](#media-upload-retrieve-and-delete)

```
use BlissJaspis\WhatsappCloudApi\Enums\Media;
use BlissJaspis\WhatsappCloudApi\Facades\Whatsapp;
use BlissJaspis\WhatsappCloudApi\Support\Image;

$upload = Whatsapp::media()->upload('/path/to/image.jpg', Media::ImageJPEG);
$mediaId = $upload->json('id');

Whatsapp::message()
    ->to('6281234567890')
    ->body(Image::media($mediaId)->build())
    ->send();

$metadata = Whatsapp::media()->retrieve($mediaId);
$binary = Whatsapp::media()->download($metadata->json('url'));
Whatsapp::media()->delete($mediaId);

// Or throw WhatsappApiException on failure:
$metadata = Whatsapp::media()->retrieveOrFail($mediaId);
$binary = Whatsapp::media()->downloadOrFail($metadata->json('url'));
```

#### Mark a message as read

[](#mark-a-message-as-read)

```
use BlissJaspis\WhatsappCloudApi\Facades\Whatsapp;

// Uses WHATSAPP_DEFAULT connection
Whatsapp::readMessage('wamid.xxx');
Whatsapp::readMessage('wamid.xxx', withTypingIndicator: true);

// Named connection with sendOrFail()
use BlissJaspis\WhatsappCloudApi\WhatsappRead;

(new WhatsappRead('default.sales'))->sendOrFail('wamid.xxx');
```

Inbound
-------

[](#inbound)

Receive WhatsApp webhooks in your Laravel app. You provide routes, controllers, jobs, and business logic; this package supplies verification and optional idempotency helpers.

### Configuration

[](#configuration-1)

Webhook credentials are set per Meta app under `connections` (see [Outbound configuration](#configuration)):

```
'connections' => [
    'default' => [
        'webhook_app_secret' => env('WHATSAPP_WEBHOOK_APP_SECRET'),
        'webhook_verify_token' => env('WHATSAPP_WEBHOOK_VERIFY_TOKEN'),
        // ...
    ],
],
```

Config keyDescription`connections.*.webhook_app_secret`App Secret (Meta Developer Console → App settings → Basic)`connections.*.webhook_verify_token`Same token as in your Meta webhook subscription`webhook_idempotency`Optional: `prefix`, `ttl`, `cache_store` (see below)### Subscription challenge (GET)

[](#subscription-challenge-get)

When you configure the webhook URL in Meta, respond to the verification request:

```
use BlissJaspis\WhatsappCloudApi\Webhooks\WebhookChallenge;
use Illuminate\Http\Request;

Route::get('/webhook/whatsapp', fn (Request $request) => WebhookChallenge::toResponse($request));
```

### Signature verification (POST)

[](#signature-verification-post)

Verify `X-Hub-Signature-256` using the raw request body and your app secret:

```
use BlissJaspis\WhatsappCloudApi\Http\Middleware\VerifyWhatsappWebhookSignature;

Route::post('/webhook/whatsapp', function (Request $request) {
    // Handle payload in your app
})->middleware(VerifyWhatsappWebhookSignature::class);
```

When no middleware parameter is passed, the app secret is read from the default connection (`WHATSAPP_DEFAULT`, usually `default`).

Pass a **connection name** (from `connections`) or a **literal app secret** after `:`:

```
Route::post('/webhook/whatsapp', function (Request $request) {
    // Handle payload in your app
})->middleware(VerifyWhatsappWebhookSignature::class . ':your-meta-app-secret');

// Connection name (per Meta app — not connection.phone)
Route::post('/webhook/whatsapp/support', $handler)
    ->middleware(VerifyWhatsappWebhookSignature::class . ':support');
```

Or verify manually:

```
use BlissJaspis\WhatsappCloudApi\Webhooks\WebhookSignature;

WebhookSignature::assertValidRequest($request); // uses config; throws InvalidWebhookSignature
WebhookSignature::assertValidRequest($request, 'custom-app-secret');
```

> **Note:** Signature verification uses the **raw request body**. Do not parse the JSON before the middleware runs. If your app secret contains `:`, Laravel splits middleware parameters on that character — use config only or `WebhookSignature::verifyRequest($request, $secret)` instead.

### Idempotency (optional)

[](#idempotency-optional)

Meta may retry webhooks. Use `WebhookPayload` to extract message/status IDs and skip duplicates:

```
use BlissJaspis\WhatsappCloudApi\Contracts\WebhookIdempotencyStore;
use BlissJaspis\WhatsappCloudApi\Http\Middleware\EnsureWhatsappWebhookIdempotency;
use BlissJaspis\WhatsappCloudApi\Webhooks\WebhookPayload;

Route::post('/webhook/whatsapp', function (Request $request) {
    // ...
})->middleware([
    VerifyWhatsappWebhookSignature::class,
    EnsureWhatsappWebhookIdempotency::class,
]);
```

Configure `webhook_idempotency` in `config/whatsapp-cloud-api.php` (default TTL: 24 hours). Bind your own `WebhookIdempotencyStore` for database-backed deduplication. For production, use Redis via `WHATSAPP_WEBHOOK_IDEMPOTENCY_CACHE_STORE`.

### Payload parsing

[](#payload-parsing)

Parse nested Meta payloads into typed events instead of drilling into `entry[0]['changes'][0]` manually:

```
use BlissJaspis\WhatsappCloudApi\Webhooks\Events\IncomingMessage;
use BlissJaspis\WhatsappCloudApi\Webhooks\Events\MessageStatus;
use BlissJaspis\WhatsappCloudApi\Webhooks\WebhookPayload;

Route::post('/webhook/whatsapp', function (Request $request) {
    foreach (WebhookPayload::events($request->all()) as $event) {
        if ($event instanceof MessageStatus) {
            // delivery/read receipts
            continue;
        }

        if (! $event instanceof IncomingMessage || ! $event->isMessagesField()) {
            continue;
        }

        if ($reply = $event->interactiveReply()) {
            ProcessInteractiveResponseJob::dispatch($event->raw(), $reply->id);

            continue;
        }

        if ($text = $event->textBody()) {
            // handle plain text
        }

        if ($mediaId = $event->mediaId()) {
            // image, video, audio, document, or sticker — see inbound media below
        }
    }
})->middleware(VerifyWhatsappWebhookSignature::class);
```

Helpers:

MethodReturns`WebhookPayload::events()`All parsed events (`IncomingMessage`, `MessageStatus`, `MessageTemplateStatusUpdate`, `UnknownWebhookEvent`)`WebhookPayload::incomingMessages()`Incoming messages only`WebhookPayload::messageStatuses()`Delivery/read/failed statuses only`WebhookPayload::messageTemplateStatusUpdates()`Template approval/rejection updates from Meta#### IncomingMessage

[](#incomingmessage)

MethodDescription`textBody()`, `hasTextBody()`Plain text messages`interactiveReply()`, `buttonReplyId()`, `listReplyId()`Button/list/NFM replies`mediaId()`, `mimeType()`, `sha256()`, `mediaCaption()`, `documentFilename()`Inbound image/video/audio/document/sticker`location()`, `isLocation()`Shared location coordinates and label`from()`, `phoneNumberId()`, `messageType()`, `raw()`Routing and debuggingUnknown webhook fields are returned as `UnknownWebhookEvent` so you can still read `value()` without the parser failing.

#### MessageStatus and delivery failures

[](#messagestatus-and-delivery-failures)

```
foreach (WebhookPayload::messageStatuses($request->all()) as $status) {
    if ($status->isFailed() && ($error = $status->firstError())) {
        // $error->code, $error->title, $error->message, $error->details
    }

    if ($status->isDelivered()) {
        // ...
    }
}
```

`errors()` returns a list of `WhatsappDeliveryError` value objects parsed from Meta's `statuses[].errors` payload.

#### Template status updates

[](#template-status-updates)

```
use BlissJaspis\WhatsappCloudApi\Webhooks\Events\MessageTemplateStatusUpdate;

foreach (WebhookPayload::messageTemplateStatusUpdates($request->all()) as $event) {
    if ($event->isApproved()) {
        // $event->templateName(), $event->templateLanguage()
    }
}
```

#### Download inbound media

[](#download-inbound-media)

When a user sends media, use `mediaId()` with the same connection that received the message:

```
use BlissJaspis\WhatsappCloudApi\Facades\Whatsapp;
use BlissJaspis\WhatsappCloudApi\Webhooks\Events\IncomingMessage;

if ($event instanceof IncomingMessage && ($mediaId = $event->mediaId())) {
    $meta = Whatsapp::connection($connection->name)->media()->retrieveOrFail($mediaId);
    $file = Whatsapp::connection($connection->name)->media()->downloadOrFail($meta->json('url'));

    // $event->mimeType(), $event->sha256(), $event->mediaCaption()
}
```

#### Route inbound messages to a connection (multiple numbers)

[](#route-inbound-messages-to-a-connection-multiple-numbers)

One webhook URL can receive events from every number in the same Meta app. Use `phoneNumberId()` to pick the connection, then reply from that number:

```
use BlissJaspis\WhatsappCloudApi\Facades\Whatsapp;
use BlissJaspis\WhatsappCloudApi\Webhooks\Events\IncomingMessage;
use BlissJaspis\WhatsappCloudApi\WhatsappConnectionManager;

foreach (WebhookPayload::events($request->all()) as $event) {
    if (! $event instanceof IncomingMessage) {
        continue;
    }

    $connection = app(WhatsappConnectionManager::class)
        ->findByPhoneNumberId($event->phoneNumberId() ?? '');

    if ($connection === null) {
        continue;
    }

    Whatsapp::connection($connection->name)
        ->message()
        ->to($event->from() ?? '')
        ->body(Text::message('Thanks!')->build())
        ->send();
}
```

### HTTP observability

[](#http-observability)

Before each Graph API request, the package dispatches `BlissJaspis\WhatsappCloudApi\Events\HttpSending` with the pending HTTP client and resolved `WhatsappConnection`. Listen in your app to log URLs, connection names, or add headers:

```
use BlissJaspis\WhatsappCloudApi\Events\HttpSending;

Event::listen(HttpSending::class, function (HttpSending $event) {
    logger()->info('WhatsApp API request', [
        'connection' => $event->connection->name,
        'phone_number_id' => $event->connection->phoneNumberId,
    ]);
});
```

### Queue outbound messages

[](#queue-outbound-messages)

The package does not queue messages automatically. Dispatch a job from your app:

```
use BlissJaspis\WhatsappCloudApi\Facades\Whatsapp;

SendWhatsappTextJob::dispatch('6281234567890', 'Hello');

// Job example:
final class SendWhatsappTextJob implements ShouldQueue
{
    public function __construct(
        private string $to,
        private string $body,
        private ?string $connection = null,
    ) {}

    public function handle(): void
    {
        $whatsapp = $this->connection
            ? Whatsapp::connection($this->connection)
            : Whatsapp::message();

        $whatsapp->to($this->to)
            ->body(['type' => 'text', 'text' => ['body' => $this->body]])
            ->sendOrFail();
    }
}
```

Testing
-------

[](#testing)

Fake outbound calls without hitting Meta (requires `phpunit/phpunit` in your app for assertion helpers):

```
use BlissJaspis\WhatsappCloudApi\Facades\Whatsapp;

Whatsapp::fake();

Whatsapp::message()
    ->to('6281234567890')
    ->body(['type' => 'text', 'text' => ['body' => 'Hello']])
    ->send();

Whatsapp::fake()->assertSentTo('6281234567890');
```

```
composer lint
```

Laravel Boost
-------------

[](#laravel-boost)

This package includes a [Laravel Boost](https://laravel.com/docs/boost) agent skill at `resources/boost/skills/laravel-whatsapp-cloud-api/SKILL.md` to help AI agents generate correct integration code.

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Bliss Jaspis](https://github.com/blissjaspis)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

43

—

FairBetter than 90% of packages

Maintenance93

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~247 days

Total

4

Last Release

37d ago

PHP version history (2 changes)v1.0.0-beta1PHP ^8.2

v1.0.0PHP ^8.1

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/8768022?v=4)[jaspis](/maintainers/jaspis)[@jaspis](https://github.com/jaspis)

---

Top Contributors

[![blissjaspis](https://avatars.githubusercontent.com/u/19877298?v=4)](https://github.com/blissjaspis "blissjaspis (44 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/blissjaspis-laravel-whatsapp-cloud-api/health.svg)

```
[![Health](https://phpackages.com/badges/blissjaspis-laravel-whatsapp-cloud-api/health.svg)](https://phpackages.com/packages/blissjaspis-laravel-whatsapp-cloud-api)
```

###  Alternatives

[spatie/laravel-responsecache

Speed up a Laravel application by caching the entire response

2.8k8.7M64](/packages/spatie-laravel-responsecache)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.1M337](/packages/psalm-plugin-laravel)[defstudio/telegraph

A laravel facade to interact with Telegram Bots

815320.5k3](/packages/defstudio-telegraph)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)[jasara/php-amzn-selling-partner-api

A fluent interface for Amazon's Selling Partner API in PHP

1348.1k1](/packages/jasara-php-amzn-selling-partner-api)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
