> ## 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.

# Retomada e expiração

> O titular fechou a aba no meio da jornada. O que reaproveitar, o que recriar e como o ref_id decide isso.

O caminho feliz é minoria. A maior parte dos chamados de integração vem do titular que abandonou,
voltou dois dias depois e encontrou uma tela quebrada.

|               |                                                                         |
| ------------- | ----------------------------------------------------------------------- |
| **Stack**     | Next.js 15 · qualquer banco                                             |
| **Você terá** | uma rota que devolve a jornada certa, criando de novo só quando precisa |

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

## As três situações

| O que aconteceu                                 | O que fazer                                               |
| ----------------------------------------------- | --------------------------------------------------------- |
| O titular voltou e a verificação ainda vale     | repetir a criação com o mesmo `ref_id`: volta a existente |
| A verificação expirou (`expires_at` no passado) | criar outra, com um `ref_id` novo                         |
| A verificação já decidiu                        | não criar nada: mostrar o desfecho                        |

A primeira linha é a que a idempotência resolve sozinha. Repetir a criação com o mesmo `ref_id` e o
mesmo corpo devolve `200` com a verificação que já existe, em vez de criar uma segunda.

## A rota

```ts title="app/api/verifications/route.ts" theme={null}
export async function POST(request: Request) {
  const user = await authenticate(request);
  const registration = await db.registrations.byUser(user.id);

  // 1. Já decidiu? Não há jornada para abrir.
  if (registration.outcome) {
    return Response.json({ outcome: registration.outcome }, { status: 409 });
  }

  // 2. Existe e ainda vale? A idempotência devolve a mesma, com o status já avançado.
  const stillValid = registration.expires_at && new Date(registration.expires_at) > new Date();
  const refId = stillValid ? registration.ref_id : `${registration.id}-${Date.now()}`;

  const response = await fetch("https://api.legitimuz.com/public/verifications", {
    method: "POST",
    headers: {
      "X-API-Key": process.env.LEGITIMUZ_API_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      schema_version: "1.0",
      ref_id: refId,
      document: { type: "cpf", number: registration.cpf },
      flow_public_id: process.env.LEGITIMUZ_FLOW_ID!,
    }),
  });

  const { verification, entry } = await response.json();

  // 3. Grave sempre: o `ref_id` mudou no caso da expiração, e o webhook virá com o novo.
  await db.registrations.updateVerification(registration.id, {
    ref_id: refId,
    public_id: verification.public_id,
    expires_at: verification.expires_at,
  });

  return Response.json({ entry });
}
```

<Warning>
  O `ref_id` é único por conta. Reaproveitá-lo depois da expiração com um corpo diferente responde
  `409 E_REF_ID_CONFLICT` — por isso o sufixo de tempo quando a jornada precisa recomeçar.
</Warning>

## O que o `status` diz

A criação repetida devolve a verificação com o `status` de agora, não o de quando foi criada:

| `status`                           | O titular                            |
| ---------------------------------- | ------------------------------------ |
| `not_opened`                       | nunca abriu o link                   |
| `started`                          | abriu e parou no meio                |
| `review` · `approved` · `rejected` | chegou ao fim; não abra jornada nova |

Use isso para a sua tela: quem está em `started` merece "continue de onde parou", não "comece
agora".

## Não fique perguntando

O `expires_at` que você gravou já responde se a jornada vale. Consultar a Legitimuz num intervalo
para descobrir isso gasta o teto da sua chave e não traz nada que o webhook não traga.

## 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 limite de quantas vezes o mesmo cadastro pode recomeçar.
* Sem aviso ao titular de quanto tempo resta.
* Sem tratamento de CPF trocado entre uma tentativa e outra.

## Próximo passo

<Columns cols={2}>
  <Card title="Status da verificação" icon="list-check" href="/api/verification-status">
    Os cinco status e o que cada um significa.
  </Card>

  <Card title="Criar verificação" icon="terminal-2" href="/api/create-verification">
    A idempotência por `ref_id`, em detalhe.
  </Card>
</Columns>
