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

# Idempotência

> Reenvie requisições POST com segurança usando Idempotency-Key.

Falhas de rede acontecem: um timeout ou uma conexão derrubada deixam você sem saber se o servidor processou a requisição. A idempotência resolve isso: você reenvia o mesmo `POST` com a mesma chave e a Repass garante que a operação rode **no máximo uma vez**, devolvendo a resposta original em qualquer reenvio.

## Como funciona

Envie um header `Idempotency-Key` em qualquer `POST` autenticado. Use um valor único por operação que você queira proteger: um `UUID v4` é a escolha mais comum.

* Na **primeira** vez que a chave é vista, a requisição é processada normalmente e a resposta é gravada.
* Em **reenvios** com a mesma chave e o mesmo payload, a Repass devolve a resposta gravada (sem reexecutar a lógica de negócio) com o header `Idempotent-Replay: true`.

A chave fica válida por **24 horas** após a primeira gravação. Depois disso ela expira e pode ser reutilizada.

<Note>
  A idempotência por header só atua em requisições `POST` autenticadas que enviam um `Idempotency-Key` não vazio. `GET`, `PUT` e `DELETE` passam direto pelo mecanismo.
</Note>

## Exemplo

Reenviar a mesma requisição com a mesma chave é seguro: a segunda chamada não cria uma segunda Conversão, apenas devolve a primeira resposta.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.userepass.com/conversions \
    -X POST \
    -H "Authorization: Bearer rstr_..." \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: 7f3b9a2e-1c4d-4f8a-9b6e-2d5c7a1f0e3b" \
    -d '{
      "affiliateId": "aff_01HZX9K3M2Qn7P4R8T6V0W2Y5C",
      "amountCents": 12990,
      "currency": "BRL",
      "sourceEventId": "order_98321"
    }'
  ```

  ```json Resposta (primeira chamada) theme={null}
  {
    "id": "conv_01HZXB7N5R3Qp8M2K4V6T0W9Y1",
    "affiliateId": "aff_01HZX9K3M2Qn7P4R8T6V0W2Y5C",
    "amountCents": 12990,
    "currency": "BRL",
    "status": "pending"
  }
  ```
</CodeGroup>

Um reenvio idêntico devolve **o mesmo corpo e o mesmo status HTTP**, agora com o header de replay:

```bash Resposta (reenvio) theme={null}
HTTP/1.1 200 OK
Idempotent-Replay: true
Content-Type: application/json

{
  "id": "conv_01HZXB7N5R3Qp8M2K4V6T0W9Y1",
  "affiliateId": "aff_01HZX9K3M2Qn7P4R8T6V0W2Y5C",
  "amountCents": 12990,
  "currency": "BRL",
  "status": "pending"
}
```

<Tip>
  Sempre cheque o header `Idempotent-Replay` na resposta quando precisar distinguir uma operação recém-executada de um replay: por exemplo, para evitar disparar uma notificação interna duas vezes.
</Tip>

## Escopo da chave

A unicidade da `Idempotency-Key` é por **organização + usuário** (escopo `organizationId:userId`). A mesma string de chave pode coexistir em organizações ou usuários diferentes sem colidir: cada tenant tem seu próprio espaço de chaves.

Isso significa que você não precisa coordenar a geração de chaves entre tenants: basta garantir que sejam únicas dentro do seu próprio escopo de credenciais.

## O que conta como "a mesma requisição"

Além da chave, a Repass compara um hash do request: `sha256("<método> <url> <body>")`. O replay só acontece quando **chave e payload coincidem**.

* **Mesma chave + mesmo payload** → replay da resposta gravada.
* **Mesma chave + payload diferente** → erro `idempotency_key_reused` (a chave já está vinculada a outra operação).

<Warning>
  Não reutilize uma `Idempotency-Key` para operações diferentes. Gere uma chave nova por operação lógica. Reusar uma chave com um corpo diferente retorna `400 idempotency_key_reused`, não a nova operação.
</Warning>

## Ciclo de vida de uma chave

```mermaid theme={null}
stateDiagram-v2
    [*] --> processing: chave inédita (ou expirada), requisição em andamento
    processing --> completed: resposta < 500 gravada (status + corpo)
    processing --> [*]: resposta >= 500, registro descartado, chave liberada para retry
    completed --> replay: mesma chave + mesmo payload, dentro de 24h
    completed --> [*]: expirou (24h), liberada e recriada no próximo POST
    replay --> [*]: devolve resposta original + Idempotent-Replay: true
