PHP integration
Send WhatsApp messages with Laravel
A complete, runnable Laravel example for sending WhatsApp messages through the SPWP REST API. There is no SDK to install — Laravel talks to the API over ordinary HTTP, so you can paste this straight into an existing project.
About 5 minutes to your first delivered message
1. Setup
Create an API key in the dashboard under Accounts → your account → API keys, then make it available to your Laravel code as an environment variable. Never commit the key or ship it in client-side code.
# Laravel's HTTP client is built in — nothing to require.
# .env
SPWP_API_KEY=wak_live_your_key_here
SPWP_WEBHOOK_SECRET=your_webhook_secret
# config/services.php
'spwp' => [
'key' => env('SPWP_API_KEY'),
'webhook_secret' => env('SPWP_WEBHOOK_SECRET'),
'base' => 'https://api.spwp.app/api/v1',
],2. Send a text message
One POST with a recipient and a body. The API replies 202 Accepted with a message id in QUEUED state; delivery happens asynchronously and you can track it by id or webhook.
<?php
// app/Services/Spwp.php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class Spwp
{
public function sendText(string $to, string $text): array
{
$response = Http::withHeaders([
'X-API-Key' => config('services.spwp.key'),
])
->timeout(10)
->acceptJson()
->post(config('services.spwp.base') . '/public/messages/text', [
'to' => $to,
'text' => $text,
])
->throw();
return $response->json('data');
}
}
// Anywhere in your app:
// app(Spwp::class)->sendText('9647503505440', "Order #{$order->id} shipped.");3. Send an image, document or other media
Media endpoints take the same shape, with a source that is either a public HTTPS URL we fetch server-side or a base64 data URL you upload inline. Swap the path segment for video, audio, document or location.
<?php
// app/Jobs/SendWhatsAppInvoice.php — send media from a queued job so a slow
// upload never blocks the request.
namespace App\Jobs;
use App\Services\Spwp;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\Http;
class SendWhatsAppInvoice implements ShouldQueue
{
use Dispatchable, Queueable;
public function __construct(
public string $phone,
public string $pdfUrl,
public string $fileName,
) {}
public function handle(): void
{
Http::withHeaders(['X-API-Key' => config('services.spwp.key')])
->post(config('services.spwp.base') . '/public/messages/document', [
'to' => $this->phone,
'source' => $this->pdfUrl,
'fileName' => $this->fileName,
'mimeType' => 'application/pdf',
])
->throw();
}
}4. Receive replies and delivery receipts
Inbound messages and status changes arrive as signed POSTs to your webhook URL. Recompute the HMAC over the timestamp and the raw request body — re-serialised JSON will not match — and reject anything older than five minutes.
<?php
// app/Http/Middleware/VerifySpwpSignature.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\HttpException;
class VerifySpwpSignature
{
public function handle(Request $request, Closure $next)
{
$ts = $request->header('X-WA-Timestamp');
preg_match('/v1=([a-f0-9]+)/', $request->header('X-WA-Signature', ''), $m);
$v1 = $m[1] ?? '';
abort_if(!$ts || !$v1, 400);
abort_if(abs(now()->getTimestampMs() - (int) $ts) > 5 * 60000, 400);
$expected = hash_hmac(
'sha256',
$ts . '.' . $request->getContent(),
config('services.spwp.webhook_secret'),
);
abort_unless(hash_equals($expected, $v1), 401);
return $next($request);
}
}
// routes/api.php — exclude from CSRF, the signature is the auth.
// Route::post('/webhooks/spwp', SpwpWebhookController::class)
// ->middleware(VerifySpwpSignature::class);Error handling
Errors carry a non-2xx status and a stable code string you can branch on: VALIDATION_FAILED, INVALID_API_KEY, ACCOUNT_NOT_CONNECTED, RATE_LIMITED, MEDIA_FETCH_FAILED. Retry on 429 while honouring Retry-After; treat 401 as a configuration problem rather than a transient one.
Next steps
The full reference covers every endpoint, field and error code, plus the webhook payload shapes.