ProPartners

Webhooks de entrada: seu sistema → ProPartners

Contrato para o seu sistema de contratos, ERP ou gateway avisar o ProPartners sobre o ciclo de vida de cada cliente indicado: contrato fechado, fatura emitida, pagamento confirmado, estorno e cancelamento. Todo o dinheiro no ProPartners nasce de um evento confirmado aqui: a comissão só existe depois de um payment.confirmed.

Para o caminho inverso (o ProPartners avisando o seu sistema), veja os webhooks de saída. A API REST completa está em dashboard.propartners.com.br/api/v1/docs.

Notas rápidas: cada evento vai para o seu endpoint (/contracts/ ou /payments/); o User-Agent identificado é obrigatório (a borda bloqueia User-Agent genérico de biblioteca com 403); event_type desconhecido responde 422; nome e documento do cliente enviados em qualquer fatura ou pagamento atualizam o contrato (o último enviado vence).

1. Por que três eventos (e não só "pagamento")

Uma comissão só pode ser gerada para um contrato que o ProPartners já conhece e que já está vinculado a um afiliado. Por isso o sistema de contratos precisa notificar o ProPartners em três momentos do ciclo de vida — não apenas no pagamento:

Afiliado indica  →  cliente fecha contrato        →  [1] contract.created   (registra o contrato + atribui ao afiliado)
                    ERP emite a fatura (ainda não paga) → [4] invoice.issued  (comissão PREVISTA — motiva o afiliado a cobrar; NÃO paga)
                    cliente paga a mensalidade     →  [2] payment.confirmed  (gera a comissão REAL no ledger + converte a previsão)
                    pagamento é estornado/chargeback → [6] payment.reversed (desfaz a comissão — reverte na carência ou clawback)
                    cliente cancela                →  [3] contract.cancelled (encerra a recorrência + avisa o afiliado)

Se o passo [1] não acontecer, o pagamento em [2] não tem a quem atribuir e é ignorado.

Previsão ≠ pagamento. invoice.issued cria apenas uma comissão prevista (informativa): o afiliado vê "se o cliente pagar, você recebe R$X". Ela nunca entra no ledger nem no saldo a pagar. Só o payment.confirmed gera dinheiro real e converte a previsão em comissão. O payout paga apenas o confirmado.

2. Como o afiliado é identificado (atribuição)

