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

# Integração e Triggers

> Como disparar o Scoring Engine via HTTP, schema completo do payload de depósito e comportamento de DLQ.

O Scoring Engine recebe eventos exclusivamente via HTTP REST. O `backend/` é o único cliente: ele observa eventos de transação no Kafka, aplica debounce + deduplicação em Redis (TTL 10min para financial, 30min para behavior) e dispara `POST /api/v1/scoring/transaction/deposit`.

<Info>
  Em versões anteriores existia também um consumer Kafka direto no scoring/ (tópico `atlas.l2.transaction.deposit`). Esse caminho foi removido — toda dedup/debounce vive no `backend/` para concentrar a lógica de orquestração e evitar duplicação.
</Info>

### Dead Letter Queue (output)

A DLQ `atlas.scoring.dlq` continua ativa, mas agora é alimentada apenas pelo **producer** do scoring/ — quando a publicação do resultado em `atlas.l3.user.score` falha, o envelope é roteado pra DLQ. Falhas de correlação Camunda retornam HTTP 502 ao caller (backend) e ficam visíveis nos logs do `backend/`.

```json theme={null}
{
  "score": { "eid": "...", "scores_final": 78, "...": "..." },
  "error": "kafka: write timeout publishing to atlas.l3.user.score",
  "original_topic": "atlas.l3.user.score",
  "failed_at": "2026-03-13T14:22:10Z"
}
```

<Warning>
  Monitore o tópico `atlas.scoring.dlq` em produção. Mensagens na DLQ indicam falha de publicação Kafka (broker offline, write timeout). Falhas de Camunda/payload aparecem como erros HTTP nos logs do backend.
</Warning>

***

## Trigger via HTTP

### Endpoint

```
POST /api/v1/scoring/transaction/deposit
Content-Type: application/json
```

### Respostas

| Status                     | Significado                                                 |
| -------------------------- | ----------------------------------------------------------- |
| `202 Accepted`             | Processo iniciado com sucesso                               |
| `200 OK`                   | Evento já está sendo processado (`businessKey = eid` ativo) |
| `422 Unprocessable Entity` | Payload inválido — campos obrigatórios ausentes             |
| `502 Bad Gateway`          | Camunda indisponível                                        |

**202 Accepted:**

```json theme={null}
{
  "eid": "550e8400-e29b-41d4-a716-446655440000",
  "process_instance_id": "a1b2c3d4-...",
  "status": "triggered"
}
```

**200 OK (já ativo):**

```json theme={null}
{
  "eid": "550e8400-e29b-41d4-a716-446655440000",
  "process_instance_id": "a1b2c3d4-...",
  "status": "already_active"
}
```

**422 Unprocessable Entity:**

```json theme={null}
{
  "error": "eid is required",
  "code": "INVALID_INPUT",
  "request_id": "req_xyz789"
}
```

***

## Schema do Payload de Depósito

Schema completo do evento `DepositEvent` para ambos os canais (Kafka e HTTP):

### Campos de Identidade e Evento

<ResponseField name="eid" type="string" required>
  Event ID único (UUID v4). Usado como `businessKey` para deduplicação no Camunda. Mesmo `eid` nunca é processado duas vezes.
</ResponseField>

<ResponseField name="organization_id" type="string" required>
  ID do operador na plataforma Atlas.
</ResponseField>

<ResponseField name="brand_id" type="string" required>
  ID da marca dentro da organização.
</ResponseField>

<ResponseField name="user_ext_id" type="string" required>
  Identificador externo do usuário no sistema do operador.
</ResponseField>

<ResponseField name="event" type="string" required>
  Sempre `"deposit"` para este endpoint.
</ResponseField>

<ResponseField name="timestamp" type="string" required>
  Timestamp do evento em UTC. Formato: `YYYY-MM-DDTHH:MM:SSZ`
</ResponseField>

### Campos de Perfil do Usuário (PII)

<Warning>
  Estes campos são processados apenas no consumer Go. **Nunca chegam ao Camunda** e não aparecem no output de score (conformidade LGPD).
</Warning>

