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

# Hotmart → Metricaas com N8N

> Do postback da Hotmart à venda no funil, em três nós.

A Hotmart chama um webhook quando a compra é aprovada. O N8N recebe, acha o lead
pelo telefone e registra a venda. Três nós.

```
Webhook (Hotmart) → HTTP Request (GET /v1/leads?phone=) → HTTP Request (POST /v1/sales)
```

## 1. Webhook — receber o postback

Crie um nó **Webhook** (POST) e cadastre a URL na Hotmart em *Ferramentas →
Webhook*, no evento **Compra aprovada**.

<Warning>
  Valide o `hottok` antes de qualquer coisa. Um nó **IF** comparando
  `{{$json.hottok}}` com o valor que a Hotmart mostra no cadastro basta. Sem isso,
  qualquer um que descubra a URL posta venda no seu funil.
</Warning>

## 2. Achar o lead

Nó **HTTP Request**, método `GET`:

```
https://app.metricaas.com.br/v1/leads?phone={{ $json.data.buyer.checkout_phone_code }}{{ $json.data.buyer.checkout_phone }}&limit=1
```

Header: `Authorization: Bearer {{ $credentials.metricaas }}`

Não normalize o telefone antes de mandar. A API compara por **DDD + os 8 últimos
dígitos**, então DDI, parênteses, traço e o nono dígito não atrapalham.

Adicione um **IF** depois: se `data` vier vazio, o comprador nunca falou no
WhatsApp e não há lead para vincular. Encerre o fluxo — forçar a criação de um
lead aqui só gera um contato `organic` sem conversa nem origem.

## 3. Registrar a venda

Nó **HTTP Request**, método `POST`, body JSON:

```json theme={null}
{
  "lead_id": "={{ $json.data[0].id }}",
  "value": "={{ $('Webhook').item.json.data.purchase.price.value }}",
  "sold_at": "={{ new Date($('Webhook').item.json.data.purchase.order_date).toISOString() }}",
  "external_source": "hotmart",
  "external_id": "={{ $('Webhook').item.json.data.purchase.transaction }}"
}
```

O lead vai para a etapa de ganho com a data da compra, e a receita entra no painel.

<Check>
  Resposta `200` com `"duplicate": true` significa que a Hotmart reenviou o mesmo
  postback. **Não é erro e não deve virar retry** — a venda já estava lá.
</Check>

## Os detalhes que custam caro

<AccordionGroup>
  <Accordion title="Sempre mande external_id — mesmo em teste">
    É o que torna o reenvio inofensivo. A Hotmart reenvia postback quando o seu
    endpoint demora ou devolve erro, e o N8N tem retry próprio. Sem
    `external_id`, cada reenvio é uma venda nova e a receita infla sozinha.

    Vale para importação de histórico também: sem ele, rodar o fluxo duas vezes
    dobra a base.
  </Accordion>

  <Accordion title="Qual valor mandar: price.value ou o total pago?">
    `price.value` — o preço do produto. O total inclui juro de parcelamento, que
    vai para a adquirente e não é sua receita. Num período real a diferença foi
    de R$ 406 em R$ 7.117: 5,7% de receita inexistente inflando o ROAS.
  </Accordion>

  <Accordion title="Um checkout com order bump gera 3 postbacks">
    Cada produto é uma transação com seu próprio `transaction`. O N8N vai
    disparar três vezes — deixe. O lead aceita várias vendas e o total soma
    sozinho. Juntar tudo num POST só perderia a idempotência de duas delas.
  </Accordion>

  <Accordion title="A data vem em epoch — e o fuso importa">
    `order_date` é epoch em ms. O `new Date(...).toISOString()` acima resolve:
    gera `2026-08-07T14:49:12.000Z`, com fuso explícito.

    Se você montar a data à mão como `2026-08-07T11:49:12`, a API responde `422`.
    É proposital: sem offset, a compra da madrugada cai no dia anterior e o
    relatório da semana fica errado sem nenhum sinal de erro.
  </Accordion>

  <Accordion title="Reembolso e chargeback">
    A Hotmart manda `PURCHASE_REFUNDED` / `PURCHASE_CHARGEBACK` num evento
    separado. Um segundo fluxo pode buscar a venda por `external_id` em
    `GET /v1/sales` e chamar `DELETE /v1/sales/{id}`.

    O lead **não** volta de etapa automaticamente — ele pode ter outras compras.
    Se quiser marcá-lo, mova de etapa explicitamente.
  </Accordion>
</AccordionGroup>

## Credencial no N8N

Crie uma credencial do tipo **Header Auth**: nome `Authorization`, valor
`Bearer mcp_...`. Use um token de modo `write`.

<Note>
  O token carrega as permissões e os **escopos** de quem o emitiu. Se ele for de
  alguém restrito a um canal, o `GET /v1/leads?phone=` pode não achar um lead que
  existe. Chame `GET /v1/me` uma vez e confira `scopes` antes de colocar o fluxo
  em produção.
</Note>
