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

# Atribuição multi-touch

> Exemplo prático de divisão de comissão entre múltiplos cliques.

Quando um cliente passa por vários cliques antes de converter, os modelos multi-touch dividem o crédito entre os afiliados envolvidos em vez de premiar um único clique. Este guia segue uma jornada concreta (quatro cliques de afiliados diferentes) e mostra exatamente como `linear`, `time_decay` e `position_based` calculam os pesos em basis points e quanto cada clique gera de Comissão.

Para a teoria dos modelos e da janela de atribuição, veja [Atribuição](/docs/conceitos/atribuicao). Aqui o foco é o cálculo passo a passo.

<Note>
  Dinheiro é sempre **centavos** (inteiro) e pesos são sempre **basis points** (bps): `10000 bps = 100%`. A soma dos pesos dos cliques vencedores é **exatamente** 10000.
</Note>

## A jornada de exemplo

Suponha um Programa `prog_01HZX...` com janela de atribuição de 30 dias e o cliente convertendo numa cobrança de **R\$ 200,00 (`amountCents: 20000`)** sob uma regra `percentage` de **10% (`percentageBps: 1000`)**.

O cliente acumulou quatro cliques dentro da janela, todos não-bot, não expirados e anteriores à conversão (regras de candidatura em [Atribuição](/docs/conceitos/atribuicao) e [Tracking](/docs/conceitos/tracking)):

| Clique  | Afiliado    | `occurredAt`          | Δt até o clique mais recente |
| ------- | ----------- | --------------------- | ---------------------------- |
| `clk_A` | `aff_ana`   | dia 0 (mais antigo)   | 14 dias                      |
| `clk_B` | `aff_bruno` | dia 7                 | 7 dias                       |
| `clk_C` | `aff_carla` | dia 11                | 3 dias                       |
| `clk_D` | `aff_diego` | dia 14 (mais recente) | 0 dia                        |

A conversão ocorre no dia 14. O modelo aplicado é o do **Programa** (campo `attributionModel`), congelado em `attributionSnapshot.model` na conversão.

```mermaid theme={null}
sequenceDiagram
    participant V as Visitante
    participant Repass as Tracking
    participant Conv as POST /conversions
    V->>Repass: clk_A (dia 0) · aff_ana
    V->>Repass: clk_B (dia 7) · aff_bruno
    V->>Repass: clk_C (dia 11) · aff_carla
    V->>Repass: clk_D (dia 14) · aff_diego
    V->>Conv: converte (dia 14, R$ 200,00)
    Conv->>Conv: matching → candidatos → pesos do modelo → comissões
```

## Como os pesos viram basis points

Cada modelo produz **pesos brutos** por clique. Esses pesos são normalizados para somar exatamente 10000 bps pelo **método dos maiores restos**:

<Steps>
  <Step title="Peso bruto por clique">
    O modelo atribui um número real a cada clique (parte igual no `linear`, decaimento exponencial no `time_decay`, posição no `position_based`).
  </Step>

  <Step title="Conversão proporcional para 10000">
    Cada peso vira `floor(peso_bruto / soma_dos_pesos × 10000)`. A soma desses pisos costuma ficar abaixo de 10000.
  </Step>

  <Step title="Distribuição da sobra (maiores restos)">
    Os bps restantes são distribuídos um a um para os cliques com maior fração descartada. Desempate: maior fração → `occurredAt` mais recente → ordem de entrada dos candidatos.
  </Step>

  <Step title="Descarte de pesos zero">
    Cliques que normalizam para **0 bps** são removidos dos vencedores: não recebem Comissão nem são marcados como convertidos (`convertedAt`).
  </Step>
</Steps>

A ordenação final dos vencedores é peso DESC → `occurredAt` DESC → ordem de entrada. O `winners[0]` resultante é o **vencedor primário** e define `conversion.affiliateId` e `conversion.clickId`.

<Warning>
  A divisão de pesos é **por clique** no `attributionSnapshot.winners[]` (para auditoria), mas a Comissão é **uma por afiliado**: se o mesmo afiliado tiver dois cliques vencedores, os pesos são **agregados** antes de calcular a Comissão. Ver [Comissões](/docs/conceitos/comissoes).
</Warning>

## Como a Comissão total vira parcelas

A Comissão **não** é arredondada peça por peça. O cálculo tem duas etapas:

1. **Comissão total da cobrança** (half-up sobre inteiros): `total = round_half_up(baseCents × percentageBps / 10000)`. Aqui: `round_half_up(20000 × 1000 / 10000) = 2000` centavos (R\$ 20,00).
2. **Split por peso** (`floor` por parte + sobra ao maior peso): cada parte vira `floor(total × weightBps / 10000)`; a sobra inteira (`total − Σ partes`) é creditada **integralmente ao winner de maior peso** (empate → o primário). Assim a soma das parcelas é **exatamente** o total.