<ResponseField name="first_name" type="string">Nome do usuário.</ResponseField>
<ResponseField name="last_name" type="string">Sobrenome do usuário.</ResponseField>
<ResponseField name="document" type="string">CPF do usuário (formato: `000.000.000-00`).</ResponseField>
<ResponseField name="birthdate" type="string">Data de nascimento. Formato: `YYYY-MM-DDTHH:MM:SSZ`</ResponseField>
<ResponseField name="email" type="string">E-mail do usuário.</ResponseField>
<ResponseField name="phone" type="string">Telefone com DDD internacional (ex: `+5511999999999`).</ResponseField>
<ResponseField name="ip" type="string">Endereço IP da sessão do usuário.</ResponseField>
<ResponseField name="country" type="string">País de residência (código ISO 3166-1 alpha-2).</ResponseField>
<ResponseField name="state" type="string">Estado (UF para Brasil, ex: `SP`).</ResponseField>
<ResponseField name="city" type="string">Cidade.</ResponseField>
<ResponseField name="post_code" type="string">CEP (formato: `00000-000`).</ResponseField>
<ResponseField name="gender" type="string">Gênero: `"M"`, `"F"` ou `null`.</ResponseField>
<ResponseField name="kyc_status" type="string">Status do KYC: `""` (não iniciado), `"pending"`, `"approved"`, `"rejected"`.</ResponseField>
<ResponseField name="registration_date" type="string">Data de cadastro. Formato: `YYYY-MM-DDTHH:MM:SSZ`</ResponseField>
<ResponseField name="registration_platform" type="string">Canal de cadastro: `"web"`, `"mobile_android"`, `"mobile_ios"`, `"api"`.</ResponseField>
<ResponseField name="status" type="string">Status da conta: `"ACTIVE"`, `"BLOCKED"`, etc.</ResponseField>

<ResponseField name="is_test_account" type="boolean" required>
  Se `true`, o evento é descartado silenciosamente pelo consumer sem processamento.
</ResponseField>

### Campos da Transação

<ResponseField name="transaction_id" type="string" required>
  ID único da transação no sistema do operador.
</ResponseField>

<ResponseField name="amount" type="number" required>
  Valor do depósito em BRL. Usado diretamente pelas regras DMN.
</ResponseField>

<ResponseField name="before_balance" type="number" required>
  Saldo antes do depósito em BRL.
</ResponseField>

<ResponseField name="after_balance" type="number" required>
  Saldo após o depósito em BRL. Deve ser `before_balance + amount`.
</ResponseField>

<ResponseField name="is_first_transaction" type="boolean" required>
  `true` se este é o primeiro depósito do usuário.
</ResponseField>

<ResponseField name="transaction_status" type="string" required>
  Status da transação: `"RECEIVED"`, `"COMPLETED"`, etc.
</ResponseField>

<ResponseField name="transaction_dt" type="string" required>
  Timestamp da transação em UTC.
</ResponseField>

### Scores de Entrada (sempre zerados)

<ResponseField name="scores_final" type="integer">Sempre `0` na entrada. Preenchido pelo engine.</ResponseField>
<ResponseField name="scores_financial" type="integer">Sempre `0` na entrada.</ResponseField>
<ResponseField name="scores_aml" type="integer">Sempre `0` na entrada.</ResponseField>
<ResponseField name="scores_behavior" type="integer">Sempre `0` na entrada.</ResponseField>
<ResponseField name="scores_ludopathy" type="integer">Sempre `0` — módulo futuro.</ResponseField>
<ResponseField name="score_ml" type="integer">Sempre `0` — módulo futuro.</ResponseField>

### IPS / Contas Vinculadas

<ResponseField name="ips" type="object" required>
  Informações de contas vinculadas detectadas por IP/device.

  <Expandable title="Campos do objeto ips">
    <ResponseField name="ips.has_linked_accounts" type="boolean" required>
      `true` se o dispositivo/IP está associado a outras contas.
    </ResponseField>

    <ResponseField name="ips.linked_accounts_count" type="integer" required>
      Número de contas vinculadas detectadas.
    </ResponseField>

    <ResponseField name="ips.linked_accounts" type="string[]" required>
      Lista dos `user_ext_id` das contas vinculadas.
    </ResponseField>
  </Expandable>
</ResponseField>

### Histórico de Apostas Esportivas

<ResponseField name="has_sports_bets" type="boolean" required>O usuário fez apostas esportivas no período.</ResponseField>
<ResponseField name="sports_bets_total_amount" type="number" required>Volume total de apostas esportivas em BRL.</ResponseField>
<ResponseField name="sports_bets_total_count" type="integer" required>Quantidade de apostas esportivas realizadas.</ResponseField>
<ResponseField name="sports_bets_high" type="boolean" required>`true` se `sports_bets_total_amount >= 2000`.</ResponseField>
<ResponseField name="sports_bets_moderate" type="boolean" required>`true` se `500 <= sports_bets_total_amount < 2000`.</ResponseField>
<ResponseField name="sports_bets_low" type="boolean" required>`true` se `sports_bets_total_amount < 500`.</ResponseField>

### Histórico de Apostas em Cassino

