WhatsApp API with Laravel: a signed webhook and a reply
To use the WhatsApp API from Laravel, add one API route for the webhook and use the HTTP client to reply. No package is required: you verify an HMAC-SHA256 signature, then call a REST API with a bearer token.
Configuration
// config/services.php'nodium' => [ 'key' => env('NODIUM_KEY'), 'webhook_secret' => env('NODIUM_WEBHOOK_SECRET'),],The route
// routes/api.phpuse Illuminate\Http\Request;use Illuminate\Support\Facades\Http;use Illuminate\Support\Facades\Route;Route::post('/webhooks/nodium', function (Request $request) { // The raw body, before any JSON parsing: the signature is computed on it. $rawBody = $request->getContent(); $timestamp = null; $signatures = []; foreach (explode(',', (string) $request->header('X-Nodium-Signature')) as $part) { [$key, $value] = array_pad(explode('=', trim($part), 2), 2, ''); if ($key === 't') $timestamp = (int) $value; if ($key === 'v1') $signatures[] = $value; } if (! $timestamp || abs(time() - $timestamp) > 300) { return response('', 400); } $expected = hash_hmac('sha256', $timestamp.'.'.$rawBody, config('services.nodium.webhook_secret')); $valid = collect($signatures)->contains(fn ($signature) => hash_equals($expected, $signature)); if (! $valid) { return response('', 400); } $event = json_decode($rawBody, true); if ($event['event'] === 'message.received' && $event['data']['type'] === 'text') { Http::withToken(config('services.nodium.key')) ->post('https://nodium.io/api/v1/messages', [ 'channelId' => $event['data']['channel_id'], 'to' => $event['data']['from'], 'type' => 'text', 'text' => 'You wrote: '.$event['data']['text'], 'idempotencyKey' => 'reply-'.$event['id'], ]); } return response('', 200);});Routes in routes/api.php are not subject to CSRF verification, which a webhook from outside could not pass.
Tell Nodium where to call you
curl https://nodium.io/api/v1/webhooks \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.example.com/webhooks/nodium", "events": ["message.received"] }'# → data.secret (whsec_…): shown onceNext
- Write first to a customer with an approved template: free text only goes out within the 24-hour window.
- Answer
200quickly and do slow work afterwards: an event that is not acknowledged is delivered again, with the sameid. - Connect your customers' numbers with one key for all of them.
- For slow work, dispatch a queued job from the route and return
200at once.
Frequently asked questions
Is there a Laravel package for Nodium?
No, and none is needed: Laravel's HTTP client and a route are enough. The API is described in OpenAPI 3.1.