Antes do split, winners do mesmo afiliado são **agregados por afiliado** (os pesos somam): uma cobrança gera no máximo uma Comissão de ciclo 1 por afiliado.

## Modelo `linear`

Divide o crédito **igualmente** entre os cliques candidatos. Com 4 cliques, cada um recebe `10000 / 4 = 2500 bps` exatos, sem sobra a distribuir.

| Clique  | Afiliado    | Peso (bps) | Base da Comissão | Comissão (`floor(2000 × peso / 10000)`) |
| ------- | ----------- | ---------- | ---------------- | --------------------------------------- |
| `clk_A` | `aff_ana`   | 2500       | `20000`          | `500` (R\$ 5,00)                        |
| `clk_B` | `aff_bruno` | 2500       | `20000`          | `500` (R\$ 5,00)                        |
| `clk_C` | `aff_carla` | 2500       | `20000`          | `500` (R\$ 5,00)                        |
| `clk_D` | `aff_diego` | 2500       | `20000`          | `500` (R\$ 5,00)                        |
|         | **Total**   | **10000**  |                  | **`2000` (R\$ 20,00)**                  |

Como os pesos são exatos (2500 bps cada), não há sobra a redistribuir: `floor(2000 × 2500 / 10000) = 500` para cada parte.

## Modelo `time_decay`

Pesa cada clique por `2^(−Δt / meia-vida)`, com **meia-vida fixa de 7 dias**. A referência é o clique mais recente; como os pesos são normalizados, deslocar a referência não muda o resultado.

Pesos brutos (Δt em dias):

* `clk_A` (Δt 14): `2^(−14/7) = 2^(−2) = 0,25`
* `clk_B` (Δt 7): `2^(−7/7) = 2^(−1) = 0,50`
* `clk_C` (Δt 3): `2^(−3/7) ≈ 0,7430`
* `clk_D` (Δt 0): `2^(0) = 1,00`

Soma dos pesos brutos ≈ `2,4930`. Normalizando para 10000 bps pelo método dos maiores restos (sobra de 2 bps para os dois maiores restos: `clk_A` e `clk_B`):

| Clique  | Afiliado    | Peso bruto | Peso (bps) | Comissão (`floor(2000 × peso / 10000)`) |
| ------- | ----------- | ---------- | ---------- | --------------------------------------- |
| `clk_A` | `aff_ana`   | 0,2500     | 1003       | `200` (R\$ 2,00)                        |
| `clk_B` | `aff_bruno` | 0,5000     | 2006       | `401` (R\$ 4,01)                        |
| `clk_C` | `aff_carla` | 0,7430     | 2980       | `596` (R\$ 5,96)                        |
| `clk_D` | `aff_diego` | 1,0000     | 4011       | `803` (R\$ 8,03)                        |
|         | **Total**   |            | **10000**  | **`2000` (R\$ 20,00)**                  |

O clique mais recente (`clk_D`) leva a maior fatia. A soma dos `floor` dá `1999`; a sobra de `1` centavo vai ao maior peso (`clk_D`), que fica com `802 + 1 = 803`. Cliques a muitas meias-vidas do mais recente tendem a 0 bps e são descartados dos vencedores.

<Tip>
  A soma das parcelas é **sempre exatamente** a Comissão total da cobrança: a sobra do `floor` é creditada inteira ao winner de maior peso, sem perder centavos. A auditoria reconstrói cada parte a partir do `attributionSnapshot`. Ver [Event store](/docs/conceitos/event-store).
</Tip>

## Modelo `position_based`

Dá **4000 bps (40%)** ao primeiro clique, **4000 bps (40%)** ao último e **2000 bps (20%)** divididos igualmente entre os cliques do meio. Com 4 cliques, os dois do meio (`clk_B`, `clk_C`) recebem `2000 / 2 = 1000 bps` cada.

| Clique  | Posição  | Afiliado    | Peso (bps) | Comissão (`floor(2000 × peso / 10000)`) |
| ------- | -------- | ----------- | ---------- | --------------------------------------- |
| `clk_A` | primeiro | `aff_ana`   | 4000       | `800` (R\$ 8,00)                        |
| `clk_B` | meio     | `aff_bruno` | 1000       | `200` (R\$ 2,00)                        |
| `clk_C` | meio     | `aff_carla` | 1000       | `200` (R\$ 2,00)                        |
| `clk_D` | último   | `aff_diego` | 4000       | `800` (R\$ 8,00)                        |
|         |          | **Total**   | **10000**  | **`2000` (R\$ 20,00)**                  |

Casos especiais do `position_based`:

