> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ararahq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Laravel

> Envie mensagens de um job em fila e receba os eventos da Arara num controller do Laravel.

Ao final desta página, seu projeto Laravel envia um template de um job em fila e registra o status de cada mensagem que chega pelo webhook.

## Antes de começar

* PHP 8.2 ou mais novo e Laravel 10, 11 ou 12.
* O SDK instalado com `composer require ararahq/sdk` e uma chave de API em `ARARA_API_KEY`. Veja [SDK PHP](/sdks/php).
* Um template aprovado. Os exemplos usam `confirmacao_pedido`. Veja [Templates](/templates/policy).
* Uma URL `https` pública para o webhook. Em desenvolvimento, veja [Testar localmente](/webhooks#testar-localmente).

## Registrar o SDK

Adicione a chave e o secret do webhook em `config/services.php`. Os valores vêm do `.env` (`ARARA_API_KEY` e `ARARA_WEBHOOK_SECRET`):

```php theme={null}
'arara' => [
    'key' => env('ARARA_API_KEY'),
    'webhook_secret' => env('ARARA_WEBHOOK_SECRET'),
],
```

Crie um service provider que registra o SDK como singleton:

```php theme={null}
// app/Providers/AraraServiceProvider.php
namespace App\Providers;

use Arara\Arara;
use Arara\Config;
use Illuminate\Support\ServiceProvider;

class AraraServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(Arara::class, function () {
            return new Arara(new Config(
                apiKey: config('services.arara.key'),
            ));
        });
    }
}
```

Registre o provider em `config/app.php`, ou em `bootstrap/providers.php` no Laravel 11 ou mais novo. As demais opções do `Config` estão em [SDK PHP](/sdks/php).

## Enviar de um controller

```php theme={null}
// app/Http/Controllers/OrderController.php
use Arara\Arara;

public function store(Request $request, Arara $sdk)
{
    $order = Order::create($request->validated());

    $sdk->messages->send(
        receiver: $order->customer->phone,
        templateName: 'confirmacao_pedido',
        variables: [
            $order->customer->first_name,
            $order->code,
            $order->total_formatted,
        ],
        idempotencyKey: "order-confirmation-{$order->id}",
    );

    return response()->json($order, 201);
}
```

O `receiver` aceita `+5511999999999`, `whatsapp:+5511999999999` ou só os dígitos; a API valida o número. `send` devolve um `array` com `id` (`ara_msg_...`) e `status` inicial.

## Enviar de um job em fila

Em produção, envie de um job. O `send` do SDK sempre manda `Idempotency-Key` e reaproveita a mesma chave nas retentativas do SDK (em 5xx). Passe `idempotencyKey:` derivada do pedido para que uma nova tentativa do job também não duplique a mensagem:

```php theme={null}
// app/Jobs/SendOrderConfirmation.php
namespace App\Jobs;

use App\Models\Order;
use Arara\Arara;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;

class SendOrderConfirmation implements ShouldQueue
{
    use Dispatchable, Queueable;

    public int $tries = 4;

    public function __construct(public int $orderId) {}

    public function handle(Arara $sdk): void
    {
        $order = Order::with('customer')->findOrFail($this->orderId);

        $sdk->messages->send(
            receiver: $order->customer->phone,
            templateName: 'confirmacao_pedido',
            variables: [
                $order->customer->first_name,
                $order->code,
                $order->total_formatted,
            ],
            idempotencyKey: "order-confirmation-{$order->id}",
        );
    }

    public function backoff(): array
    {
        return [30, 120, 300];
    }
}
```

Coloque o job na fila:

```php theme={null}
SendOrderConfirmation::dispatch($order->id);
```

A API responde `202`. Repetir a mesma `Idempotency-Key` devolve a mensagem original, sem novo envio. Em `POST /v1/messages` isso vale mesmo se o payload mudar (a API não compara destinatário nem variáveis), então nunca reaproveite a chave para outra mensagem.

## Webhook

<Steps>
  <Step title="Guarde o secret">
    No painel, abra [Webhooks](https://app.ararahq.com/dashboard/webhooks), gere o secret e copie o valor para o `.env`:

    ```ini theme={null}
    ARARA_WEBHOOK_SECRET=whsec_...
    ```
  </Step>

  <Step title="Crie a rota">
    ```php theme={null}
    // routes/api.php
    use App\Http\Controllers\AraraWebhookController;

    Route::post('/webhooks/arara', AraraWebhookController::class)
        ->name('webhooks.arara');
    ```
  </Step>

  <Step title="Crie o controller">
    O controller lê o corpo bruto, verifica a assinatura e registra o ID e o status de cada mensagem.

    ```php theme={null}
    // app/Http/Controllers/AraraWebhookController.php
    namespace App\Http\Controllers;

    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Log;

    class AraraWebhookController extends Controller
    {
        private const TOLERANCE_SECONDS = 300;

        public function __invoke(Request $request)
        {
            $body = $request->getContent();
            $timestamp = (string) $request->header('X-Arara-Timestamp', '');
            $signature = (string) $request->header('X-Arara-Signature', '');
            $secret = (string) config('services.arara.webhook_secret');

            $expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $secret);
            $fresh = ctype_digit($timestamp) && abs(time() - (int) $timestamp) <= self::TOLERANCE_SECONDS;

            if ($secret === '' || ! $fresh || ! hash_equals($expected, $signature)) {
                abort(401, 'Invalid signature');
            }

            $payload = json_decode($body, true);
            $data = is_array($payload) && is_array($payload['data'] ?? null) ? $payload['data'] : [];
            $deliveryId = $request->header('X-Arara-Delivery-Id');

            match (is_array($payload) ? ($payload['event'] ?? null) : null) {
                'message.status_updated' => $this->handleStatus($data, $deliveryId),
                'message.received' => $this->handleInbound($data, $deliveryId),
                'conversation.needs_human' => $this->handleHandoff($data, $deliveryId),
                default => null,
            };

            return response()->noContent();
        }

        private function handleStatus(array $data, ?string $deliveryId): void
        {
            Log::info('arara.message.status', [
                'delivery_id' => $deliveryId,
                'id' => $data['id'] ?? null,
                'status' => $data['status'] ?? null,
            ]);
        }

        private function handleInbound(array $data, ?string $deliveryId): void
        {
            Log::info('arara.message.received', [
                'delivery_id' => $deliveryId,
                'id' => $data['id'] ?? null,
                'from' => $data['from'] ?? null,
                'type' => $data['type'] ?? null,
            ]);
        }

        private function handleHandoff(array $data, ?string $deliveryId): void
        {
            Log::info('arara.handoff', [
                'delivery_id' => $deliveryId,
                'conversation_id' => $data['conversationId'] ?? null,
                'reason' => $data['reason'] ?? null,
            ]);
        }
    }
    ```

    Troque os `Log::info` pela gravação no seu banco. `$data['id']` é o mesmo ID devolvido no envio. `$data['status']` vem em minúsculas: `queued`, `sent`, `delivered`, `read`, `failed`.
  </Step>

  <Step title="Cadastre a URL">
    Na mesma tela do painel, informe `https://seu-dominio.com/api/webhooks/arara` e dispare o evento de teste.
  </Step>
</Steps>

<Warning>
  Na 2.0.0 do SDK, `WebhookUtils::verifySignature($body, $signature, $secret, $timestamp)` faz a mesma conferência (assinatura sobre `timestamp.corpo` e janela anti-replay) e pode substituir o código manual acima. Se mantiver o manual, compare com `hash_equals`, que é em tempo constante. Nunca use `==`. Quem está na 1.8.1 deve atualizar: lá a função não inclui o timestamp e nunca confere.
</Warning>

Responda `2xx` rápido. Quando a entrega falha, a Arara tenta de novo, então o controller precisa aguentar o mesmo evento mais de uma vez. Use o header `X-Arara-Delivery-Id` para deduplicar. Prazos e intervalos estão em [Entrega](/webhooks#entrega).

<Note>
  Rotas em `routes/api.php` já ficam fora da verificação de CSRF. Se registrar o webhook em `routes/web.php`, adicione o caminho ao `VerifyCsrfToken@except` (Laravel 10) ou a `validateCsrfTokens(except: [...])` em `bootstrap/app.php` (Laravel 11 ou mais novo).
</Note>

## Atualizar o admin em tempo real

Para mostrar o status no seu admin com Pusher ou Reverb, crie um evento de broadcast:

```php theme={null}
// app/Events/MessageStatusChanged.php
namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;

class MessageStatusChanged implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets;

    public function __construct(public string $messageId, public string $status) {}

    public function broadcastOn(): Channel
    {
        return new Channel('arara.messages');
    }
}
```

Emita o evento no `handleStatus`, depois do `Log::info`:

```php theme={null}
if (is_string($data['id'] ?? null) && is_string($data['status'] ?? null)) {
    broadcast(new \App\Events\MessageStatusChanged($data['id'], $data['status']));
}
```

## Próximos passos

<CardGroup cols={2}>
  <Card title="Eventos" icon="list-check" href="/webhooks/events">
    Corpo e campos de cada evento.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/webhooks">
    Retentativas, deduplicação e teste local.
  </Card>

  <Card title="SDK PHP" icon="php" href="/sdks/php">
    Métodos disponíveis e limitações da versão publicada.
  </Card>

  <Card title="Receitas" icon="book-open" href="/recipes">
    Carrinho abandonado, confirmação de pedido, suporte.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.