Blog/Tutorials

How to Send WhatsApp Messages in Laravel Using Rapiwa

Send WhatsApp messages from a Laravel application using Rapiwa API. Full guide with Service class, Facade, Laravel Notification channel, and queue integration. PHP code examples included.

by Moumina
How to Send WhatsApp Messages in Laravel Using Rapiwa

You can send WhatsApp messages from a Laravel application using Rapiwa API by creating a service class that calls POST https://app.rapiwa.com/send-message. This guide shows how to build a reusable RapiwaService, create a Laravel Notification channel, and queue WhatsApp messages for high-throughput applications. Rapiwa costs $5/month flat with no per-message fees.

Prerequisites

  • Rapiwa account (free 3-day trial at rapiwa.com)
  • Your Rapiwa API key (Dashboard → API Keys)
  • Laravel 9.x, 10.x, or 11.x
  • PHP 8.1+
  • composer require guzzlehttp/guzzle (usually included with Laravel)

Step 1: Add Rapiwa Credentials to .env

# Add to .env
RAPIWA_API_KEY=your_api_key_here
RAPIWA_API_URL=https://app.rapiwa.com

Add to config/services.php:

'rapiwa' => [
    'key' => env('RAPIWA_API_KEY'),
    'url' => env('RAPIWA_API_URL', 'https://app.rapiwa.com'),
],

Step 2: Create the RapiwaService

php artisan make:service RapiwaService
# If make:service is not available, create app/Services/RapiwaService.php manually
<?php
// app/Services/RapiwaService.php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\Response;

class RapiwaService
{
    private string $apiKey;
    private string $baseUrl;

    public function __construct()
    {
        $this->apiKey = config('services.rapiwa.key');
        $this->baseUrl = config('services.rapiwa.url');
    }

    /**
     * Send a text message via WhatsApp.
     *
     * @param string $phone  International format without '+', e.g. 8801234567890
     * @param string $message  Plain text or WhatsApp-formatted text (*bold*, _italic_)
     * @return array{status: string, messageId: string}
     */
    public function sendMessage(string $phone, string $message): array
    {
        $response = Http::withToken($this->apiKey)
            ->timeout(10)
            ->post("{$this->baseUrl}/send-message", [
                'number' => $phone,
                'message' => $message,
            ]);

        $this->handleErrors($response);

        return $response->json();
    }

    /**
     * Send an image message via WhatsApp.
     *
     * @param string $phone
     * @param string $imageUrl  Publicly accessible image URL
     * @param string $caption   Optional image caption
     */
    public function sendImage(string $phone, string $imageUrl, string $caption = ''): array
    {
        $response = Http::withToken($this->apiKey)
            ->timeout(15)
            ->post("{$this->baseUrl}/send-image", [
                'number' => $phone,
                'imageUrl' => $imageUrl,
                'caption' => $caption,
            ]);

        $this->handleErrors($response);

        return $response->json();
    }

    private function handleErrors(Response $response): void
    {
        if ($response->status() === 401) {
            throw new \RuntimeException('Rapiwa API key is invalid or expired. Check config/services.php rapiwa.key');
        }

        if ($response->status() === 400) {
            throw new \InvalidArgumentException('Invalid request to Rapiwa API: ' . $response->body());
        }

        if ($response->failed()) {
            throw new \RuntimeException('Rapiwa API request failed: ' . $response->body());
        }
    }
}

Step 3: Register as a Singleton (Optional)

In app/Providers/AppServiceProvider.php:

public function register(): void
{
    $this->app->singleton(RapiwaService::class, function ($app) {
        return new RapiwaService();
    });
}

Step 4: Use in Controllers

<?php
// app/Http/Controllers/OrderController.php

namespace App\Http\Controllers;

use App\Services\RapiwaService;
use Illuminate\Http\Request;

class OrderController extends Controller
{
    public function __construct(private RapiwaService $rapiwa) {}

    public function store(Request $request): \Illuminate\Http\JsonResponse
    {
        // Create the order
        $order = Order::create($request->validated());

        // Send WhatsApp confirmation
        if ($order->customer->phone) {
            $message = "Hi {$order->customer->first_name}! ✅\n\n"
                . "Your order #{$order->id} has been confirmed.\n"
                . "Total: \${$order->total}\n\n"
                . "We'll notify you when it ships!";

            $this->rapiwa->sendMessage(
                phone: $order->customer->phone,
                message: $message
            );
        }

        return response()->json(['order' => $order], 201);
    }
}

Step 5: Laravel Notification Channel