* **1 clique** → 10000 bps (recebe tudo, primeiro = último).
* **2 cliques** → 5000 / 5000 bps (não há "meio").

## Requisição e resposta

A requisição é a mesma de qualquer conversão S2S: o modelo é resolvido a partir do Programa, não enviado no corpo. Veja a aba Referência da API para o schema completo.

```bash cURL theme={null}
curl -X POST https://api.userepass.com/conversions \
  -H "Authorization: Bearer rstr_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 1f0c9c2e-..." \
  -d '{
    "type": "subscription_created",
    "amountCents": 20000,
    "currency": "BRL",
    "customer": { "id": "cus_8821", "email": "cliente@exemplo.com" },
    "sourceEventId": "evt_gateway_998877",
    "visitorId": "vis_5fJ2..."
  }'
```

A conversão devolve os vencedores por clique em `attributionSnapshot.winners[]` (exemplo com `position_based`):

```json theme={null}
{
  "attributed": true,
  "conversion": {
    "id": "conv_01HZX...",
    "programId": "prog_01HZX...",
    "affiliateId": "aff_diego",
    "clickId": "clk_D",
    "matchMethod": "visitor_id",
    "status": "approved",
    "amountCents": 20000,
    "currency": "BRL",
    "attributionSnapshot": {
      "model": "position_based",
      "windowDays": 30,
      "matchMethod": "visitor_id",
      "candidates": [
        { "clickId": "clk_A", "affiliateId": "aff_ana",   "occurredAt": "2026-05-30T12:00:00.000Z" },
        { "clickId": "clk_B", "affiliateId": "aff_bruno", "occurredAt": "2026-06-06T12:00:00.000Z" },
        { "clickId": "clk_C", "affiliateId": "aff_carla", "occurredAt": "2026-06-10T12:00:00.000Z" },
        { "clickId": "clk_D", "affiliateId": "aff_diego", "occurredAt": "2026-06-13T12:00:00.000Z" }
      ],
      "winners": [
        { "clickId": "clk_D", "affiliateId": "aff_diego", "weightBps": 4000 },
        { "clickId": "clk_A", "affiliateId": "aff_ana",   "weightBps": 4000 },
        { "clickId": "clk_B", "affiliateId": "aff_bruno", "weightBps": 1000 },
        { "clickId": "clk_C", "affiliateId": "aff_carla", "weightBps": 1000 }
      ]
    }
  }
}
```

Cada afiliado vencedor recebe sua própria Comissão de ciclo 1, criada na **mesma transação** da conversão, emitindo um evento `commission.created` por Comissão (além de `conversion.created`). Detalhes do ciclo de vida da Comissão em [Comissões](/docs/conceitos/comissoes).

## Afiliado com mais de um clique

Se um mesmo afiliado aparece em dois cliques vencedores, o snapshot guarda **um winner por clique** (auditoria), mas a Comissão é **uma só**, com `calculation.weightBps` igual à **soma** dos pesos dos seus cliques. Exemplo no `linear` com `clk_A` e `clk_C` ambos de `aff_ana`:

* `winners[]`: `clk_A` (2500 bps) e `clk_C` (2500 bps), dois registros.
* Comissão de `aff_ana`: **uma**, com `calculation.weightBps = 5000` → `floor(2000 × 5000 / 10000) = 1000` (R\$ 10,00).

Todos os cliques vencedores são marcados com `convertedAt`.

## Interação com cupom (`split_50_50`)

Quando há também um Cupom de afiliado e o Programa usa a política `split_50_50`, o afiliado do cupom fica primário com **5000 bps** e os vencedores do clique são **reescalados** para somar os outros **5000 bps**, preservando as proporções do modelo. Exemplo: `linear` com 2 cliques + cupom → cupom 5000 + 2500 + 2500. As políticas `coupon_wins` e `click_wins` não dividem entre clique e cupom. Ver [Links e cupons](/docs/conceitos/links-e-cupons) e [Atribuição](/docs/conceitos/atribuicao).

## Próximos passos

<CardGroup cols={2}>
  <Card title="Atribuição" icon="route" href="/docs/conceitos/atribuicao">
    Modelos, janela e ordem de matching em detalhe.
  </Card>

  <Card title="Comissões" icon="coins" href="/docs/conceitos/comissoes">
    Cálculo, recorrência e ciclo de vida das Comissões geradas.
  </Card>

  <Card title="Conversões e fraude" icon="shield-check" href="/docs/conceitos/conversoes-e-fraude">
    O pipeline síncrono que aplica esses pesos no registro da conversão.
  </Card>

  <Card title="Conversões server-to-server" icon="server" href="/docs/guias/conversoes-server-to-server">
    Como enviar conversões S2S com matching por clique e e-mail.
  </Card>
</CardGroup>