```

Pontos importantes do ciclo de vida:

* **Respostas `>= 500` não são gravadas.** O registro é descartado e a chave fica livre para uma nova tentativa. Falhas de servidor nunca "travam" uma chave. Reenvie à vontade.
* **Respostas `4xx` (como `400` ou `409`) são gravadas** como resultado idempotente. Um reenvio com a mesma chave devolve o mesmo erro determinístico via replay.
* **TTL de 24 horas.** Após esse período a chave expira; o próximo `POST` com ela inicia uma operação nova do zero.

## Tratamento de erros

Erros de idempotência usam o envelope padrão de erro com `type: idempotency_error`. Veja o formato completo em [Erros](/docs/convencoes/erros).

| Situação                                            | HTTP  | `code`                   | O que fazer                                    |
| --------------------------------------------------- | ----- | ------------------------ | ---------------------------------------------- |
| Requisição com a mesma chave ainda em processamento | `409` | `idempotency_in_flight`  | Aguarde e tente de novo após um curto backoff. |
| Mesma chave reutilizada com payload diferente       | `400` | `idempotency_key_reused` | Use uma chave nova para a nova operação.       |

```json 409 idempotency_in_flight theme={null}
{
  "error": {
    "type": "idempotency_error",
    "code": "idempotency_in_flight",
    "message": "A request with this idempotency key is still being processed"
  }
}
```

<Info>
  O `idempotency_in_flight` também protege contra **corridas**: se duas requisições com a mesma chave chegam ao mesmo tempo, apenas uma é processada e a outra recebe `409`. Isso é esperado: reenvie a perdedora após a primeira concluir.
</Info>

## Padrão de retry recomendado

Combine idempotência com retry e backoff exponencial. Como a chave é estável entre tentativas, reenviar é sempre seguro: você nunca duplica a operação.

```bash Retry com backoff theme={null}
KEY=$(uuidgen)

for attempt in 1 2 3; do
  status=$(curl -s -o /tmp/resp.json -w "%{http_code}" \
    https://api.userepass.com/conversions \
    -X POST \
    -H "Authorization: Bearer rstr_..." \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $KEY" \
    -d '{"affiliateId":"aff_01HZX9K3M2Qn7P4R8T6V0W2Y5C","amountCents":12990,"currency":"BRL","sourceEventId":"order_98321"}')

  # 5xx ou 409 in-flight: aguarde e reenvie com a MESMA chave
  if [ "$status" -ge 500 ] || [ "$status" -eq 409 ]; then
    sleep $((attempt * 2))
    continue
  fi

  cat /tmp/resp.json
  break
done
```

<Check>
  A mesma chave é mantida em todas as tentativas. Numa falha `5xx`, o registro foi descartado e o reenvio reexecuta a operação. Num sucesso anterior que você não chegou a receber, o reenvio devolve a resposta gravada via replay. De qualquer forma, a operação acontece no máximo uma vez.
</Check>

## Rotas que não suportam idempotência

Os fluxos públicos de tracking e ingestão **não** usam o header `Idempotency-Key`: eles têm idempotência própria de negócio (por exemplo, deduplicação por `sourceEventId`). Estes prefixos são ignorados pelo mecanismo:

| Prefixo          | Uso                                                                                |
| ---------------- | ---------------------------------------------------------------------------------- |
| `/t/`, `/track/` | Endpoints públicos de [tracking](/docs/conceitos/tracking) e registro de Clique.        |
| `/ingest/`       | [Ingestão](/docs/conceitos/ingestao) de eventos externos (ex.: webhooks de provedores). |
| `/api/auth/`     | Endpoints de [autenticação](/docs/autenticacao).                                        |

<Note>
  Para garantir exatamente-uma-vez em conversões server-to-server, use o campo `sourceEventId` no corpo da requisição: ele deduplica por evento de origem. Veja [Conversões server-to-server](/docs/guias/conversoes-server-to-server).
</Note>

## Próximos passos

<CardGroup cols={2}>
  <Card title="Erros" icon="triangle-exclamation" href="/docs/convencoes/erros">
    O envelope de erro único e todos os códigos da API.
  </Card>

  <Card title="IDs e recursos" icon="fingerprint" href="/docs/convencoes/ids-e-recursos">
    Identificadores opacos com prefixo de tipo + ULID.
  </Card>

  <Card title="Conversões server-to-server" icon="server" href="/docs/guias/conversoes-server-to-server">
    Deduplicação por sourceEventId em integrações de backend.
  </Card>

  <Card title="Integração básica" icon="rocket" href="/docs/guias/integracao-basica">
    Primeiros passos para integrar com a API Repass.
  </Card>
</CardGroup>
