Search by

ziming / laravel-crisp-whatsapp

ziming.opensource

This is my package laravel-crisp-whatsapp

Package info

github.com/ziming/laravel-crisp-whatsapp

pkg:composer/ziming/laravel-crisp-whatsapp

Fund package maintenance!

ziming

Statistics

Installs: 20 772

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

1.2.1 2026-05-30 15:35 UTC

This package is auto-updated.

Last update: 2026-09-21 04:54:03 UTC


README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

Send WhatsApp Messages or Notifications with Crisp Chat!

Support me

You can use my referral link to sign up for Crisp Chat free account. At no extra costs to you, I get a small reward if you upgrade to a paid plan at the Essentials or Plus Plan in the future.

I highly recommend Crisp Chat if you are looking for a chat support SaaS for your website. As it charges a flat monthly fee instead of charging by per seat. It has a very nice UI, powerful bot builder & healthy plugins ecosystem as well.

Side Note: Looking to integrate with Crisp in non WhatsApp ways? Check out my Laravel Crisp package too!

Installation

You can install the package via composer:

composer require ziming/laravel-crisp-whatsapp

You may publish the config file with:

php artisan vendor:publish --tag="crisp-whatsapp-config"

You may also publish the migration file if you want to log your whatsapp requests with

php artisan vendor:publish --tag="crisp-whatsapp-migrations"```

And set CRISP_WHATSAPP_LOG_REQUESTS in your .env file to true

This is the contents of the published config file:

declare(strict_types=1);

return [
    'website_id' => env('CRISP_WEBSITE_ID'),

    'base_url' => env('CRISP_BASE_URL', 'https://plugins.crisp.chat/urn:crisp.im:whatsapp:0/wa/api/website/'),
    'access_key_id' => env('CRISP_WHATSAPP_ACCESS_KEY_ID'),
    'secret_access_key' => env('CRISP_WHATSAPP_SECRET_ACCESS_KEY'),
    'from_phone' => env('CRISP_WHATSAPP_FROM_PHONE'),

    // change it to false when you are ready for production
    'test_mode' => env('CRISP_WHATSAPP_TEST_MODE', true),

    // when test_mode is true, all whatsapp notifications will go to this number
    'to_test_phone' => env('CRISP_WHATSAPP_TO_TEST_PHONE'),
    
    'enable_caching' => env('CRISP_WHATSAPP_ENABLE_CACHE', true),
    
    // if you want to log whatsapp requests, you will need to publish migration if this is true
    'log_requests' => env('CRISP_WHATSAPP_LOG_REQUESTS', false),
];

Quick Usage (More documentation in the future!)

Here are some examples on how you can use it in a laravel notification class.

Quick Example (Recommended as it greatly simplify things as most of the whatsapp template is auto generated for you after fetching)

declare(strict_types=1);

use \Illuminate\Http\Client\ConnectionException;
use Ziming\LaravelCrispWhatsApp\Enums\ParameterTypeEnum;
use Ziming\LaravelCrispWhatsApp\CrispWhatsAppChannel;
use Ziming\LaravelCrispWhatsApp\Enums\ParameterTypeEnum;
use Ziming\LaravelCrispWhatsApp\CrispWhatsAppMessage;
use Ziming\LaravelCrispWhatsApp\Interfaces\CrispWhatsAppNotification;
use Ziming\LaravelCrispWhatsApp\CanReceiveCrispWhatsAppNotification;
use Ziming\LaravelCrispWhatsApp\Factories\ComponentParameterFactory;
use Ziming\LaravelCrispWhatsApp\Factories\ComponentFactory;
use Ziming\LaravelCrispWhatsApp\Enums\ButtonSubTypeEnum;
use Ziming\LaravelCrispWhatsApp\LaravelCrispWhatsApp;
use Ziming\LaravelCrispWhatsApp\Facades\LaravelCrispWhatsApp as LaravelCrispWhatsAppFacade;

class OrderShippedNotification extends Notification implements CrispWhatsAppNotification
{
    use Queueable;

    public function via(CanReceiveCrispWhatsAppNotification $notifiable): array
    {
        return [
            CrispWhatsAppChannel::class;
        ];
    }

    /**
     * @throws ConnectionException
     */
    public function toCrispWhatsApp(CanReceiveCrispWhatsAppNotification $notifiable): CrispWhatsAppMessage
    {
        $templateArray = LaravelCrispWhatsAppFacade::getMessageTemplateArray('hello_world');

        return CrispWhatsAppMessageFactory::createFromTemplateArray(
            $templateArray,
            [
                ComponentParameterFactory::text('Crispy Fries'),
            ],
        );
    }
}

Detailed Example (If you want a lot more control over your whatsapp message)

declare(strict_types=1);

use \Illuminate\Http\Client\ConnectionException;
use Ziming\LaravelCrispWhatsApp\Enums\ParameterTypeEnum;
use Ziming\LaravelCrispWhatsApp\CrispWhatsAppChannel;
use Ziming\LaravelCrispWhatsApp\Enums\ParameterTypeEnum;
use Ziming\LaravelCrispWhatsApp\CrispWhatsAppMessage;
use Ziming\LaravelCrispWhatsApp\Interfaces\CrispWhatsAppNotification;
use Ziming\LaravelCrispWhatsApp\CanReceiveCrispWhatsAppNotification;
use Ziming\LaravelCrispWhatsApp\Factories\ComponentParameterFactory;
use Ziming\LaravelCrispWhatsApp\Factories\ComponentFactory;
use Ziming\LaravelCrispWhatsApp\Enums\ButtonSubTypeEnum;
use Ziming\LaravelCrispWhatsApp\LaravelCrispWhatsApp;
use Ziming\LaravelCrispWhatsApp\Facades\LaravelCrispWhatsApp as LaravelCrispWhatsAppFacade;

class OrderShippedNotification extends Notification implements CrispWhatsAppNotification
{
    use Queueable;

    public function via(CanReceiveCrispWhatsAppNotification $notifiable): array
    {
        return [
            CrispWhatsAppChannel::class;
        ];
    }

    /**
     * @throws ConnectionException
     */
    public function toCrispWhatsApp(CanReceiveCrispWhatsAppNotification $notifiable): CrispWhatsAppMessage
    {
        // See the source code for more methods on CrispWhatsAppMessage!
        $crispMessages = []
        
        // Example 1
        $crispMessages[] =  CrispWhatsAppMessage::make()
            ->templateLanguage('en')
            ->toNumber($notifiable->mobile_phone)
            ->templateName('template-name')
            ->addTemplateBodyComponent(
                LaravelCrispWhatsApp::make()->getMessageTemplateBodyText('template-name'),
                [
                    ComponentParameterFactory::text('Crisp'),
                ]
            )
            ->addTemplateFooterComponent(
                // you may use the facade as well!
                LaravelCrispWhatsAppFacade::getMessageTemplateFooterText('template-name')
            )
            ->addTemplateButtonComponent('CTA', ButtonSubTypeEnum::Url)
            ->addTemplateButtonComponent('Not interested anymore');
            
           // Example 2
           $crispMessages[] =  CrispWhatsAppMessage::make()
                ->rawMessageTemplate([
                    'language' => 'en_US',
                    'name' => 'hello_world',
                    'components' => [
                        [
                            'type' => 'HEADER',
                            'FORMAT' => 'TEXT',
                            'text' => 'Hello World',
                        ],
                        [
                            'type' => 'BODY',
                            'text' => 'This is a body text',
                        ],
                        [
                            'type' => 'FOOTER',
                            'text' => 'This is a footer text',
                        ],
                    ],
                ]);
                
            // Example 3
            $crispMessages[] => CrispWhatsAppMessage::make()
                ->templateLanguage('en')
                ->toNumber($notifiable->mobile_phone)
                ->rawTemplateComponents([
                    ComponentFactory::headerText('Order #12345'),
                    ComponentFactory::body('{{1}} the Builder', ComponentParameterFactory::text('Crisp')),
                    ComponentFactory::button('Call To Action', ButtonSubTypeEnum::Url)
                    ComponentFactory::button('No', ButtonSubTypeEnum::QuickReply)
                    ComponentFactory::footer('This is the footer of your whatsapp template'),
                ]);
         
         return $crispMessages[random_int(0, 2)];
    }
}

Then in your model classes that can receive WhatsApp notifications, you can use the CanReceiveWhatsAppNotification trait:

Below is an example:

use Illuminate\Database\Eloquent\Model;
use Ziming\LaravelCrispWhatsApp\Interfaces\CanReceiveCrispWhatsAppNotification;

class User extends Model implements CanReceiveCrispWhatsAppNotification
{
    public function routeNotificationForCrispWhatsApp(): string
    {
        return $this->attributes['mobile_phone'];
    }
}

WhatsApp API callbacks

Publish and run the migrations before enabling the callback handler:

php artisan vendor:publish --tag=crisp-whatsapp-migrations
php artisan migrate

The upgrade uses separate migrations for lifecycle columns and callback history. Existing callback_response_* columns remain populated for compatibility. If you manually applied the earlier unreleased combined migration, reconcile your migration history before running these split migrations; they are intended for the original schema.

Your application's callback controller can delegate persistence to the package:

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Ziming\LaravelCrispWhatsApp\Actions\RecordCrispWhatsAppCallback;

public function __invoke(Request $request, RecordCrispWhatsAppCallback $recordCallback): Response
{
    if ($request->input('request_id') === null) {
        return response()->noContent();
    }

    $recordCallback($request->json()->all());

    return response()->noContent();
}

The action validates the payload, stores its full contents, deduplicates retries with canonical JSON hashing, and reconciles the parent log under a transaction and row lock. Successful reply/session callbacks take precedence over pending callbacks, regardless of delivery order. Failed and unknown callbacks are preserved without advancing lifecycle state. The legacy fields represent the most advanced successful lifecycle callback when available; inspect event history for subsequent errors and other events.

Callbacks arriving before a send log are stored without a parent. Normal Eloquent creation of the send log reconciles them automatically; duplicate deliveries also retry reconciliation. For logs inserted outside Eloquent, call $recordCallback->reconcile($requestId) after insertion. The action does not authenticate requests; retain the application's configured webhook authentication. It does not create routes or change the Crisp callback URL.

Schedule pruning explicitly for both package models (Laravel's default app model discovery does not include these vendor models):

use Illuminate\Support\Facades\Schedule;
use Ziming\LaravelCrispWhatsApp\Models\CrispWhatsAppCallbackEvent;
use Ziming\LaravelCrispWhatsApp\Models\CrispWhatsAppMessageLog;

Schedule::command('model:prune', ['--model' => [
    CrispWhatsAppMessageLog::class,
    CrispWhatsAppCallbackEvent::class,
]])->daily();

Ordinary logs retain the existing eight-day default. Pending logs and callback events default to 90 days, configurable using CRISP_WHATSAPP_PENDING_RETENTION_DAYS and CRISP_WHATSAPP_CALLBACK_RETENTION_DAYS. Existing published config files should add the corresponding pending_retention_days and callback_retention_days entries to use these environment variables. Once history is pruned, deduplication against that history is no longer possible. Pruning a parent keeps its events with a null parent link until their own expiry.

Caching

Caching is enabled by default. You can disable it by setting the CRISP_WHATSAPP_ENABLE_CACHING environment variable to false.

When caching is enabled, the package will cache the message template for an hour. This is to reduce the number of API calls made to Crisp.

This package also uses the spatie/laravel-data package to build the data objects. You may wish to refer its structure caching documentation too.

Why are the classes final?

This is selfish on my part, my hope is that it would incentivise you to make a pull request so that everyone would benefit. I will also get to know more about where my package is lacking especially in the early days of this library.

Testing

Sending whatsapp messages costs money. Hence there are no tests. So if there are issues, just create an issue to let me know or make a PR fix for it :)

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.