# 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

```php
// config/services.php
'nodium' => [
    'key' => env('NODIUM_KEY'),
    'webhook_secret' => env('NODIUM_WEBHOOK_SECRET'),
],
```

## The route

```php
// routes/api.php
use 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

```bash
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 once
```

## Next

- Write first to a customer with an approved [template](https://nodium.io/en/features/templates-broadcasts): free text only goes out within the [24-hour window](https://nodium.io/en/guides/24-hour-window).
- Answer `200` quickly and do slow work afterwards: an event that is not acknowledged is delivered again, with the same `id`.
- Connect your customers' numbers with [one key for all of them](https://nodium.io/en/features/multi-tenant).
- For slow work, dispatch a queued job from the route and return `200` at 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.

Updated 2026-11-10 · https://nodium.io/en/guides/whatsapp-api-laravel