<ResponseField name="has_casino_bets" type="boolean" required>O usuário fez apostas em cassino no período.</ResponseField>
<ResponseField name="casino_bets_total_amount" type="number" required>Volume total de apostas em cassino em BRL.</ResponseField>
<ResponseField name="casino_bets_total_count" type="integer" required>Quantidade de rodadas/partidas em cassino.</ResponseField>
<ResponseField name="casino_bets_high" type="boolean" required>`true` se `casino_bets_total_amount >= 2000`.</ResponseField>
<ResponseField name="casino_bets_moderate" type="boolean" required>`true` se `500 <= casino_bets_total_amount < 2000`.</ResponseField>
<ResponseField name="casino_bets_low" type="boolean" required>`true` se `casino_bets_total_amount < 500`.</ResponseField>

### Histórico de Depósitos

<ResponseField name="has_deposits" type="boolean" required>Sempre `true` para eventos de depósito.</ResponseField>
<ResponseField name="deposits_total_amount" type="number" required>Volume total depositado no período (mês corrente). Usado nas regras `HIGH_MONTHLY_DEPOSIT_VOLUME` e `ELEVATED_MONTHLY_VOLUME`.</ResponseField>
<ResponseField name="deposits_total_count" type="integer" required>Número de depósitos no período.</ResponseField>
<ResponseField name="deposits_high" type="boolean" required>`true` se `deposits_total_amount >= 50000`.</ResponseField>
<ResponseField name="deposits_moderate" type="boolean" required>`true` se `20000 <= deposits_total_amount < 50000`.</ResponseField>
<ResponseField name="deposits_low" type="boolean" required>`true` se `deposits_total_amount < 20000`.</ResponseField>

### Histórico de Saques

<ResponseField name="has_withdrawals" type="boolean" required>O usuário realizou saques no período.</ResponseField>
<ResponseField name="withdrawals_total_amount" type="number" required>Volume total sacado em BRL. Usado para calcular `withdrawalRatio`.</ResponseField>
<ResponseField name="withdrawals_total_count" type="integer" required>Número de saques realizados.</ResponseField>
<ResponseField name="withdrawals_high" type="boolean" required>`true` se `withdrawals_total_amount / deposits_total_amount >= 0.90`.</ResponseField>
<ResponseField name="withdrawals_moderate" type="boolean" required>`true` se ratio entre 0.70 e 0.90.</ResponseField>
<ResponseField name="withdrawals_low" type="boolean" required>`true` se ratio \< 0.70.</ResponseField>

### Histórico de Ganhos

<ResponseField name="has_winnings" type="boolean" required>O usuário teve ganhos no período.</ResponseField>
<ResponseField name="winnings_total_amount" type="number" required>Volume total de ganhos em BRL.</ResponseField>
<ResponseField name="winnings_total_count" type="integer" required>Número de eventos de ganho.</ResponseField>
<ResponseField name="winnings_high" type="boolean" required>`true` se `winnings_total_amount >= 10000`.</ResponseField>
<ResponseField name="winnings_moderate" type="boolean" required>`true` se `1000 <= winnings_total_amount < 10000`.</ResponseField>
<ResponseField name="winnings_low" type="boolean" required>`true` se `winnings_total_amount < 1000`.</ResponseField>

***

## Payload de Exemplo Completo

