PHPackages                             telmodev/cloud-api-whatsapp - 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. telmodev/cloud-api-whatsapp

ActiveLibrary

telmodev/cloud-api-whatsapp
===========================

Laravel SDK for the Meta WhatsApp Cloud API — send messages, manage templates, handle webhooks and more

v1.0.0(today)00MITPHP ^8.2|^8.3

Since Jul 22Compare

[ Source](https://github.com/telmoDev/cloud-api-whatsapp)[ Packagist](https://packagist.org/packages/telmodev/cloud-api-whatsapp)[ Docs](https://github.com/telmoDev/cloud-api-whatsapp)[ RSS](/packages/telmodev-cloud-api-whatsapp/feed)WikiDiscussions Synced today

READMEChangelogDependencies (3)Versions (2)Used By (0)

Laravel WhatsApp Cloud API Client
=================================

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

[![Latest Version on Packagist](https://camo.githubusercontent.com/dcba60c91cfd5ac46428f50595371506f6fa623c0cfcc1bf3ea860cf1fe75eb6/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f74656c6d6f6465762f636c6f75642d6170692d77686174736170702e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/telmodev/cloud-api-whatsapp)[![Total Downloads](https://camo.githubusercontent.com/20c60d95e135f6452a9619f63f93d983aef12b28cb7b943d7ec9e245c83c19de/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f74656c6d6f6465762f636c6f75642d6170692d77686174736170702e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/telmodev/cloud-api-whatsapp)[![Software License](https://camo.githubusercontent.com/55c0218c8f8009f06ad4ddae837ddd05301481fcf0dff8e0ed9dadda8780713e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE)

A simple, clean, and elegant Laravel package to interact with the Meta WhatsApp Cloud API.

Features
--------

[](#features)

- ⚡️ Seamless integration with Laravel's HTTP Client (`Http::`) and facades
- 📱 Dynamic configuration — swap token or phone number ID on the fly (multi-tenant support)
- ✉️ Text messages, template messages, replies, and emoji reactions
- 🔘 Interactive messages — reply buttons and list menus
- 🖼️ Media — images, videos, audio, documents, stickers (upload, send, delete)
- 📍 Location sharing and contact cards
- 🏢 Business Profile management (read and update)
- 📋 Template management — list, create, and delete message templates
- 🔔 Webhook handling — challenge verification and HMAC-SHA256 signature validation
- 🧪 Fully testable with Laravel's HTTP mocking

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

[](#requirements)

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

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

[](#installation)

Install the package via Composer:

```
composer require telmodev/cloud-api-whatsapp
```

The Service Provider and Facade are registered automatically via Laravel's package auto-discovery. No manual changes to `config/app.php` are needed.

### Publish the configuration file

[](#publish-the-configuration-file)

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

This creates `config/cloud-api-whatsapp.php` in your project with all available options.

Configuration
-------------

[](#configuration)

Add these variables to your `.env` file:

```
WHATSAPP_TOKEN="your-meta-system-user-access-token"
WHATSAPP_PHONE_NUMBER_ID="your-phone-number-id"
WHATSAPP_BUSINESS_ACCOUNT_ID="your-whatsapp-business-account-id"
WHATSAPP_API_VERSION="v20.0"
WHATSAPP_TIMEOUT=30
```

`WHATSAPP_BUSINESS_ACCOUNT_ID` is required for template management endpoints. All other template and messaging endpoints only need `WHATSAPP_PHONE_NUMBER_ID`.

---

Usage
-----

[](#usage)

All examples use the `CloudApiWhatsapp` facade. You can also resolve the class via dependency injection.

```
use Telmo\CloudApiWhatsapp\Facades\CloudApiWhatsapp;
```

---

### Text Messages

[](#text-messages)

```
// Simple text
CloudApiWhatsapp::sendMessage('+1234567890', 'Hello from Laravel!');

// With link preview
CloudApiWhatsapp::sendMessage('+1234567890', 'Check this: https://laravel.com', [
    'preview_url' => true,
]);
```

### Reply to a Message

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

Quote a previous message in the conversation thread.

```
CloudApiWhatsapp::replyToMessage(
    to: '+1234567890',
    body: 'Got your message, we will look into it!',
    replyMessageId: 'wamid.HBgLMTIzNDU2Nzg5MA=='
);
```

### Emoji Reactions

[](#emoji-reactions)

React to a received message. Pass an empty string to remove an existing reaction.

```
// Add a reaction
CloudApiWhatsapp::sendReaction('wamid.HBgLMTIzNDU2Nzg5MA==', '👍');

// Remove a reaction
CloudApiWhatsapp::sendReaction('wamid.HBgLMTIzNDU2Nzg5MA==', '');
```

### Template Messages

[](#template-messages)

Required for business-initiated conversations (outside the 24-hour customer window).

```
CloudApiWhatsapp::sendTemplate(
    to: '+1234567890',
    templateName: 'order_confirmation',
    languageCode: 'en_US',
    components: [
        [
            'type' => 'body',
            'parameters' => [
                ['type' => 'text', 'text' => 'ORD-98765'],
                ['type' => 'text', 'text' => '$49.99'],
            ],
        ],
    ]
);
```

---

### Interactive Messages

[](#interactive-messages)

#### Reply Buttons (up to 3)

[](#reply-buttons-up-to-3)

```
CloudApiWhatsapp::sendButtons(
    to: '+1234567890',
    body: 'Would you like to confirm your appointment?',
    buttons: [
        ['id' => 'confirm', 'title' => 'Yes, confirm'],
        ['id' => 'cancel',  'title' => 'No, cancel'],
    ],
    header: 'Appointment Reminder',  // optional
    footer: 'Reply anytime'          // optional
);
```

#### List Menu

[](#list-menu)

```
CloudApiWhatsapp::sendList(
    to: '+1234567890',
    body: 'Please select a support category',
    buttonLabel: 'View categories',
    sections: [
        [
            'title' => 'Technical',
            'rows' => [
                ['id' => 'cat_billing',  'title' => 'Billing',       'description' => 'Invoices and payments'],
                ['id' => 'cat_account',  'title' => 'My Account',    'description' => 'Login, password, profile'],
            ],
        ],
        [
            'title' => 'General',
            'rows' => [
                ['id' => 'cat_other', 'title' => 'Other', 'description' => 'Anything else'],
            ],
        ],
    ],
    header: 'Support',   // optional
    footer: 'We\'re here to help'  // optional
);
```

---

### Media

[](#media)

You can send media using a public URL or a Meta Media ID obtained after uploading.

#### Images

[](#images)

```
// By URL
CloudApiWhatsapp::sendImage('+1234567890', 'https://example.com/banner.png', 'Summer sale!');

// By Media ID
CloudApiWhatsapp::sendImage('+1234567890', 'your-media-id');
```

#### Documents

[](#documents)

```
CloudApiWhatsapp::sendDocument(
    to: '+1234567890',
    documentUrlOrId: 'https://example.com/invoice.pdf',
    filename: 'Invoice-July.pdf',  // optional, only applied for URL-based documents
    caption: 'Your July invoice'   // optional
);
```

#### Video

[](#video)

```
CloudApiWhatsapp::sendVideo('+1234567890', 'https://example.com/intro.mp4', 'Intro video');
```

#### Audio

[](#audio)

```
CloudApiWhatsapp::sendAudio('+1234567890', 'https://example.com/voice.ogg');
```

#### Stickers

[](#stickers)

Stickers must be in `.webp` format.

```
CloudApiWhatsapp::sendSticker('+1234567890', 'https://example.com/sticker.webp');
// or by Media ID
CloudApiWhatsapp::sendSticker('+1234567890', 'your-sticker-media-id');
```

#### Upload, retrieve, and delete media

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

```
// Upload a local file and get back a Media ID
$response = CloudApiWhatsapp::uploadMedia(
    filePath: storage_path('app/invoice.pdf'),
    mimeType: 'application/pdf'
);
$mediaId = $response->json('id');

// Get metadata (includes temporary download URL)
$response = CloudApiWhatsapp::getMedia($mediaId);
$downloadUrl = $response->json('url');

// Delete
CloudApiWhatsapp::deleteMedia($mediaId);
```

---

### Location

[](#location)

```
CloudApiWhatsapp::sendLocation(
    to: '+1234567890',
    latitude: 37.7749,
    longitude: -122.4194,
    name: 'Salesforce Tower',    // optional
    address: 'San Francisco, CA' // optional
);
```

### Contacts

[](#contacts)

```
CloudApiWhatsapp::sendContact('+1234567890', [
    [
        'name' => [
            'first_name'     => 'Jane',
            'last_name'      => 'Doe',
            'formatted_name' => 'Jane Doe',
        ],
        'phones' => [
            ['phone' => '+1987654321', 'type' => 'MOBILE'],
        ],
        'emails' => [
            ['email' => 'jane@example.com', 'type' => 'WORK'],
        ],
    ],
]);
```

### Mark as Read

[](#mark-as-read)

```
CloudApiWhatsapp::markAsRead('wamid.HBgLMTIzNDU2Nzg5MA==');
```

### Raw Payload

[](#raw-payload)

For advanced use cases not covered by a dedicated method:

```
CloudApiWhatsapp::sendRaw([
    'messaging_product' => 'whatsapp',
    'to' => '1234567890',
    'type' => 'text',
    'text' => ['body' => 'Custom payload'],
]);
```

---

### Business Profile

[](#business-profile)

```
// Read profile (returns about, address, description, email, websites, vertical, profile_picture_url)
$response = CloudApiWhatsapp::getBusinessProfile();

// Read specific fields only
$response = CloudApiWhatsapp::getBusinessProfile(['about', 'email']);

// Update profile
CloudApiWhatsapp::updateBusinessProfile([
    'about'    => 'We ship in 24 hours.',
    'email'    => 'support@yourcompany.com',
    'websites' => ['https://yourcompany.com'],
    'vertical' => 'RETAIL',
]);
```

---

### Template Management

[](#template-management)

Requires `WHATSAPP_BUSINESS_ACCOUNT_ID` to be set.

```
// List all approved templates
$response = CloudApiWhatsapp::getTemplates(['status' => 'APPROVED']);

// List by name
$response = CloudApiWhatsapp::getTemplates(['name' => 'order_confirmation']);

// Create a new template
CloudApiWhatsapp::createTemplate([
    'name'       => 'order_shipped',
    'language'   => 'en_US',
    'category'   => 'UTILITY',
    'components' => [
        ['type' => 'BODY', 'text' => 'Your order {{1}} has shipped and will arrive by {{2}}.'],
    ],
]);

// Delete a template by name
CloudApiWhatsapp::deleteTemplate('old_promo_template');
```

---

### Webhooks

[](#webhooks)

#### 1. Verify the webhook subscription (GET endpoint)

[](#1-verify-the-webhook-subscription-get-endpoint)

Meta sends a GET request to your webhook URL to verify ownership. Return the challenge value as a plain text response.

```
// routes/web.php or routes/api.php
Route::get('/webhook/whatsapp', function (Request $request) {
    try {
        $challenge = CloudApiWhatsapp::verifyWebhook(
            queryParams: $request->query(),
            verifyToken: config('services.whatsapp.verify_token')
        );
        return response($challenge, 200)->header('Content-Type', 'text/plain');
    } catch (\InvalidArgumentException $e) {
        abort(403, $e->getMessage());
    }
});
```

#### 2. Process incoming events (POST endpoint)

[](#2-process-incoming-events-post-endpoint)

Meta sends a POST request with an HMAC-SHA256 signature in the `X-Hub-Signature-256` header. Always verify it before processing.

```
Route::post('/webhook/whatsapp', function (Request $request) {
    try {
        $entries = CloudApiWhatsapp::parseWebhook(
            rawBody:   $request->getContent(),
            signature: $request->header('X-Hub-Signature-256'),
            appSecret: config('services.whatsapp.app_secret')
        );
    } catch (\InvalidArgumentException $e) {
        abort(403, $e->getMessage());
    }

    foreach ($entries as $entry) {
        foreach ($entry['changes'] as $change) {
            $messages = $change['value']['messages'] ?? [];
            foreach ($messages as $message) {
                // Handle $message['type'], $message['text']['body'], etc.
            }
        }
    }

    return response('EVENT_RECEIVED', 200);
});
```

---

Error Handling
--------------

[](#error-handling)

Every API method returns an `Illuminate\Http\Client\Response` object. The SDK does **not** throw exceptions on 4xx/5xx responses from Meta — you decide how to handle them.

### Checking the response

[](#checking-the-response)

```
$response = CloudApiWhatsapp::sendMessage('+1234567890', 'Hello!');

if ($response->failed()) {
    $error = $response->json('error');
    // $error['code']    — numeric Meta error code
    // $error['message'] — human-readable description
    // $error['type']    — e.g. OAuthException, GraphMethodException
}
```

### Common Meta error codes

[](#common-meta-error-codes)

CodeMeaningAction0Unknown / generic errorCheck message for details10App does not have permissionReview app permissions in Meta dashboard100Invalid parameterCheck the request payload130429Rate limit hitBack off and retry after a delay131030Recipient phone number not on WhatsAppVerify the number before sending131047Re-engagement message not allowedSend a template to re-open the window131051Message type unsupported for this recipientTry a different message type190Access token expired or invalidRefresh or regenerate the token### Throwing on failure

[](#throwing-on-failure)

If you prefer exceptions over manual checks, chain `->throw()`:

```
// Throws Illuminate\Http\Client\RequestException on any 4xx/5xx
$response = CloudApiWhatsapp::sendMessage('+1234567890', 'Hello!')->throw();

// Or handle specific status codes
$response = CloudApiWhatsapp::sendMessage('+1234567890', 'Hello!')
    ->throwIf(fn($r) => $r->status() === 401, new \RuntimeException('Token expired'));
```

### Network errors

[](#network-errors)

Timeouts and connection failures throw `Illuminate\Http\Client\ConnectionException` regardless of the `->throw()` chain. Catch it at the boundary where you call the SDK:

```
use Illuminate\Http\Client\ConnectionException;

try {
    $response = CloudApiWhatsapp::sendMessage('+1234567890', 'Hello!');
} catch (ConnectionException $e) {
    // Log and retry, or alert your team
}
```

### SDK-level exceptions

[](#sdk-level-exceptions)

`InvalidArgumentException` is thrown before any HTTP request is made in these cases:

- Missing or empty `WHATSAPP_TOKEN` or `WHATSAPP_PHONE_NUMBER_ID`
- Missing `WHATSAPP_BUSINESS_ACCOUNT_ID` when calling template management methods
- Phone number contains fewer than 7 digits after stripping non-numeric characters
- File path passed to `uploadMedia()` does not exist
- Webhook verification token mismatch or invalid `hub.mode`
- Webhook payload signature does not match the expected HMAC-SHA256 hash

These are programmer-configuration errors. They should surface during development, not be silently swallowed in production.

---

Dynamic Configuration (Multi-tenant)
------------------------------------

[](#dynamic-configuration-multi-tenant)

Override the default token or phone number ID per request. The facade returns a cloned instance — the singleton is never mutated.

```
$client = CloudApiWhatsapp::withToken('tenant-access-token')
    ->withPhoneNumberId('tenant-phone-number-id');

$client->sendMessage('+1234567890', 'Message from tenant account.');
```

---

Testing
-------

[](#testing)

Since the package uses Laravel's native HTTP client, testing requires no real API connections:

```
use Illuminate\Support\Facades\Http;
use Telmo\CloudApiWhatsapp\Facades\CloudApiWhatsapp;

public function test_sends_message(): void
{
    Http::fake([
        'graph.facebook.com/*' => Http::response([
            'messages' => [['id' => 'wamid.mock123']],
        ], 200),
    ]);

    $response = CloudApiWhatsapp::sendMessage('+1234567890', 'Hello!');

    $this->assertTrue($response->successful());

    Http::assertSent(function ($request) {
        return $request['to'] === '1234567890'
            && $request['text']['body'] === 'Hello!';
    });
}
```

Run the package's own test suite:

```
composer test
```

---

License
-------

[](#license)

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

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

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

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/15f5c253776a5dcdef4d32b9461294d05c0b7af0f21f00906d5e73d6aaea261c?d=identicon)[telmoDev](/maintainers/telmoDev)

---

Tags

laravelfacebookwhatsappmetacloud-api

###  Code Quality

TestsPHPUnit

### Embed Badge

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

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

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)[api-platform/laravel

API Platform support for Laravel

58174.6k17](/packages/api-platform-laravel)

PHPackages © 2026

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