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 com403);event_typedesconhecido responde422; 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.issuedcria 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ó opayment.confirmedgera 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_codeidentifica 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 doreferral_code— não é preciso enviar o serviço à parte. Se oreferral_codenã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.
- Header:
X-ProPartners-Signature - Valor:
HMAC_SHA256(corpo_bruto_da_requisição, SEGREDO)em hexadecimal minúsculo - SEGREDO: chave compartilhada, entregue fora deste documento (não versionar). Trocável por rotação.
- Assinar o corpo exato enviado (bytes), antes de qualquer reserialização.
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
- Todo evento deve ter um
event_idúnico e estável (string). O ProPartners deduplica por(origem, event_id): reenviar o mesmoevent_idresponde 200duplicatesem reprocessar. - Isso torna seguro o retry: em falha de rede ou resposta ≥ 500, reenvie o mesmo
event_id. - Recomendado: fila com retry exponencial (ex.: 1min, 5min, 30min, 2h) até receber 2xx.
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"}
owner:this_partner(o lead é doreferral_codeinformado) ouother_partner. Semreferral_code, a resposta traz só de quem é (partner_referral_code).transferable:truequando a indicação já pode passar a outro parceiro (transfer_reason:refusedoustale);falseem andamento (active) ou já convertida (converted).found: false: ninguém indicou essa pessoa neste fornecedor.
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) sefound: falsee há parceiro,POST /leads/com oreferral_code; (3) guarde olead_idno seu sistema e envie-o no contrato. Contrato criado com código de parceiro e sem lead continua aceito, mas volta comwarningsna 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:
- Ainda na carência (PENDING) — a comissão é revertida e nunca foi creditada: nada saiu, sem saldo negativo. É a razão de existir a carência.
- Já maturada/paga (CONFIRMED/PAID) — aplica-se clawback: um débito reverso gera saldo negativo no afiliado, recuperado automaticamente das próximas comissões.
{
"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
contract.createdantes do primeiropayment.confirmeddaquele contrato. Se um pagamento chegar antes do contrato existir, virá422— reenvie o pagamento após criar o contrato.payment.confirmeda cada ciclo pago (é o que sustenta a comissão recorrente/vitalícia).contract.cancelleduma vez, ao encerrar.
8. Ambientes
- Produção:
https://propartners.com.br/api/v1/webhooks/... - Homologação/testes: combine com o time ProPartners um segredo de teste e valide o fluxo completo antes de apontar para produção.
- User-Agent obrigatório: a produção está atrás do Cloudflare, que bloqueia (403) requisições
com User-Agent genérico/ausente de bibliotecas HTTP. Envie um
User-Agentque identifique seu sistema (ex.:ERP-Contratos/1.0). Um403"cloudflare" na resposta = User-Agent recusado, não é erro de assinatura. event_typedesconhecido →422: qualquer tipo fora da lista é recusado explicitamente (nada é criado/cancelado). Confira o nome exato do evento e o endpoint (contracts × payments).
9. Checklist de implementação (time de contratos)
- [ ] Capturar o
pp_ref(cookie/URL) no fechamento do contrato e enviar comoreferral_code. - [ ] Guardar o
tenant(UUID do fornecedor) recebido no onboarding. - [ ] Enviar
customer_document(CPF/CNPJ do cliente) nocontract.created— habilita a trava de auto-indicação. - [ ] Emitir
contract.createdno fechamento;payment.confirmeda cada pagamento;contract.cancelledno encerramento. - [ ] Emitir
payment.reversedem estorno/chargeback/reembolso, com o mesmoinvoice_iddo pagamento original. - [ ] Assinar cada requisição com HMAC-SHA256 do corpo bruto (header
X-ProPartners-Signature). - [ ]
event_idúnico e estável por evento; fila com retry exponencial em 5xx. - [ ] Tratar 202/200 como sucesso; 400/401 como erro de contrato (não reenviar); 422 como pendência de ordem.
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 depayment.confirmed, independente do status do contrato — nao envie pagamentos de contratos declinados.