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

# Conversões e fraude

> Ingestão, matching, atribuição e scoring de fraude de conversões.

Uma Conversão é o fato de negócio "um cliente atribuível executou uma ação comissionável": ela é o vínculo auditável entre cliente e afiliado que justifica o pagamento de Comissões. A Conversão **não** é o objeto que recebe dinheiro: o dinheiro flui pelas Comissões ao longo das cobranças (1 Conversão → N Comissões). A Conversão guarda a prova de *por que* aquele afiliado merece comissão sobre aquele cliente.

Ao registrar uma Conversão via API server-to-server, a Repass executa de forma **síncrona** todo o pipeline de decisão: matching de identidade, atribuição ao clique/cupom vencedor, resolução da regra de comissão, scoring antifraude e geração da Comissão do primeiro ciclo. Cada decisão é congelada em snapshots imutáveis dentro da própria Conversão e nos eventos que ela emite, de modo que o histórico possa ser reconstruído integralmente a partir do [histórico de eventos](/docs/conceitos/event-store).

<Info>
  IDs de Conversão têm o prefixo `conv_` seguido de um ULID (ex.: `conv_01J9Z3K8...`). Valores monetários são sempre inteiros em **centavos**; pesos de atribuição em **basis points** (10000 = 100%). Veja [IDs e recursos](/docs/convencoes/ids-e-recursos).
</Info>

## Tipos de conversão

O campo `type` classifica a ação comissionável:

| Tipo                   | Significado                                                                                         |
| ---------------------- | --------------------------------------------------------------------------------------------------- |
| `subscription_created` | Assinatura recorrente criada.                                                                       |
| `one_time_purchase`    | Compra avulsa (não recorrente).                                                                     |
| `trial_converted`      | Trial convertido em assinatura paga.                                                                |
| `upgrade`              | Mudança de plano de um cliente já convertido: tem semântica especial na deduplicação (veja abaixo). |
| `custom`               | Evento comissionável arbitrário definido pela integração.                                           |

## Fontes de conversão

O campo `source` registra a origem do registro:

* **`api`**: Conversão registrada server-to-server via `POST /conversions`. É o caminho recomendado para integrações. Veja o guia [Conversões server-to-server](/docs/guias/conversoes-server-to-server).
* **`manual`**: Registro feito por um operador via `POST /conversions/manual`, com afiliado explícito e **justificativa obrigatória** (1 a 1000 caracteres). Não roda matching de identidade.
* **`webhook`**: Conversão derivada da ingestão de webhooks de gateway de pagamento (como o Stripe). Alimenta o mesmo pipeline com `source: "webhook"`. Veja [Ingestão](/docs/conceitos/ingestao).

## Ingestão e deduplicação

### Idempotência por sourceEventId

Todo registro de Conversão exige um `sourceEventId`, uma chave de idempotência **permanente por organização** (não tem TTL). Reentregar o mesmo `sourceEventId` retorna a Conversão existente com `replayed: true`, sem criar uma nova nem reemitir eventos.

<Note>
  A idempotência de negócio por `sourceEventId` é **independente** da [idempotência de transporte HTTP](/docs/convencoes/idempotencia) por `Idempotency-Key`. As duas convivem por desenho: o `Idempotency-Key` curto-circuita reentregas idênticas do mesmo cliente HTTP; o `sourceEventId` garante que o mesmo evento de negócio nunca crie duas Conversões, qualquer que seja o `Idempotency-Key`.
</Note>

### Deduplicação de cliente ativo

Um cliente que já possui uma Conversão ativa (`pending` ou `approved`) no mesmo Programa **não** gera nova atribuição: cobranças subsequentes pertencem à Conversão original. A resposta retorna a Conversão original com `deduplicated: true`.

A exceção é `type: upgrade`: em vez de deduplicar, cria-se uma **Conversão filha** vinculada via `parentConversionId`, herdando o afiliado da original (sem rodar matching de novo).

```mermaid theme={null}
stateDiagram-v2
    [*] --> Replay: sourceEventId já visto
    [*] --> Dedupe: cliente com conversão ativa
    [*] --> Upgrade: cliente ativo + type=upgrade
    [*] --> Pipeline: caso novo
    Replay --> [*]: retorna existente (replayed=true)
    Dedupe --> [*]: retorna original (deduplicated=true)
    Upgrade --> [*]: cria filha (parentConversionId)
    Pipeline --> [*]: matching → atribuição → fraude → comissão
```

