PHPackages                             42dx/whatsapp-laravel-sdk - 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. 42dx/whatsapp-laravel-sdk

ActiveLibrary[API Development](/categories/api)

42dx/whatsapp-laravel-sdk
=========================

A laravel package that abstracts all the whatsapp api integration into an easy-to-use services and facades

1.1.0(1w ago)141[8 issues](https://github.com/42dx/whatsapp-laravel-sdk/issues)MITPHPCI passing

Since Jun 29Pushed 1w ago1 watchersCompare

[ Source](https://github.com/42dx/whatsapp-laravel-sdk)[ Packagist](https://packagist.org/packages/42dx/whatsapp-laravel-sdk)[ Fund](https://github.com/sponsors/42dx)[ Fund](https://ko-fi.com/42developerexperience)[ RSS](/packages/42dx-whatsapp-laravel-sdk/feed)WikiDiscussions main Synced 3d ago

READMEChangelog (8)Dependencies (9)Versions (19)Used By (0)

Whatsapp Laravel SDK
====================

[](#whatsapp-laravel-sdk)

This Laravel package's goal is to abstract and simplify integration with Facebook's Whatsapp Business API through services, models, traits, and message builders, while documenting how to work with it correctly.

Check our package on [Packagist](https://packagist.org/packages/42dx/whatsapp-laravel-sdk).

Project Meta Data
-----------------

[](#project-meta-data)

[![All Contributors](https://camo.githubusercontent.com/c757f7c91affe2b097b4816f2da3a9fc0befdf818a23efe9309126d5f5f3be59/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616c6c2d636f6e7472696275746f72732f343264782f77686174736170702d6c61726176656c2d73646b3f636f6c6f723d65653834343926666c6174266c6162656c3d436f6e7472696275746f7273)](https://github.com/42dx/whatsapp-laravel-sdk/blob/main/README.md#contributors)[![Reliability Rating](https://camo.githubusercontent.com/806b987e6fdc0e8753a1a9c899346557a33d731bf981a8ebb0e371e261717be4/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d343264785f77686174736170702d6c61726176656c2d73646b266d65747269633d72656c696162696c6974795f726174696e67)](https://sonarcloud.io/summary/new_code?id=42dx_whatsapp-laravel-sdk)[![Security Rating](https://camo.githubusercontent.com/600b484a7613a27d02eb9251aee4670d4decfb9e73b6e32c6a135ce74e83f0b2/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d343264785f77686174736170702d6c61726176656c2d73646b266d65747269633d73656375726974795f726174696e67)](https://sonarcloud.io/summary/new_code?id=42dx_whatsapp-laravel-sdk)[![Maintainability Rating](https://camo.githubusercontent.com/29cc0735f544353b9a8a3c6c2f5458b577bdc20804b8bf2b593a37a28102b103/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d343264785f77686174736170702d6c61726176656c2d73646b266d65747269633d7371616c655f726174696e67)](https://sonarcloud.io/summary/new_code?id=42dx_whatsapp-laravel-sdk)[![Vulnerabilities](https://camo.githubusercontent.com/2c0cc4ae4fb95d708eb7820a17dcd0040d1249409b6d24220228ebcf187ed503/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d343264785f77686174736170702d6c61726176656c2d73646b266d65747269633d76756c6e65726162696c6974696573)](https://sonarcloud.io/summary/new_code?id=42dx_whatsapp-laravel-sdk)

Project Status (`v1.0`, `main` branch)
--------------------------------------

[](#project-status-v10-main-branch)

[![Quality Gate Status](https://camo.githubusercontent.com/983b6d3f9495e0d271e2fc4e8405a2e977bfc7ec0c2890a890596efa363ea59f/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d343264785f77686174736170702d6c61726176656c2d73646b266d65747269633d616c6572745f737461747573)](https://sonarcloud.io/summary/new_code?id=42dx_whatsapp-laravel-sdk)[![Bugs](https://camo.githubusercontent.com/ed6a22496adc4d5926516c6999745facaa773a7eeeb46b4876801b6e474f09a6/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d343264785f77686174736170702d6c61726176656c2d73646b266d65747269633d62756773)](https://sonarcloud.io/summary/new_code?id=42dx_whatsapp-laravel-sdk)[![Code Smells](https://camo.githubusercontent.com/08a077b3398d48fae2a148c1bb5459b9c9079aa303bbf2994b21078dea8001c4/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d343264785f77686174736170702d6c61726176656c2d73646b266d65747269633d636f64655f736d656c6c73)](https://sonarcloud.io/summary/new_code?id=42dx_whatsapp-laravel-sdk)[![Coverage](https://camo.githubusercontent.com/a5f1087436ba04a6fea3e7855c51868c8b1f54b0b58f88d7a6189ecf52418e0e/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d343264785f77686174736170702d6c61726176656c2d73646b266d65747269633d636f766572616765)](https://sonarcloud.io/summary/new_code?id=42dx_whatsapp-laravel-sdk)[![Technical Debt](https://camo.githubusercontent.com/933a2d16f1f56c6466812b427748aa7643591bdbec5b14de8b99344a011a100e/68747470733a2f2f736f6e6172636c6f75642e696f2f6170692f70726f6a6563745f6261646765732f6d6561737572653f70726f6a6563743d343264785f77686174736170702d6c61726176656c2d73646b266d65747269633d7371616c655f696e646578)](https://sonarcloud.io/summary/new_code?id=42dx_whatsapp-laravel-sdk)

Table of Contents
-----------------

[](#table-of-contents)

- [Composer Scripts](#composer-scripts)
- [Installation](#installation)
    - [Requirements](#requirements)
- [Configure Whatsapp Webhook](#configure-whatsapp-webhook)
- [Using the Package](#using-the-package)
    - [Migrations](#migrations)
        - [`whatsapp_messages` Table](#whatsapp_messages-table)
        - [Messageable Migration](#messageable-migration)
    - [General Concepts](#general-concepts)
        - [Sending Messages](#sending-messages)
        - [Message Templates](#message-templates)
        - [Available Templates](#available-templates)
        - [Message Support](#message-support)
        - [Whatsapp API Entities](#whatsapp-api-entities)
    - [Meta Webhook Validation](#meta-webhook-validation)
    - [Receiving Messages](#receiving-messages)
- [Run and Test](#run-and-test)
- [Tooling](#tooling)
    - [Comitzen](#comitizen)
    - [Pinggy](#pinggy)
        - [How to use Pinggy](#how-to-use-pinggy)
- [Contributors](#contributors)
- [Changelog](#changelog)
- [Roadmap](#roadmap)

Composer Scripts
----------------

[](#composer-scripts)

We tried to automate as much boring tasks and CLI commands as we could on short `composer` scripts. Below you will find a general description of what each one does:

- `composer commit`: Runs the [`comitzen` binary](https://github.com/lintingzhen/commitizen-go) which opens an interactive CLI to keep commits in a standard that our pipeline can use to generate the package changelog automatically.
- `composer coverage`: Run the tests and generates code coverage. If you are contributing, please make sure you do not lower the current coverage. ;)
- `composer start`: Starts the sample application through Laravel Sail with the package already loaded.
- `composer connect`: Connects to Pinggy service and exposes local port `80` to the web. It assumes the sample app is running through Sail.
- `composer setup`: Prepares the sample application: installs dependencies, creates `.env` when missing, generates the app key, starts Sail, waits for MySQL, and runs migrations.
- `composer stop`: Stops the sample application containers.
- `composer test`: Run the tests (do not generate coverage report)
- `composer lint`: Checks PHP code style with Laravel Pint.

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

[](#installation)

### Requirements

[](#requirements)

- A Laravel application with Composer package auto-discovery enabled, or manual service provider registration.
- A configured WhatsApp Business Account, phone number ID, business account ID, and API token from Meta.
- The examples use the Laravel 11/12 `bootstrap/providers.php` provider registration style.

Install the package through Composer:

```
composer require 42dx/whatsapp-laravel-sdk
```

The package supports Laravel package auto-discovery and registers both service providers through `composer.json`. If auto-discovery is disabled in your application, register the providers manually in `bootstrap/providers.php`:

```
return [
    // [...]
    The42dx\Whatsapp\RouteServiceProvider::class,
    The42dx\Whatsapp\WhatsappServiceProvider::class,
];
```

Publish the configuration file before running migrations so you can adjust table names, messageable columns, and the user model:

```
php artisan vendor:publish --tag=whatsapp-business-api-config
```

*[back to top](#table-of-contents)*

Configure Whatsapp Webhook
--------------------------

[](#configure-whatsapp-webhook)

The package automatically registers two routes to handle Meta's webhook interactions:

MethodURIController MethodPurposeGET`{webhook_route}``WebhookController@check`Webhook verification handshakePOST`{webhook_route}``WebhookController@handle`Receive webhook eventsThe default URI is `webhook/whatsapp`. You can customize it by setting `WHATSAPP_WEBHOOK_ROUTE` in your `.env` file:

```
WHATSAPP_WEBHOOK_ROUTE=custom/webhook/path
```

### Meta Dashboard Setup

[](#meta-dashboard-setup)

1. Go to your app on the [Meta Developer Dashboard](https://developers.facebook.com/).
2. Navigate to **WhatsApp** → **Configuration** → **Webhook**.
3. Click **Edit** and set the **Callback URL** to `https://your-domain.com/{webhook_route}`.
4. Set the **Verify Token** to a string of your choice — this must match the `WHATSAPP_WEBHOOK_VERIFY` value in your `.env` file.
5. Subscribe to the webhook fields you need (at minimum, **messages**).

### Environment Variables

[](#environment-variables)

Add the following to your `.env` file:

```
WHATSAPP_API_VERSION=v20.0
WHATSAPP_BUSINESS_ID=
WHATSAPP_BUSINESS_PHONE_ID=
WHATSAPP_BUSINESS_PHONE_NUMBER=
WHATSAPP_SERVER_URL=https://graph.facebook.com
WHATSAPP_TOKEN=
WHATSAPP_WEBHOOK_VERIFY=
WHATSAPP_WEBHOOK_ROUTE=webhook/whatsapp
WHATSAPP_DEFAULT_TEMPLATE_LANGUAGE=en_US
```

VariableRequiredDefaultDescription`WHATSAPP_API_VERSION`No`v20.0`Meta Graph API version to use`WHATSAPP_BUSINESS_ID`Yes—Your WhatsApp Business Account ID`WHATSAPP_BUSINESS_PHONE_ID`Yes—The phone number ID from your WhatsApp Business dashboard`WHATSAPP_BUSINESS_PHONE_NUMBER`Yes—The phone number associated with your Business account`WHATSAPP_SERVER_URL`No`https://graph.facebook.com`Base URL for the WhatsApp API`WHATSAPP_TOKEN`Yes—Permanent or temporary access token for the API`WHATSAPP_WEBHOOK_VERIFY`Yes—Verify token for webhook validation (must match Meta dashboard)`WHATSAPP_WEBHOOK_ROUTE`No`webhook/whatsapp`URI path where Meta will send webhook events`WHATSAPP_DEFAULT_TEMPLATE_LANGUAGE`No`en_US`Default language code for message templates> **Important:** The `WHATSAPP_WEBHOOK_VERIFY` value must exactly match the verify token you entered on the Meta dashboard. The package uses it to validate the webhook verification request from Meta.

*[back to top](#table-of-contents)*

Using the Package
-----------------

[](#using-the-package)

After installing, adjust the package configuration according to your Meta application, then run the migrations to create the tables and columns the package needs.

### Migrations

[](#migrations)

The package will create a `whatsapp_messages` table to store the messages (both inbound and outbound) handled by the package. It will also create a unique messageable phone column on your messageable entity (default: `phone` on the `users` table).

If you need to customize this, adjust the `database.*` keys in your `/config/whatsapp.php` file **before running the migrations**.

If your application owns its migrations manually, set `database.skip_migrations` to `true` in `config/whatsapp.php` and create equivalent application migrations.

To keep everything nice and tidy in your project, it is recommended (but not necessary) to publish the default migrations. You can do so by running the following command:

```
php artisan vendor:publish --tag=whatsapp-business-api-migrations
```

Then run your application migrations:

```
php artisan migrate
```

#### `whatsapp_messages` Table

[](#whatsapp_messages-table)

This table stores persisted WhatsApp message records — both inbound messages received from contacts and outbound messages sent by your application. Reactions are stored as payload metadata on the referenced message instead of being stored as independent message rows. Below is the schema:

ColumnTypeNullableDefaultDescription`id`bigint (auto-increment)No—Primary key`whatsapp_message_id`varcharNo—WhatsApp message ID (unique)`contact_phone_number`varcharNo—Phone number of the contact`{messageable_id_column}`foreignIdYes—FK to your messageable table (default: `user_id` → `users.id`), with cascade delete`way`enum (`inbound`, `outbound`)No—Message direction`status`enum (`pending`, `sent`, `delivered`, `read`, `failed`, `deleted`, `warning`)No`pending`Delivery status`type`enum (`audio`, `button`, `contacts`, `document`, `image`, `interactive`, `location`, `reaction`, `sticker`, `template`, `text`, `unsupported`, `video`)No—Message type`text`textYes—Text body (for text/button messages)`payload`jsonYes—Full message data including context, reactions, and template components`whatsapp_deleted_at`timestampYes—When Meta reported the message as deleted`created_at`timestampYes—Laravel managed`updated_at`timestampYes—Laravel managed`deleted_at`timestampYes—Laravel soft delete timestamp`delivered_at`timestampYes—When Meta confirmed delivery`read_at`timestampYes—When the contact read the message`sent_at`timestampYes—When Meta confirmed the message was sent**Indexes:** `whatsapp_message_id` (unique), `contact_phone_number`, `way`, `status`, `type`.

The table name defaults to `whatsapp_messages` but can be changed via `config('whatsapp.database.table_name')`. The `payload` column is automatically cast to a PHP array of payload entries on the `WhatsappMessage` model:

```
$message = WhatsappMessage::first();
$firstPayloadItem = $message->payload[0] ?? null;
```

You can also filter the payload by type using the model's helper methods:

```
// Get only context payload items for reply messages
$replyContext = $message->getPayloadType(ContextType::REPLY);

// Get all payload items except reaction data
$withoutReactions = $message->getPayloadWithoutType(MessageType::REACTION);
```

#### Messageable Migration

[](#messageable-migration)

The package also adds the configured messageable phone column to your messageable table (default: `phone` on `users`) so you can associate WhatsApp messages with your application's users. The migration adds:

ColumnTypeNullableUniqueDescription`{messageable_phone_column}`varcharYesYesContact's WhatsApp phone number (default: `phone`)`{messageable_phone_column}_verified_at`timestampYesNoPhone verification timestamp (default: `phone_verified_at`)The phone column name is configurable via `config('whatsapp.database.messageable_phone_column')` before running the migrations. The verification timestamp column is derived from it by appending `_verified_at`:

```
// config/whatsapp.php
'database' => [
    'messageable_phone_column' => 'phone',       // column name for the phone number
    'skip_migrations'          => false,          // set true to disable package-loaded migrations
    'table_name'               => 'whatsapp_messages',
    'users_table'              => 'users',        // table to alter
    'users_table_pk'           => 'id',            // primary key of the users table
    'messageable_id_column'    => 'user_id',       // FK column on whatsapp_messages
    'user_model'               => App\Models\User::class, // model used for lookups
],
```

> **Note:** If your application already has a `phone` column on the `users` table, change `messageable_phone_column` to a different name **before** running the migrations to avoid conflicts.

> **Note:** If you publish and customize the messageable migration, keep the `down()` method aligned with both columns. Only `{messageable_phone_column}` has a unique index by default.

### General Concepts

[](#general-concepts)

The package follows the same data structure as the [WhatsApp Business API](https://developers.facebook.com/docs/whatsapp/cloud-api). Understanding a few key concepts will help you work with it effectively:

#### Message Direction

[](#message-direction)

Every message stored in the `whatsapp_messages` table has a `way` column that indicates its direction:

- **Inbound** — messages received from a WhatsApp contact
- **Outbound** — messages sent by your application

#### Message Lifecycle

[](#message-lifecycle)

When Meta processes a message, it sends status updates via the webhook. The schema supports all statuses below, but the webhook handler currently updates timestamps only for `sent`, `delivered`, `read`, and `deleted`. Unhandled statuses, such as `failed` and `warning`, are valid column values but are logged instead of timestamped by the current handler.

StatusTimestamp ColumnMeaning`pending`—Message created but not yet confirmed by Meta`sent``sent_at`Meta confirmed the message was sent`delivered``delivered_at`Meta confirmed delivery to the contact's device`read``read_at`The contact read the message`deleted``whatsapp_deleted_at`Meta reported the message as deleted`failed`Not handledMeta could not deliver the message`warning`Not handledA non-critical issue was flagged#### The `payload` Column

[](#the-payload-column)

The `payload` JSON column stores structured data that varies by message type — context (replies, forwards), reaction data, template components, and button payloads. This allows the package to preserve the full message structure without requiring separate tables for each type.

#### Sending Messages

[](#sending-messages)

To send messages, add the `CanSendWhatsappMsg` trait to any Eloquent model (typically your `User` model). The model must expose the configured `messageable_phone_column` attribute, which defaults to `phone`, matching the contact's WhatsApp number:

```
use The42dx\Whatsapp\Enums\{MessageComponent, MessageType};
use The42dx\Whatsapp\Models\Traits\CanSendWhatsappMsg;

class User extends Authenticatable
{
    use CanSendWhatsappMsg;
}
```

Then you can send messages through the model:

```
$phoneColumn = config('whatsapp.database.messageable_phone_column', 'phone');
$user = User::where($phoneColumn, '+1234567890')->first();

// Send a text message
$user->sendWhatsappMsg(MessageType::TEXT, 'Hello from the SDK!');

// Send a template message
$user->sendWhatsappMsg(MessageType::TEMPLATE, [
    'name' => 'hello_world',
    'lang' => 'en_US',
]);

// React to a message
$user->sendWhatsappMsg(MessageType::REACTION, '👍', $originalMessage);
```

For template messages, `name` is required and must match the template name approved in Meta. `lang` is optional and falls back to `WHATSAPP_DEFAULT_TEMPLATE_LANGUAGE`.

You can also use the `WhatsappService` directly via dependency injection:

```
use The42dx\Whatsapp\Factories\WhatsappApiMessage;
use The42dx\Whatsapp\Services\WhatsappService;

public function __construct(
    protected WhatsappService $whatsapp,
) {}

public function send(): void
{
    $message = WhatsappApiMessage::compose('+1234567890')
        ->with(text: 'Hello from the SDK!');

    $response = $this->whatsapp->send($message);
}
```

Or resolve it from the container:

```
$whatsapp = app(WhatsappService::class);

$message = WhatsappApiMessage::make('+1234567890')
    ->withText('Hello from the SDK!');

$response = $whatsapp->send($message, $user);
```

#### Message Templates

[](#message-templates)

Template components are passed in the same structure used by `WhatsappApiMessage::withComponent()`. The `CanSendWhatsappMsg` trait expects each component to include a `type` and a `parameters` array. Use at most one component for each `MessageComponent` type, matching WhatsApp API expectations.

```
$user->sendWhatsappMsg(MessageType::TEMPLATE, [
    'name' => 'order_update',
    'lang' => 'en_US',
    'components' => [
        [
            'type' => MessageComponent::BODY,
            'parameters' => [
                ['name' => 'customer_name', 'text' => 'Rafael'],
                ['name' => 'order_code', 'text' => 'DX-123'],
            ],
        ],
        [
            'type' => MessageComponent::BUTTON,
            'subType' => MessageComponent::COPY_CODE,
            'index' => 0,
            'parameters' => [
                [
                    'type' => MessageComponent::COUPON_CODE,
                    'couponCode' => 'SAVE10',
                ],
            ],
        ],
    ],
]);
```

When using the builder directly, call `usingTemplate()` and add components with `withComponent()`:

```
$message = WhatsappApiMessage::compose('+1234567890')
    ->usingTemplate('order_update', 'en_US')
    ->withComponent(MessageComponent::BODY, [
        ['name' => 'customer_name', 'text' => 'Rafael'],
        ['name' => 'order_code', 'text' => 'DX-123'],
    ]);

$response = app(WhatsappService::class)->send($message, $user);
```

#### Available Templates

[](#available-templates)

Use `WhatsappService::getMessageTemplates()` to retrieve templates available for the configured WhatsApp Business Account:

```
use The42dx\Whatsapp\Services\WhatsappService;

$templates = app(WhatsappService::class)->getMessageTemplates();
```

The method returns the decoded API response as an array. If Meta returns an error, the package logs it and returns an empty array.

#### Message Support

[](#message-support)

Outbound support through `CanSendWhatsappMsg`:

`MessageType`SupportedBehavior`TEXT`YesSends text and stores an outbound `WhatsappMessage``TEMPLATE`YesSends template data and stores the template payload`REACTION`YesAdds or removes reaction payload on an existing stored message`AUDIO`, `BUTTON`, `CONTACTS`, `DOCUMENT`, `IMAGE`, `INTERACTIVE`, `LOCATION`, `STICKER`, `VIDEO`, `UNSUPPORTED`NoLogs a warning and does not call the APIInbound support through the webhook:

Inbound dataSupportedBehaviorText messagesYesStores text and base message dataButton repliesYesStores button text and payloadReactionsYesAdds or removes reaction payload on the referenced stored message without creating a separate rowContext and status updatesYesStores reply/forward metadata and delivery timestampsAudio, contacts, document, image, interactive, location, sticker, videoPartialStores the generic message row, logs unsupported type, and does not persist type-specific payload fields#### Whatsapp API Entities

[](#whatsapp-api-entities)

When Meta sends a webhook event, the payload follows a nested structure. The package maps this structure to a hierarchy of entity objects, each representing a level of the API response:

```
EventEntity
 └── EntryEntity[]
      └── ChangesEntity[]
           ├── ContactsEntity[]      (when field = MESSAGES)
           ├── MessageEntity[]       (when field = MESSAGES)
           └── StatusEntity[]        (when field = MESSAGES)
```

**Top-level entities:**

EntityKey PropertiesDescription`EventEntity``$object` (ObjectType), `$entries` (Collection)Root of every webhook event`EntryEntity``$id` (string), `$changes` (Collection)Represents a WhatsApp Business Account entry`ChangesEntity``$field` (ApiEvent), `$value` (Entity)A single change — the `$field` determines the value type**Message-related entities (under `ChangesEntity` when `$field` is `MSGS`):**

EntityKey PropertiesDescription`MessagesEntity``$contacts`, `$messages`, `$statuses`, `$waId`, `$phone`Container for all message data in a single change`ContactsEntity``$name`, `$waId`Contact profile info from an inbound message`MessageEntity``$id`, `$from`, `$type`, `$text`, `$timestamp`, `$context`, + type-specific propsA single inbound or outbound message`StatusEntity``$id`, `$recipientNumber`, `$status`, `$timestamp`A delivery status update**Message sub-entities (accessed via `MessageEntity` properties):**

EntityAccessed ViaKey Properties`ContextEntity``$message->context``$id`, `$from`, `$type` (REPLY, FWD, F\_FWD)`ReactionEntity``$message->reaction``$emoji`, `$messageId``ButtonEntity``$message->button``$text`, `$payload``LocationEntity``$message->location``$latitude`, `$longitude`, `$address`, `$name``ContactEntity``$message->contacts``$name`, `$phones`, `$emails`, `$addresses`, `$org``ImageEntity``$message->image``$id`, `$caption`, `$mimeType``VideoEntity``$message->video``$id`, `$caption`, `$mimeType``DocumentEntity``$message->document``$id`, `$caption`, `$filename`, `$mimeType``AudioEntity``$message->audio``$id`, `$mimeType``StickerEntity``$message->sticker``$id`, `$type` (STATIC, ANIMATED)All entities implement the `Entity` contract, providing `toArray()` and `toJson()` methods for serialization, and dynamic property access via `__get()`.

*[back to top](#table-of-contents)*

### Meta Webhook Validation

[](#meta-webhook-validation)

When you configure a webhook in the Meta Dashboard, Meta sends a `GET` request to verify your endpoint. The package handles this automatically via the `WebhookController@check` route.

**How it works:**

1. Meta sends a `GET` request with `hub_mode`, `hub_verify_token`, and `hub_challenge` query parameters
2. The `WebhookCheckRequest` form request validates all three:
    - `hub_mode` — must be `subscribe` (enforced by the `HubMode` rule)
    - `hub_verify_token` — must match your `config('whatsapp.webhook_verify')` value (enforced by the `VerifyToken` rule)
    - `hub_challenge` — must be a present string
3. If validation passes, the controller responds with the `hub_challenge` value — this confirms to Meta that your endpoint is valid

**Setup in Meta Dashboard:**

1. Set `WHATSAPP_WEBHOOK_VERIFY` in your `.env` to a secret string of your choice
2. In the Meta Dashboard → WhatsApp → Configuration → Webhook, enter your callback URL and the same verify token
3. Meta will send the verification request — if the token matches, the webhook is activated

If verification fails, check that the `WHATSAPP_WEBHOOK_VERIFY` value in `.env` exactly matches what you entered in the Meta Dashboard.

*[back to top](#table-of-contents)*

### Receiving Messages

[](#receiving-messages)

When Meta forwards a webhook event, the `WebhookController@handle` endpoint processes it automatically. The incoming `POST` request is validated by `ApiEventRequest` (requires `object` to be a valid ObjectType and `entry` to be a non-empty array), then the payload is parsed into the entity hierarchy.

**Event routing:**

The controller's `hookRouter` dispatches each `ChangesEntity` based on its `$field`:

`ApiEvent` fieldHandlerBehavior`MSGS``handleMessages()`Processes inbound messages and status updatesAll other fields`handleDefault()`Logs a warning and ignores the event**Message processing (`MSGS` field):**

1. **Inbound messages** — Each `MessageEntity` is processed by `handleMessage()`:

    - Creates or updates a `WhatsappMessage` model with `way = INBOUND`, the contact's phone number, type, and WhatsApp message ID
    - Attempts to associate the message with a user by matching `from` against the configurable `messageable_phone_column`
    - Dispatches to a type-specific handler:

    `MessageType`HandlerBehavior`TEXT``handleText()`Stores the text body`REACTION``handleReaction()`Adds or removes reaction payload on the referenced stored message without creating a separate row`BUTTON``handleButton()`Stores the button text/payload`AUDIO`, `CONTACTS`, `DOCUMENT`, `IMAGE`, `INTERACTIVE`, `LOCATION`, `STICKER`, `VIDEO`—Stores generic message data and logs an unsupported-type warning
    - Processes context (reply/forward) via `handleContext()`, storing it in the `payload` column
    - Persists via `updateOrCreate` using `whatsapp_message_id` + `contact_phone_number` as the unique key
2. **Status updates** — Each `StatusEntity` is processed by `handleStatus()`:

    - Looks up the existing `WhatsappMessage` by `whatsapp_message_id`
    - Updates the status and sets the corresponding timestamp (`sent_at`, `delivered_at`, `read_at`, `whatsapp_deleted_at`)
    - Logs a warning if the message is not found in the database

*[back to top](#table-of-contents)*

Run and Test
------------

[](#run-and-test)

We included a sample fresh Laravel app to help those who want to contribute to the package. To start it just go through the following steps:

1. Clone this repository.
2. Run `composer setup` from the repository root. It enters the sample app, installs dependencies, creates `.env` when missing, generates the app key, starts Sail, waits for MySQL, runs migrations, and returns to the root folder.
3. Run `composer start` from the repository root. It will run the sample app through `laravel sail`.
4. With the local server running, from another terminal run `composer connect` from the repository. It will connect to [pinggy](https://pinggy.io) service and expose your local application to the web.
5. Adjust your webhook configuration on the Meta/Facebook dashboard with the generated [pinggy](https://pinggy.io) URI.

Tooling
-------

[](#tooling)

There are a few tools we provide alongside with the source code to ease a little bit the burden of following a bunch of patterns and standards we set up, as well as automate some boring processes.

### Comitizen

[](#comitizen)

This CLI helps with writting commit messages in a meaningful and standardized way, so that our automation process can use them to properly write our software changelog.

If you like the tool, [kudo the devs](https://github.com/lintingzhen/commitizen-go) ;)

#### How to use Comitzen

[](#how-to-use-comitzen)

Just run `composer commit` from the repository's root folder instead of the traditional `git commit` and follow the CLI interactive steps :)

### Pinggy

[](#pinggy)

This service allows routing external requests to your local environment. You can use it to test your Watsapp webooks locally while developing.

If you like the tool, [kudos the devs](https://pinggy.io) ;)

#### How to use Pinggy

[](#how-to-use-pinggy)

Just run `composer connect` from the repository's root folder. You will need to have our sample application running so you can receive webhook requests locally. After running the composer command, update your [Meta Developer Dashboard](https://developers.facebook.com/). Follow the process on [Meta Dashboard Setup](#meta-dashboard-setup) section of this README.

Contributors
------------

[](#contributors)

Kudos to all our dear contributors. Without them, nothing would have been possible ❤️

   [![Rafael Eduardo Paulin](https://avatars.githubusercontent.com/u/13452406?v=4?s=60)
**Rafael Eduardo Paulin**](https://github.com/rafapaulin)
[💻](https://github.com/42dx/whatsapp-laravel-sdk/commits?author=rafapaulin "Code") [🎨](#design-rafapaulin "Design") [📖](https://github.com/42dx/whatsapp-laravel-sdk/commits?author=rafapaulin "Documentation") [🤔](#ideas-rafapaulin "Ideas, Planning, & Feedback") [🚇](#infra-rafapaulin "Infrastructure (Hosting, Build-Tools, etc)") [🚧](#maintenance-rafapaulin "Maintenance") [📆](#projectManagement-rafapaulin "Project Management") [👀](https://github.com/42dx/whatsapp-laravel-sdk/pulls?q=is%3Apr+reviewed-by%3Arafapaulin "Reviewed Pull Requests") [⚠️](https://github.com/42dx/whatsapp-laravel-sdk/commits?author=rafapaulin "Tests") [🔧](#tool-rafapaulin "Tools") [✅](#tutorial-rafapaulin "Tutorials")  Would you like to see your profile here? Take a look on our [Code of Conduct](https://github.com/42dx/.github/blob/main/CODE_OF_CONDUCT.md) and our [Contributing](https://github.com/42dx/.github/blob/main/CONTRIBUTING.md) docs, and start coding! We would be thrilled to review a PR of yours! 💯

*[back to top](#table-of-contents)*

Changelog
---------

[](#changelog)

All changes made to this package since the start of development can be found either on our [release list](https://github.com/42dx/whatsapp-laravel-sdk/releases) or on the [changelog](CHANGELOG.md).

*[back to top](#table-of-contents)*

Roadmap
-------

[](#roadmap)

Any planned enhancement to the package will be described and tracked in our [project page](https://github.com/orgs/42dx/projects/3)

*[back to top](#table-of-contents)*

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance78

Regular maintenance activity

Popularity11

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 86.7% 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 ~103 days

Recently: every ~11 days

Total

8

Last Release

9d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/147879070?v=4)[42 Developer Experience](/maintainers/42dx)[@42dx](https://github.com/42dx)

---

Top Contributors

[![rafapaulin](https://avatars.githubusercontent.com/u/13452406?v=4)](https://github.com/rafapaulin "rafapaulin (85 commits)")[![semantic-release-bot](https://avatars.githubusercontent.com/u/32174276?v=4)](https://github.com/semantic-release-bot "semantic-release-bot (8 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (3 commits)")[![allcontributors[bot]](https://avatars.githubusercontent.com/in/23186?v=4)](https://github.com/allcontributors[bot] "allcontributors[bot] (2 commits)")

---

Tags

automationfacebookintegrationlaravellaravel-packagemessagingmetaphpsdkwhatsapp

###  Code Quality

TestsPHPUnit

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/42dx-whatsapp-laravel-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/42dx-whatsapp-laravel-sdk/health.svg)](https://phpackages.com/packages/42dx-whatsapp-laravel-sdk)
```

###  Alternatives

[exsyst/swagger

A php library to manipulate Swagger specifications

35916.4M7](/packages/exsyst-swagger)[hubspot/api-client

Hubspot API client

24016.2M20](/packages/hubspot-api-client)[pocketmine/bedrock-protocol

An implementation of the Minecraft: Bedrock Edition protocol in PHP

172445.0k16](/packages/pocketmine-bedrock-protocol)[botman/driver-telegram

Telegram driver for BotMan

93459.5k6](/packages/botman-driver-telegram)

PHPackages © 2026

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