<Tabs>
  <Tab title="Low Risk">
    Jogador recreativo. Nenhuma regra DMN ativada. Score final esperado: **0**.

    ```json theme={null}
    {
      "eid": "a1b2c3d4-0000-0000-0000-000000000001",
      "organization_id": "org_simulate",
      "brand_id": "brand_simulate",
      "user_ext_id": "user_c875f165-4ee6-4c70-ad6a-c1e0d28e66df",
      "event": "deposit",
      "ip": "177.82.154.12",
      "timestamp": "2026-03-13T14:00:00Z",

      "first_name": "Ana",
      "last_name": "Silva",
      "document": "345.678.901-23",
      "birthdate": "1990-06-15T00:00:00Z",
      "country": "BR",
      "state": "SP",
      "city": "São Paulo",
      "post_code": "01310-100",
      "email": "user+test-low@example.com",
      "gender": "F",
      "is_test_account": false,
      "kyc_status": "approved",
      "phone": "+5511987654321",
      "registration_date": "2025-09-15T10:00:00Z",
      "registration_platform": "web",
      "status": "ACTIVE",

      "amount": 150.00,
      "before_balance": 320.50,
      "after_balance": 470.50,
      "is_first_transaction": false,
      "transaction_status": "RECEIVED",
      "transaction_dt": "2026-03-13T13:59:55Z",
      "transaction_id": "txn_low_001",

      "scores_final": 0, "scores_financial": 0, "scores_behavior": 0,
      "scores_aml": 0, "scores_ludopathy": 0, "score_ml": 0,

      "ips": {
        "has_linked_accounts": false,
        "linked_accounts_count": 0,
        "linked_accounts": []
      },

      "has_sports_bets": true,
      "sports_bets_total_amount": 80.00,
      "sports_bets_total_count": 3,
      "sports_bets_high": false,
      "sports_bets_moderate": false,
      "sports_bets_low": true,

      "has_casino_bets": false,
      "casino_bets_total_amount": 0.00,
      "casino_bets_total_count": 0,
      "casino_bets_high": false,
      "casino_bets_moderate": false,
      "casino_bets_low": false,

      "has_deposits": true,
      "deposits_total_amount": 800.00,
      "deposits_total_count": 6,
      "deposits_high": false,
      "deposits_moderate": false,
      "deposits_low": true,

      "has_withdrawals": true,
      "withdrawals_total_amount": 40.00,
      "withdrawals_total_count": 2,
      "withdrawals_high": false,
      "withdrawals_moderate": false,
      "withdrawals_low": true,

      "has_winnings": false,
      "winnings_total_amount": 0.00,
      "winnings_total_count": 0,
      "winnings_high": false,
      "winnings_moderate": false,
      "winnings_low": false
    }
    ```
  </Tab>

  <Tab title="High Risk">
    Indicadores máximos de lavagem e ludopatia. Score final esperado: **\~98**.

    ```json theme={null}
    {
      "eid": "b9c8d7e6-ffff-ffff-ffff-ffffffffffff",
      "organization_id": "org_simulate",
      "brand_id": "brand_simulate",
      "user_ext_id": "user_highrisk_demo_001",
      "event": "deposit",
      "ip": "189.120.44.7",
      "timestamp": "2026-03-13T14:00:01Z",

      "first_name": "Carlos",
      "last_name": "Souza",
      "document": "123.456.789-00",
      "birthdate": "1985-03-22T00:00:00Z",
      "country": "BR",
      "state": "RJ",
      "city": "Rio de Janeiro",
      "post_code": "20040-020",
      "email": "user+highrisk@example.com",
      "gender": "M",
      "is_test_account": false,
      "kyc_status": "pending",
      "phone": "+5521987654321",
      "registration_date": "2026-02-23T08:00:00Z",
      "registration_platform": "mobile_android",
      "status": "ACTIVE",

      "amount": 35000.00,
      "before_balance": 5200.00,
      "after_balance": 40200.00,
      "is_first_transaction": false,
      "transaction_status": "RECEIVED",
      "transaction_dt": "2026-03-13T13:59:58Z",
      "transaction_id": "txn_high_001",

      "scores_final": 0, "scores_financial": 0, "scores_behavior": 0,
      "scores_aml": 0, "scores_ludopathy": 0, "score_ml": 0,

      "ips": {
        "has_linked_accounts": true,
        "linked_accounts_count": 7,
        "linked_accounts": [
          "user_linked_01", "user_linked_02", "user_linked_03",
          "user_linked_04", "user_linked_05", "user_linked_06", "user_linked_07"
        ]
      },

      "has_sports_bets": true,
      "sports_bets_total_amount": 5000.00,
      "sports_bets_total_count": 42,
      "sports_bets_high": true,
      "sports_bets_moderate": false,
      "sports_bets_low": false,

      "has_casino_bets": true,
      "casino_bets_total_amount": 4500.00,
      "casino_bets_total_count": 87,
      "casino_bets_high": true,
      "casino_bets_moderate": false,
      "casino_bets_low": false,

      "has_deposits": true,
      "deposits_total_amount": 80000.00,
      "deposits_total_count": 12,
      "deposits_high": true,
      "deposits_moderate": false,
      "deposits_low": false,

      "has_withdrawals": true,
      "withdrawals_total_amount": 76000.00,
      "withdrawals_total_count": 9,
      "withdrawals_high": true,
      "withdrawals_moderate": false,
      "withdrawals_low": false,

      "has_winnings": true,
      "winnings_total_amount": 12000.00,
      "winnings_total_count": 3,
      "winnings_high": true,
      "winnings_moderate": false,
      "winnings_low": false
    }
    ```
  </Tab>
</Tabs>

***

## Testando Localmente

Use o Makefile do módulo `scoring/` para publicar os dois perfis de teste:

```bash theme={null}
# Publica dois eventos: low-risk e high-risk com identidade padrão
make kafka-produce

# Sobrescrever a identidade do operador
make kafka-produce ORG_ID=org_acme BRAND_ID=brand_betx USER_EXT_ID=user_abc123

# Consumir o resultado do scoring
make kafka-consume TOPIC=atlas.l3.user.score

# Verificar mensagens na DLQ
make kafka-consume TOPIC=atlas.scoring.dlq
```

<Tip>
  Abra o **Camunda Cockpit** em `http://localhost:8092/camunda` (demo/demo) para acompanhar as instâncias de processo em execução, inspecionar variáveis e verificar Incidents.

  ```bash theme={null}
  make cockpit  # abre o Cockpit no browser
  ```
</Tip>
