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

# Backend em Laravel

> A rota que emite a credencial e o receptor de webhook, num projeto Laravel.

A integração num projeto PHP. O ponto de atenção é o middleware: a rota de webhook precisa ficar
fora do `VerifyCsrfToken` e receber o corpo sem transformação.

|               |                                                                                   |
| ------------- | --------------------------------------------------------------------------------- |
| **Stack**     | PHP 8.3 · Laravel 11 · HTTP client nativo                                         |
| **Você terá** | uma rota que emite a credencial e um endpoint de webhook com assinatura conferida |

<Card title="Confira o exemplo" icon="brand-github" href="https://github.com/Legitimuz-Tech/legitimuz-examples/tree/master/pocs/laravel-backend" horizontal>
  Os arquivos desta POC, com o destino de cada um.
</Card>

## Emitir a credencial

```php title="app/Http/Controllers/VerificationController.php" theme={null}
<?php

namespace App\Http\Controllers;

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

class VerificationController extends Controller
{
    public function store(Request $request): JsonResponse
    {
        $registration = $request->user()->registration;

        $response = Http::withHeaders([
            'X-API-Key' => config('services.legitimuz.api_key'),
        ])->timeout(10)->post('https://api.legitimuz.com/public/verifications', [
            'schema_version' => '1.0',
            'ref_id' => $registration->id,
            'document' => ['type' => 'cpf', 'number' => $registration->cpf],
            'flow_public_id' => config('services.legitimuz.flow_id'),
        ]);

        if ($response->failed()) {
            return response()->json(['error' => 'legitimuz_unavailable'], 502);
        }

        $data = $response->json();
        $registration->update(['verification_public_id' => $data['verification']['public_id']]);

        // Só a `entry` volta ao cliente.
        return response()->json(['entry' => $data['entry']]);
    }
}
```

```php title="config/services.php" theme={null}
'legitimuz' => [
    'api_key' => env('LEGITIMUZ_API_KEY'),
    'webhook_secret' => env('LEGITIMUZ_WEBHOOK_SECRET'),
    'flow_id' => env('LEGITIMUZ_FLOW_ID'),
],
```

## Receber o desfecho

```php title="app/Http/Controllers/WebhookController.php" theme={null}
<?php

namespace App\Http\Controllers;

use App\Jobs\ProcessOutcome;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class WebhookController extends Controller
{
    private const TOLERANCE_SECONDS = 300;

    public function __invoke(Request $request): Response
    {
        // `getContent()` devolve o corpo ORIGINAL. `$request->all()` já teria feito o parse.
        $rawBody = $request->getContent();

        if (! $this->isSignatureValid($rawBody, $request->header('X-Legitimuz-Signature', ''))) {
            return response()->noContent(401);
        }

        $delivery = $request->header('X-Legitimuz-Delivery', '');

        // INSERT com chave única, não SELECT seguido de INSERT: duas entregas simultâneas passariam.
        $isNew = Delivery::firstOrCreate(['delivery_id' => $delivery])->wasRecentlyCreated;
        if ($isNew) {
            ProcessOutcome::dispatch(json_decode($rawBody, true));
        }

        return response()->noContent(200);
    }

    private function isSignatureValid(string $rawBody, string $header): bool
    {
        $parts = [];
        foreach (explode(',', $header) as $part) {
            [$key, $value] = array_pad(explode('=', $part, 2), 2, null);
            $parts[$key] = $value;
        }

        if (empty($parts['t']) || empty($parts['v1'])) {
            return false;
        }

        if (abs(time() - (int) $parts['t']) > self::TOLERANCE_SECONDS) {
            return false;
        }

        $expected = hash_hmac(
            'sha256',
            $parts['t'].'.'.$rawBody,
            config('services.legitimuz.webhook_secret')
        );

        return hash_equals($expected, $parts['v1']);
    }
}
```

```php title="routes/web.php" theme={null}
// Fora do grupo `web`: o CSRF do Laravel recusaria o POST da Legitimuz, que não tem sessão.
Route::post('/api/webhooks/legitimuz', WebhookController::class)
    ->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);
```

<Warning>
  Esquecer o `withoutMiddleware` é o erro mais comum aqui: o webhook responde `419` e o dashboard
  mostra a entrega falhando sem explicação óbvia no seu log.
</Warning>

## Antes de rodar

```bash title=".env" theme={null}
LEGITIMUZ_API_KEY=<SUA_CHAVE>
LEGITIMUZ_WEBHOOK_SECRET=<SEGREDO_DO_ENDPOINT>
LEGITIMUZ_FLOW_ID=<FLOW_PUBLIC_ID>
```

| Variável                   | Onde achar                                                                       |
| -------------------------- | -------------------------------------------------------------------------------- |
| `LEGITIMUZ_API_KEY`        | Integrações → Segurança → [Chaves de API](/platform/tokens). Aparece uma vez     |
| `LEGITIMUZ_WEBHOOK_SECRET` | Integrações → Segurança → [Webhooks](/platform/webhooks), na criação do endpoint |
| `LEGITIMUZ_FLOW_ID`        | Solução KYC → Fluxos, no menu da linha, em **Copiar ID do fluxo**                |

Use uma integração **sandbox**. Nenhum dos três valores vai para o browser ou para o app.

## O que esta POC não faz

<Warning>
  POC é código para entender o fluxo, não para copiar em produção. Em todas elas, `authenticate()` é
  um stub, `db` é um objeto de mentira e não há migration, observabilidade nem retentativa própria.
</Warning>

* Sem migration para a tabela `deliveries`; ela precisa de índice único em `delivery_id`.
* `ProcessOutcome` é um job vazio. A lógica de negócio é sua.
* Sem tratamento de `429`. Veja [limites](/api/errors#limites-de-uso).

## Próximo passo

<Columns cols={2}>
  <Card title="Fila e worker" icon="stack" href="/guides/pocs/queue-worker">
    O que fazer depois de responder `2xx`.
  </Card>

  <Card title="Segurança dos webhooks" icon="shield-check" href="/webhooks/security">
    Retentativa, deduplicação e troca de segredo.
  </Card>
</Columns>