## Matching e atribuição

O matching vincula a Conversão a um clique através de uma ordem **determinística** de fallthrough. O primeiro identificador que produzir candidatos válidos vence; o método vencedor é gravado em `matchMethod`:

<Steps>
  <Step title="clickId explícito">
    `click_id`: clique informado diretamente pela integração S2S.
  </Step>

  <Step title="visitorId">
    `visitor_id`: cookie de visitante.
  </Step>

  <Step title="emailHash (cross-device)">
    `email_hash`: SHA-256 do e-mail do cliente em minúsculas. Derivado de `customer.email` quando o hash não é enviado.
  </Step>

  <Step title="fingerprint">
    `fingerprint`: fingerprint probabilístico.
  </Step>
</Steps>

Um clique só é candidato se pertence ao mesmo Programa, não é bot, ainda não expirou e ocorreu **antes** da Conversão. Candidatos cujo afiliado está `banned` ou `rejected` são descartados. Se nenhum identificador casa e não há cupom, **nada é persistido**: a resposta retorna `{ attributed: false, conversion: null }` e nenhum evento é emitido.

A escolha do vencedor entre os candidatos segue o modelo de atribuição do Programa (`last_click`, `first_click`, `linear`, `time_decay`, `position_based`) e a política de conflito cupom × clique. Toda a decisão (candidatos avaliados, modelo, janela, vencedores e pesos) é congelada em `attributionSnapshot`. Os detalhes dos modelos e do conflito de cupom estão em [Atribuição](/docs/conceitos/atribuicao).

<Tip>
  O `attributionSnapshot` é a trilha auditável da decisão de atribuição: ele lista todos os cliques considerados (`candidates[]`) e os vencedores com seus pesos em basis points (`winners[]`). Use-o para responder "por que esse afiliado foi escolhido?".
</Tip>

## Resolução de regra e comissão

Quando há vencedor, a Repass resolve a regra de comissão por precedência: regra custom do afiliado → regra do tier → regra padrão do Programa. A regra vigente é congelada em `ruleSnapshot` e a Comissão do **primeiro ciclo** é gerada junto com a Conversão. Detalhes em [Programas e regras](/docs/conceitos/programas-e-regras) e [Comissões](/docs/conceitos/comissoes).

A geração de comissão pode ser **pulada**; nesse caso `ruleSnapshot` fica `null` e `commissionSkippedReason` registra o motivo:

| `commissionSkippedReason` | Motivo                                                              |
| ------------------------- | ------------------------------------------------------------------- |
| `program_paused`          | Programa pausado.                                                   |
| `affiliate_paused`        | Afiliado pausado.                                                   |
| `self_referral`           | Autorreferência detectada (e-mail do cliente = e-mail do afiliado). |
| `product_not_applicable`  | `productId` declarado e fora do `applicableProductIds` da regra.    |

## Scoring de fraude

Toda Conversão recebe um **score de risco determinístico** entre `0` e `1`, calculado de forma síncrona na criação. O motor é baseado em regras com pesos: o score é a soma ponderada dos sinais disparados, saturada em `1` e arredondada a 4 casas decimais.

### Sinais

| Sinal (`key`)           | Peso | Dispara quando                                         |
| ----------------------- | ---- | ------------------------------------------------------ |
| `conversion_velocity`   | 0.4  | Intervalo clique → conversão menor que 60s.            |
| `click_burst`           | 0.2  | Mais de 30 cliques do visitante na última hora.        |
| `affiliate_account_age` | 0.2  | Conta do afiliado com menos de 7 dias.                 |
| `self_referral`         | 0.8  | Autorreferência detectada.                             |
| `disposable_email`      | 0.3  | Domínio do e-mail do cliente na lista de descartáveis. |

`conversion_velocity` só dispara se houver clique vencedor com timestamp. `disposable_email` só é avaliado quando o `customer.email` em texto puro é enviado (o hash não permite extrair o domínio).

<Note>
  A Conversão manual também roda fraude: autorreferência, idade da conta e e-mail descartável valem normalmente. `conversion_velocity` e `click_burst` ficam zerados por não haver clique.
</Note>