Create a custom notification channel for clean, reusable WhatsApp notifications:

<?php
// app/Channels/WhatsAppChannel.php

namespace App\Channels;

use App\Services\RapiwaService;
use Illuminate\Notifications\Notification;

class WhatsAppChannel
{
    public function __construct(private RapiwaService $rapiwa) {}

    public function send(mixed $notifiable, Notification $notification): void
    {
        $message = $notification->toWhatsApp($notifiable);

        if (!$message) return;

        $phone = $notifiable->routeNotificationFor('whatsApp');

        if (!$phone) return;

        $this->rapiwa->sendMessage(phone: $phone, message: $message);
    }
}

Example Notification class:

<?php
// app/Notifications/OrderShippedNotification.php

namespace App\Notifications;

use App\Channels\WhatsAppChannel;
use Illuminate\Notifications\Notification;

class OrderShippedNotification extends Notification
{
    public function __construct(private Order $order) {}

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

    public function toWhatsApp(mixed $notifiable): string
    {
        return "Hi {$notifiable->first_name}! 🚚\n\n"
            . "Your order #{$this->order->id} has shipped!\n\n"
            . "Tracking: {$this->order->tracking_number}\n"
            . "Track here: {$this->order->tracking_url}";
    }
}

Add route to User model:

// app/Models/User.php
public function routeNotificationForWhatsApp(): ?string
{
    return $this->phone; // Returns international format phone number
}

Send the notification:

$user->notify(new OrderShippedNotification($order));

Step 6: Queue WhatsApp Messages

For high-volume applications, queue WhatsApp sends to avoid slowing down HTTP responses:

php artisan make:job SendWhatsAppMessage
<?php
// app/Jobs/SendWhatsAppMessage.php

namespace App\Jobs;

use App\Services\RapiwaService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;

class SendWhatsAppMessage implements ShouldQueue
{
    use Dispatchable, Queueable;

    public int $tries = 3;
    public int $backoff = 60; // Retry after 60 seconds on failure

    public function __construct(
        private string $phone,
        private string $message
    ) {}

    public function handle(RapiwaService $rapiwa): void
    {
        $rapiwa->sendMessage($this->phone, $this->message);
    }
}

Dispatch the job:

SendWhatsAppMessage::dispatch($user->phone, $message)
    ->delay(now()->addMinutes(5)); // Optional delay

Test cURL (verify API key before integrating):

curl -X POST https://app.rapiwa.com/send-message \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "number": "8801234567890",
    "message": "Hello from Laravel! This is a test from Rapiwa API."
  }'

Expected response:

{
  "status": "success",
  "messageId": "msg_laravel_abc123",
  "timestamp": "2026-06-22T10:30:00Z"
}

Step 7: Handle Incoming Webhooks in Laravel

// routes/api.php
Route::post('/whatsapp/webhook', [WhatsAppWebhookController::class, 'handle'])
    ->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);
// app/Http/Controllers/WhatsAppWebhookController.php
public function handle(Request $request): JsonResponse
{
    $event = $request->input('event');
    $data = $request->input('data');

    if ($event === 'message.received') {
        ProcessIncomingWhatsApp::dispatch($data);
    }

    return response()->json(['status' => 'ok']);
}

Common Errors and Fixes

  • cURL error: SSL certificate problem: In local dev, you may need to disable SSL verification: Http::withoutVerifying()->... (never in production)
  • GuzzleHttp\Exception\ConnectException: Your server can't reach app.rapiwa.com. Check firewall rules and outbound HTTPS access
  • TypeError: phone must be string: Ensure phone numbers are cast to string — (string) $user->phone
  • 401 Unauthorized: Check config('services.rapiwa.key') — run php artisan config:clear if you recently updated .env

FAQ

Is there a Rapiwa Laravel package on Packagist? Not yet — a community package is on the roadmap. For now, use the service class pattern in this tutorial, which gives you full control.

Can I send WhatsApp messages from Laravel Queues on AWS SQS? Yes. The SendWhatsAppMessage job works with any Laravel queue driver including Redis, database, and SQS.

Does Rapiwa charge per message sent from Laravel? No. Rapiwa charges $5/month flat with no per-message fees. Send unlimited messages from your Laravel application.

How do I format WhatsApp text in PHP strings? WhatsApp supports: *bold*, _italic_, ~strikethrough~, \``monospace```. Use \n` for line breaks in PHP strings.

Can I send multiple messages in one API call? No — each API call sends one message. For bulk sends, loop over your contacts and call the API once per number (with a 1-second delay to respect rate limits).