Quando o cliente chega por um link de indicação (https://propartners.com.br/r/<CODIGO>), o ProPartners grava um cookie pp_ref=<CODIGO> (90 dias). O sistema de contratos deve capturar esse código (do cookie, de um campo oculto no formulário, ou da URL) e enviá-lo como referral_code no evento contract.created.

A adesão do afiliado é POR SERVIÇO. Cada referral_code identifica um vínculo do afiliado com um serviço do fornecedor; o serviço define a regra de comissão (percentual, valor fixo ou selo). O ProPartners resolve o serviço a partir do referral_code — não é preciso enviar o serviço à parte. Se o referral_code não corresponder a um vínculo aprovado e ativo, o evento responde 422.

3. Autenticação — HMAC SHA-256 (obrigatório)

Toda requisição é assinada. Sem assinatura válida, o ProPartners responde 401.

Exemplo (Python):

import hmac, hashlib, requests, json
corpo = json.dumps(payload, separators=(",", ":")).encode()   # serialize UMA vez
assinatura = hmac.new(SEGREDO.encode(), corpo, hashlib.sha256).hexdigest()
requests.post(URL, data=corpo,
              headers={"Content-Type": "application/json", "X-ProPartners-Signature": assinatura})

4. Idempotência e reentrega

5. Endpoints

Base de produção: https://propartners.com.br

Evento Método + rota
[1] contract.created POST /api/v1/webhooks/contracts/
[2] payment.confirmed POST /api/v1/webhooks/payments/
[3] contract.cancelled POST /api/v1/webhooks/contracts/
[4] invoice.issued POST /api/v1/webhooks/payments/
[5] invoice.cancelled POST /api/v1/webhooks/payments/
[6] payment.reversed POST /api/v1/webhooks/payments/

Os eventos [1] e [3] vão para /contracts/; [2], [4], [5] e [6] vão para /payments/. Em cada endpoint, o campo event_type diferencia o handler. Em /payments/, um payload sem event_type é tratado como payment.confirmed (compatibilidade com a integração atual).

5.1 contract.created — registrar contrato e atribuir ao afiliado

{
  "event_id": "ctr-000123-created",
  "event_type": "contract.created",
  "external_id": "000123",
  "customer_name": "Cliente Exemplo LTDA",
  "customer_document": "12345678000199",
  "monthly_value": "499.00",
  "referral_code": "AB12CD34",
  "lead_id": "UUID-do-lead-no-propartners"
}
Campo Obrig. Descrição
event_id sim Id único do evento (idempotência).
event_type sim Fixo: contract.created.
referral_code sim Código do vínculo afiliado↔serviço (cookie pp_ref). Resolve o fornecedor e o serviço. Sem vínculo aprovado ⇒ 422.
external_id sim Id do contrato no sistema de contratos. Chave de referência dos eventos [2] e [3] e da idempotência por (tenant, external_id).
customer_name sim Nome do cliente final.
customer_document recomendado CPF/CNPJ do cliente final (só dígitos ou com máscara). Alimenta a trava antifraude de auto-indicação: se for igual ao documento do afiliado, a comissão nasce em quarentena (não paga) até revisão. Sem ele, a trava não pode agir.
monthly_value sim Valor mensal do contrato (string decimal, ponto).
lead_id esperado UUID do lead no ProPartners (o mesmo que o seu sistema recebeu ao registrar a indicação, ou o que a consulta por contato devolve). O contrato o vincula e o lead é fechado (CONVERTED). Só vale se o lead for deste parceiro; lead de outro parceiro gera disputa (abaixo). Sem lead_id e sem indicação encontrada pelo contato, o contrato é aceito mas fica sem rastro de indicação (auditoria contract.without_lead; na API REST a resposta traz warnings).
customer_phone não Telefone do cliente (com DDD, com ou sem 55). Sem lead_id, o ProPartners procura por ele a indicação da pessoa neste fornecedor e vincula sozinho.
customer_email não E-mail do cliente. Mesmo uso do customer_phone.
~~started_at~~ não NÃO é lido pelo webhook — o campo é ignorado se enviado. O início do contrato é sempre a data de recebimento do evento (contract.created). Para informar started_at retroativo (ex.: importação), use POST /api/v1/contracts/ (API direta), que aceita o campo — data futura é rejeitada.

O tenant e o serviço são derivados do referral_code (não precisam ser enviados).

Indicação única e disputa

Dentro de um fornecedor, uma pessoa (telefone ou e-mail) tem um único lead, com um parceiro por vez: vale quem cadastrou primeiro. Se a pessoa recusou o contato, ou a indicação ficou 90 dias sem contrato, outro parceiro pode indicá-la e o lead (mesmo id) é transferido; quem perdeu recebe o aviso lead.transferred. Lead já convertido em contrato não muda de parceiro (a comissão é vitalícia).

Antes de fechar uma venda, consulte se a pessoa já é indicação de alguém:

GET /api/v1/leads/lookup/?phone=11912345678&referral_code=AB12CD34
Authorization: Bearer tp_live_…   (chave do fornecedor)

{"found": true, "lead_id": "bdc3b01f…", "status": "AUTHORIZED", "owner": "other_partner",
 "partner_referral_code": "Y9JQCMY0", "claimed_at": "2026-09-29T13:26:54Z",
 "transferable": false, "transfer_reason": "active", "name": "João Silva"}

Registrar uma indicação pela API (POST /api/v1/leads/, chave do fornecedor). Ao cadastrar, o seu sistema declara que a pessoa autorizou o contato (fica registrado quem e quando). Basta o código do parceiro (referral_code): fornecedor e vínculo são derivados dele (tenant/enrollment continuam aceitos). A resposta segue a regra de indicação única, então trate os três códigos:

POST /api/v1/leads/
{"referral_code": "AB12CD34", "name": "João Silva", "phone": "11911112222", "email": ""}

201 {"id": "bdc3b01f…", "outcome": "created", "partner_referral_code": "AB12CD34", "status": "AUTHORIZED", …}

Ordem obrigatória na venda indireta (decisão de 13/10/2026): assim que a pessoa e o contato forem identificados no atendimento, (1) GET /leads/lookup/ pelo telefone/e-mail; (2) se found: false e há parceiro, POST /leads/ com o referral_code; (3) guarde o lead_id no seu sistema e envie-o no contrato. Contrato criado com código de parceiro e sem lead continua aceito, mas volta com warnings na resposta e fica registrado na auditoria (contract.without_lead).

HTTP outcome Significado O que fazer
201 created Lead novo deste parceiro Guardar o id (é o lead_id do contrato)
200 existing O mesmo parceiro já tinha indicado a pessoa Sucesso; usar o id devolvido, sem avisar "nova indicação"
201 transferred Era de outro parceiro, mas a pessoa recusou o contato ou passaram 90 dias sem contrato: o lead (mesmo id) passou a ser deste parceiro Sucesso; o ProPartners avisa quem perdeu
409 — A pessoa tem indicação em andamento (code: "active") ou já convertida (code: "converted") de outro parceiro Não é falha de integração: informe ao parceiro que a indicação vale para quem cadastrou primeiro, sem dizer quem é o outro

Corpo do 409:

{"detail": "Esta pessoa já foi indicada por outro parceiro em 29/09/2026. A indicação vale para quem cadastrou primeiro enquanto estiver em andamento (até 90 dias sem contrato).",
 "code": "active", "since": "2026-09-29T13:26:54Z"}

Indicações em aberto de um parceiro (para o seu sistema saber o que ele tem em andamento):

GET /api/v1/leads/?referral_code=AB12CD34&open=1&page_size=200
Authorization: Bearer tp_live_…   (chave do fornecedor)

{"count": 2, "next": null, "previous": null, "results": [
  {"id": "bdc3b01f…", "name": "João Silva", "phone": "11911112222", "email": "[email protected]",
   "status": "AUTHORIZED", "status_display": "Autorizado", "claimed_at": "2026-09-29T13:26:54Z",
   "enrollment": "8d34d8bd…", "tenant": "e55df0ed…", "created_at": "2026-09-29T13:26:54Z"}
]}

open=1 devolve só as indicações ativas, ainda não convertidas em contrato e não recusadas pela pessoa; sem ele, vêm todas (use status=CONVERTED, AUTHORIZED ou LOST para filtrar). search= procura por nome, telefone (DDD+número, sem 55) ou e-mail. Paginação: page_size até 200 e page.

Disputa. Se o contract.created chegar para o parceiro B e a pessoa tiver indicação em andamento (ou já convertida) do parceiro A, o contrato é criado marcado como disputa (dispute: true no evento contract.created de saída), sem lead vinculado. As comissões dele ficam retidas (quarentena) até a equipe do fornecedor escolher o parceiro em Gestão → Contratos → Resolver; aí o lead é vinculado, o contrato e as comissões retidas vão para o vencedor e o outro parceiro é avisado. A negociação do contrato não espera a disputa: só a comissão. A comissão é congelada neste momento a partir do serviço (percentual fixo, valor fixo, ou percentual do selo vigente do afiliado naquele serviço) — mudanças futuras de selo não afetam contratos já criados.

5.2 payment.confirmed — pagamento confirmado gera comissão

Enviar a cada pagamento confirmado (mensalidade recorrente inclusa).

{
  "event_id": "pay-000123-2026-07",
  "event_type": "payment.confirmed",
  "tenant": "UUID-do-fornecedor",
  "contract_external_id": "000123",
  "amount": "499.00",
  "competence": "2026-07-01",
  "invoice_id": "NF-99887",
  "is_courtesy": false
}
Campo Obrig. Descrição
event_id sim Id único do evento.
event_type sim Fixo: payment.confirmed.
tenant + contract_external_id sim Resolve o contrato. (Alternativa: contract = UUID do contrato no ProPartners.)
amount sim Valor efetivamente pago. Pagamento parcial gera comissão proporcional.
competence não Mês de competência (YYYY-MM-DD, normalizado p/ dia 1). Default: hoje.
invoice_id não Nº da fatura/NF — segunda chave de idempotência (evita comissão dupla).
is_courtesy não true em mês grátis/cortesia ⇒ comissão zero (registra o evento sem creditar).

Regras aplicadas automaticamente: comissão só de pagamento confirmado; cortesia = 0; parcial = proporcional; carência (maturação) do fornecedor antes de liberar o saque; idempotência dupla por event_id e por invoice_id.

5.3 contract.cancelled — encerra a recorrência

{
  "event_id": "ctr-000123-cancelled",
  "event_type": "contract.cancelled",
  "tenant": "UUID-do-fornecedor",
  "contract_external_id": "000123",
  "reason": "inadimplência 60 dias"
}

Encerra a recorrência de comissões do contrato e dispara o aviso ao afiliado (pelo seu sistema, via notify.message, e/ou por e-mail). reason é opcional (entra na notificação e na auditoria). Também descarta (DROPPED) qualquer comissão prevista ainda em aberto daquele contrato.

5.4 invoice.issued — fatura EMITIDA gera comissão PREVISTA (não paga)

Enviar quando o ERP emite a fatura da mensalidade (antes do pagamento). Cria uma previsão: o afiliado passa a ver "previsto: se o cliente pagar, você recebe R$X". Isso o motiva a ajudar a cobrar. Não credita o ledger e não conta como saldo a pagar.

{
  "event_id": "inv-000123-2026-08",
  "event_type": "invoice.issued",
  "tenant": "UUID-do-fornecedor",
  "contract_external_id": "000123",
  "amount": "499.00",
  "competence": "2026-08-01",
  "invoice_id": "NF-99912",
  "is_courtesy": false
}
Campo Obrig. Descrição
event_id sim Id único do evento (idempotência por (origem, event_id)).
event_type sim Fixo: invoice.issued.
tenant + contract_external_id sim Resolve o contrato (igual ao payment.confirmed). Alternativa: contract = UUID.
amount sim Valor da fatura emitida. A comissão prevista usa a regra congelada do contrato (percentual/fixo).
competence não Mês de competência (YYYY-MM-DD, dia 1). Default: hoje. Casa a previsão ao pagamento.
invoice_id não Nº da fatura. Chave de idempotência por (tenant, contract, invoice_id) e de conversão.
is_courtesy não true em cortesia ⇒ previsão zero.

A previsão nasce EXPECTED. Quando o payment.confirmed daquela fatura chega, ela vira CONVERTED (casada por invoice_id; na falta, por contract + competence) e a comissão real é creditada no ledger. Se o contrato/fatura for cancelado antes do pagamento, a previsão vira DROPPED.

5.5 invoice.cancelled — fatura cancelada antes do pagamento (opcional)

{
  "event_id": "inv-000123-2026-08-cancel",
  "event_type": "invoice.cancelled",
  "tenant": "UUID-do-fornecedor",
  "contract_external_id": "000123",
  "invoice_id": "NF-99912"
}

Descarta (DROPPED) a comissão prevista daquela fatura. Não afeta comissões já pagas/confirmadas.

5.6 payment.reversed — estorno/chargeback/reembolso desfaz a comissão

Enviar quando um pagamento antes confirmado é revertido: estorno, chargeback, reembolso ou contestação. O ProPartners desfaz a comissão daquele pagamento, decidindo automaticamente o tratamento pelo estado da comissão:

{
  "event_id": "rev-000123-2026-07",
  "event_type": "payment.reversed",
  "tenant": "UUID-do-fornecedor",
  "contract_external_id": "000123",
  "invoice_id": "NF-99887",
  "reason": "chargeback bandeira"
}
Campo Obrig. Descrição
event_id sim Id único do evento (idempotência).
event_type sim payment.reversed (aceita também payment.refunded, payment.chargeback, payment.failed).
tenant + contract_external_id sim Resolve o contrato. Alternativa: contract = UUID.
invoice_id recomendado Nº da fatura do pagamento revertido — casa a comissão exata a desfazer. Sem ele, desfaz todas as comissões daquele contrato ainda ativas.
reason não Motivo (entra na auditoria e no aviso ao afiliado).

Idempotente: comissões já revertidas/clawback são ignoradas. Emite commission.reversed (saída) para os avisos. Envie o mesmo invoice_id usado no payment.confirmed correspondente.

5.7 Troca de titularidade — SEM evento próprio (reconciliação automática)

Quando os dados do cliente mudam sem trocar o contrato (ex.: PF que se formaliza e vira PJ, correção de razão social), não há chamada extra: basta enviar customer_name e customer_document atualizados no próximo invoice.issued ou payment.confirmed daquele contract_external_id. O ProPartners compara com o que está gravado e, se diferente, atualiza o contrato — o último enviado sempre vence. Recomenda-se enviar esses dois campos em TODA fatura.

O que NUNCA muda na reconciliação: a comissão congelada e a atribuição ao afiliado — a recorrência segue no mesmo contrato. O documento novo vale para as próximas comissões (inclusive a trava antifraude de auto-indicação).

6. Respostas do ProPartners

HTTP Significado Ação do sistema de contratos
202 Accepted Evento aceito e processado. Marcar como entregue.
200 { "detail": "duplicate" } event_id já processado. Marcar como entregue (não reenviar).
400 JSON inválido ou campos faltando. Não reenviar; corrigir payload.
401 Assinatura HMAC inválida. Não reenviar; revisar segredo/assinatura.
422 Erro de processamento (ex.: contrato inexistente). Reenviar após corrigir a causa (ex.: enviar contract.created antes).
5xx Falha temporária no ProPartners. Reenviar com backoff (mesmo event_id).

7. Ordem e garantias esperadas

  1. contract.created antes do primeiro payment.confirmed daquele contrato. Se um pagamento chegar antes do contrato existir, virá 422 — reenvie o pagamento após criar o contrato.
  2. payment.confirmed a cada ciclo pago (é o que sustenta a comissão recorrente/vitalícia).
  3. contract.cancelled uma vez, ao encerrar.

8. Ambientes

9. Checklist de implementação (time de contratos)


10. API REST: contrato em duas etapas e lista de parceiros

Alem dos webhooks (secoes acima), o sistema de origem pode usar a API REST com a API key do fornecedor (Authorization: Bearer tp_live_...). A chave ja identifica a empresa: todas as respostas vem filtradas para ela. Envie sempre um User-Agent identificado (User-Agent generico de biblioteca e bloqueado pelo firewall com 403).

Base: https://dashboard.propartners.com.br/api/v1

10.1 Lista de parceiros do fornecedor

GET /api/v1/enrollments/?page_size=100

Retorna os vinculos de parceiro da empresa da API key, paginados (campo next). Campos principais: referral_code (codigo interno do parceiro), name, status (APPROVED = apto a indicar), tier, balance.

10.2 Criar contrato AGUARDANDO ATIVACAO (1a chamada)

POST /api/v1/contracts/
{
  "referral_code": "A1B2C3D4",      // codigo interno do parceiro (obrigatorio*)
  "supplier_code": "EFSP",          // codigo do fornecedor (opcional; conferido)
  "external_id":  "CT-000123",      // NUMERO do contrato no sistema de origem
  "customer_name": "Cliente Exemplo LTDA",
  "monthly_value": "109.99",        // opcional
  "status": "PENDING"               // PENDING = aguardando ativacao (omitir = ACTIVE)
}

Alternativa a referral_code: enviar enrollment (UUID do vinculo). A comissao do parceiro e congelada na criacao* (condicao vigente do servico). external_id e unico por fornecedor — repetir o numero da 400.

10.3 Atualizar o status do contrato (2a chamada)

POST /api/v1/contracts/set-status/
{ "external_id": "CT-000123", "status": "ACTIVE" }     // ou "DECLINED" / "CANCELLED"

Transicoes: PENDING -> ACTIVE (ativa), PENDING -> DECLINED (declinado), PENDING/ACTIVE -> CANCELLED (cancela e encerra a recorrencia de comissao, com avisos). Repetir o mesmo status e idempotente (200). Ativar/declinar contrato que nao esta aguardando ativacao da 400. Tambem aceita id (UUID) no lugar de external_id.

Faturas e pagamentos continuam pelos webhooks (secao 5) usando o MESMO contract_external_id. Comissao so nasce de payment.confirmed, independente do status do contrato — nao envie pagamentos de contratos declinados.