### Bandas de decisão

O campo `fraudDecision` é atribuído comparando o score com a [política de fraude](#politica-de-fraude) da organização (`approveBelow`, `reviewAbove`). Os limites são **inclusivos** na banda `monitor`:

| Condição (defaults `0.3` / `0.7`)      | `fraudDecision` | `status` na criação                  |
| -------------------------------------- | --------------- | ------------------------------------ |
| `score < approveBelow`                 | `approve`       | `approved`                           |
| `approveBelow <= score <= reviewAbove` | `monitor`       | `approved` (marcado para amostragem) |
| `score > reviewAbove`                  | `review`        | `pending` (trava em revisão)         |

Apenas a banda `review` trava a Conversão em `pending` e a coloca na fila de revisão manual. `monitor` é aprovado: não entra na fila. Toda a decisão é congelada em `fraudSnapshot` (`score`, `decision`, `signals[]` com `key`/`weight`/`triggered`, e a `policy` vigente).

<Tip>
  O `fraudSnapshot` deixa explícito **por que** uma Conversão foi flagada: o operador vê cada sinal disparado e o peso, não apenas o score final.
</Tip>

### Política de fraude

As bandas são configuráveis por organização. O default (quando não configurada) é `approveBelow = 0.3` e `reviewAbove = 0.7`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PUT https://api.userepass.com/settings/fraud-policy \
    -H "Authorization: Bearer rstr_..." \
    -H "Content-Type: application/json" \
    -d '{ "approveBelow": 0.25, "reviewAbove": 0.6 }'
  ```
</CodeGroup>

A validação exige `approveBelow <= reviewAbove` (do contrário retorna `400 parameter_invalid`). Veja a aba Referência da API para o schema completo.

### Isolamento por afiliado e auto-pause

Confirmar fraude **anula apenas a Conversão**: nada além dela é travado e o Programa segue ativo; o isolamento é por afiliado.

Há uma exceção automática: ao detectar autorreferência na criação, se o afiliado está `approved` e acumulou **3 ou mais** tentativas de autorreferência, ele é pausado automaticamente. O evento `affiliate.paused` é emitido com ator `system` e `reason: "self_referral_recurrence"`.

## Ciclo de vida da conversão

O campo `status` reflete o estado da Conversão:

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: criada com fraudDecision=review
    [*] --> approved: criada com fraudDecision approve/monitor
    pending --> approved: clearFraud (falso positivo)
    pending --> voided: confirmFraud / void
    approved --> voided: void
    voided --> [*]
    approved --> [*]
```

* `pending`: aguardando revisão de fraude (nasce assim quando `fraudDecision = review`).
* `approved`: ativa e válida para comissão.
* `voided`: anulada (void manual ou fraude confirmada).
* `refunded`: reembolsada. Este estado existe no enum, mas a transição para `refunded` vive no fluxo de [refund e clawback](/docs/guias/refund-e-clawback) de comissões/ingestão, não neste módulo.

### Revisão de fraude

Conversões travadas (`status = pending` e `fraudDecision = review`) aparecem em `GET /fraud/review-queue`. O operador inspeciona o score e os sinais em `GET /fraud/checks/{conversionId}` e decide:

* **Falso positivo**: `POST /fraud/checks/{conversionId}/clear` move a Conversão para `approved` + `cleared` e emite `fraud.cleared`.
* **Fraude confirmada**: `POST /fraud/checks/{conversionId}/confirm` move a Conversão para `voided` + `confirmed`, grava `voidReason: "fraud_confirmed"` e emite `fraud.confirmed`.

Ambas as ações só são válidas a partir de `fraudDecision = review`; fora disso retornam `409 conflict`.

```mermaid theme={null}
sequenceDiagram
    participant Op as Operador
    participant Queue as GET /fraud/review-queue
    participant Check as GET /fraud/checks/{id}
    participant Action as POST clear|confirm
    Op->>Queue: lista conversões pending + review
    Op->>Check: score, decisão e sinais detalhados
    alt falso positivo
        Op->>Action: /clear → approved + cleared (fraud.cleared)
    else fraude
        Op->>Action: /confirm → voided + confirmed (fraud.confirmed)
    end
```

### Void

`POST /conversions/{conversionId}/void` anula uma Conversão e exige um motivo. Aceita apenas Conversões em `pending` ou `approved`; outros estados retornam `409 conflict`. Emite `conversion.voided` com o status anterior, `after: "voided"` e o `reason`.

## Auditoria

`GET /conversions/{conversionId}/audit` reconstrói a trilha de auditoria a partir do [histórico de eventos](/docs/conceitos/event-store), e não do estado atual. Todos os eventos da Conversão são listados em ordem, permitindo reconstruir cada decisão: criação, comissão gerada, void, clear e confirm de fraude, e o auto-pause por reincidência.

Eventos emitidos pela Conversão:

| Evento               | Quando                                                                |
| -------------------- | --------------------------------------------------------------------- |
| `conversion.created` | Criação S2S ou manual (snapshot completo; `justification` no manual). |
| `commission.created` | Comissão do primeiro ciclo gerada na criação (uma por winner).        |
| `conversion.voided`  | `void`: inclui status anterior e motivo.                              |
| `fraud.cleared`      | `clear` de falso positivo.                                            |
| `fraud.confirmed`    | `confirm` de fraude.                                                  |
| `affiliate.paused`   | Auto-pause na 3ª autorreferência (ator `system`).                     |

## Exemplo: registrar uma conversão

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.userepass.com/conversions \
    -H "Authorization: Bearer rstr_..." \
    -H "Content-Type: application/json" \
    -d '{
      "type": "subscription_created",
      "amountCents": 9990,
      "currency": "BRL",
      "customer": { "id": "cus_8821", "email": "ana@example.com" },
      "sourceEventId": "stripe_evt_1Q2x...",
      "clickId": "clk_01J9Z3K8...",
      "productId": "plan_pro"
    }'
  ```

  ```json Resposta (201) theme={null}
  {
    "attributed": true,
    "conversion": {
      "id": "conv_01J9Z4M2...",
      "programId": "prog_01J8...",
      "affiliateId": "aff_01J7...",
      "clickId": "clk_01J9Z3K8...",
      "type": "subscription_created",
      "status": "approved",
      "amountCents": 9990,
      "currency": "BRL",
      "matchMethod": "click_id",
      "fraudDecision": "approve",
      "fraudSnapshot": {
        "score": 0.0,
        "decision": "approve",
        "signals": [],
        "policy": { "approveBelow": 0.3, "reviewAbove": 0.7 }
      }
    }
  }
  ```
</CodeGroup>

O endpoint exige ao menos **um** identificador de matching (`clickId`, `visitorId`, `emailHash`, `fingerprint`, `couponCode` ou `customer.email`). Sem nenhum, retorna `400 parameter_invalid`. Respostas: `201` quando atribuiu e criou; `200` quando `replayed`, `deduplicated` ou `attributed=false`. Veja a aba Referência da API para todos os campos e filtros de listagem.

## Regras relacionadas

<AccordionGroup>
  <Accordion title="Conversões">
    Conversão é o vínculo cliente↔afiliado, não o objeto que recebe dinheiro. Idempotência permanente por `sourceEventId`; dedupe de cliente ativo com exceção de `upgrade` via `parentConversionId`. Registro manual exige justificativa. Estados `pending`/`approved`/`voided`/`refunded`.
  </Accordion>

  <Accordion title="Antifraude">
    Score determinístico 0 a 1 com sinais ponderados; bandas configuráveis approve/monitor/review; auto-pause na 3ª autorreferência; isolamento por afiliado; clear de falso positivo.
  </Accordion>
</AccordionGroup>

## Próximos passos

<CardGroup cols={2}>
  <Card title="Atribuição" icon="diagram-project" href="/docs/conceitos/atribuicao">
    Modelos last/first click, multi-touch e conflito cupom × clique.
  </Card>

  <Card title="Comissões" icon="money-bill-trend-up" href="/docs/conceitos/comissoes">
    Como cada cobrança gera uma Comissão a partir do snapshot de regra.
  </Card>

  <Card title="Conversões server-to-server" icon="server" href="/docs/guias/conversoes-server-to-server">
    Guia prático de integração da API de conversões.
  </Card>

  <Card title="Refund e clawback" icon="rotate-left" href="/docs/guias/refund-e-clawback">
    O que acontece com a Conversão e as Comissões em um reembolso.
  </Card>
</CardGroup>
