Plano e recursos necessários
Este fluxo usa checkout público, incluído no Growth e no Scale na oferta atual. Ative Integrações Externas para consultar a situação financeira no pós-venda. A emissão de NFS-e depende do recurso fiscal e da configuração da empresa.
Confira os requisitos por tipo de integração e compare os planos disponíveis.
Uma venda não termina no botão “assinar”
Em um SaaS, o ciclo começa na oferta e continua por muito tempo depois da ativação. O cliente escolhe um plano, informa seus dados, aceita termos e paga quando houver valor inicial. Seu sistema ainda precisa saber quando liberar, avisar, restringir ou restaurar o acesso.
O RecoPay concentra catálogo, contrato, cobrança, gateway, conciliação, régua de comunicação e NFS-e. O sistema terceiro concentra aquisição, onboarding e entrega do produto. A integração conecta esses dois domínios sem duplicar a verdade financeira.
O navegador inicia a venda, mas não confirma pagamento nem autoriza acesso. A ativação nasce de um evento do backend e o estado financeiro deve ser revalidado pela API protegida.
Entenda quem decide cada parte do ciclo
Seu sistema
- Apresenta a oferta e o onboarding.
- Recebe e processa o webhook.
- Provisiona a conta, workspace ou licença.
- Define carência e política de acesso.
- Mantém o vínculo local com a assinatura.
RecoPay
- Publica produtos, planos e componentes.
- Cria cliente, assinatura e cobranças.
- Processa e concilia pagamentos.
- Dispara automações e mensagens.
- Expõe fatos financeiros pela API.
| Canal | Origem | Uso correto |
|---|---|---|
| Checkout público | Navegador | Catálogo, cadastro, aceite, contratação e redirecionamento retornado pela API. |
Webhook primeiro_pagamento_confirmado | RecoPay → backend terceiro | Iniciar o provisionamento depois do primeiro pagamento confirmado. |
Webhook assinatura_ativada_sem_cobranca | RecoPay → backend terceiro | Iniciar o provisionamento quando o valor inicial for zero e não existir primeira cobrança. |
| URL de boas-vindas | Navegador do comprador | Conduzir a experiência após a ativação; nunca provar pagamento nem ativação. |
| API de integrações | Backend terceiro → RecoPay | Consultar fatos financeiros usando uma chave com escopo mínimo. |
Prepare produto, domínio e credenciais
- Cadastre o produto, os planos, componentes, gateways e termos no RecoPay.
- Em Planos, gere o identificador público do checkout e copie o código do produto.
- No produto, informe a origem HTTPS exata do checkout. Ela não pode conter caminho, wildcard, credenciais ou HTTP.
- Defina a URL de boas-vindas e o webhook de plano ativo conforme a arquitetura do seu produto.
- Em Webhooks, gere o segredo do produto e armazene-o somente no backend receptor.
- Para consultas financeiras, ative Integrações Externas e crie uma chave com o escopo
financeiro:ler.
| Campo do produto | Exemplo | Efeito no ciclo |
|---|---|---|
| URL do site | https://produto-a.example | Identifica a presença pública do produto. Hoje é informativa no contrato do Checkout. |
| URL do app / painel | https://app.produto-a.example | Identifica onde o cliente usa o produto. Não substitui a URL de boas-vindas. |
| URL do checkout deste produto | https://checkout.produto-a.example | Autoriza a origem e vincula aquele domínio ao produto, exibindo apenas seus planos. |
| URL de boas-vindas | https://app.produto-a.example/onboarding | Recebe o navegador após a ativação, com ou sem primeira cobrança. |
| Webhook de plano ativo | https://api.produto-a.example/webhooks/recopay/plano-ativo | Recebe um POST servidor a servidor para iniciar o provisionamento. |
O checkoutPublicoId pertence à empresa. Cada produto possui seu próprio código e pode ter sua própria origem. Assim, checkout.produto-a.example e checkout.produto-b.example usam o mesmo identificador da empresa, mas filtram produtos diferentes. O domínio .example é usado somente como placeholder nesta documentação.
Fluxo completo da visita ao pós-venda
Carregue a oferta
O Checkout consulta os planos do produto e o detalhe do plano diretamente na API.
Identifique o comprador
Cliente novo informa o cadastro. Cliente existente autentica sua identidade antes de contratar novamente.
Crie a contratação
Uma requisição idempotente cria o cliente e a assinatura. Com valor inicial positivo, cria também a primeira cobrança; com valor zero, ativa a assinatura sem gerar cobrança.
Conclua o pagamento, quando necessário
Consulte pagamentoNecessario. Quando for true, o navegador segue a checkoutUrl oficial para pagar; quando for false, a mesma propriedade leva à experiência de boas-vindas.
Ative e notifique
O RecoPay enfileira primeiro_pagamento_confirmado após o pagamento ou assinatura_ativada_sem_cobranca na ativação imediata de valor inicial zero.
Provisione e receba o cliente
Seu backend cria o acesso de forma idempotente; o navegador pode seguir para o onboarding.
Acompanhe a recorrência
O RecoPay gera e cobra os próximos ciclos. Seu backend consulta fatos financeiros para aplicar sua política de acesso.
Enviado ao RecoPay identifica a requisição criada pelo Checkout ou pelo seu backend. Recebido do RecoPay identifica a resposta da API. No webhook, recebido pelo seu backend é o POST enviado pelo RecoPay, e devolvido ao RecoPay é somente a resposta HTTP do seu endpoint.
Publique o Checkout para um produto
Na cópia hospedada em seu domínio, fixe o identificador público da empresa e o código do produto durante o build. Nenhum desses dois valores é uma chave secreta.
VITE_RECO_API_URL=https://api.recopay.com.br
VITE_RECOPAY_APP_URL=https://recopay.app
VITE_CLIENTES_URL=https://clientes.recopay.com.br
VITE_CHECKOUT_PUBLICO_ID=SEU_CHECKOUT_PUBLICO_ID
VITE_PRODUTO_CODIGO=produto-aO catálogo deve sempre vir da API. Isso mantém preço, componentes, formas de pagamento, periodicidade e termos sincronizados com o painel.
/checkout/{checkoutPublicoId}/produtos/{produtoCodigo}/planoscurl --request GET \
--url https://api.recopay.com.br/checkout/SEU_CHECKOUT_PUBLICO_ID/produtos/produto-a/planos \
--header "Accept: application/json"
curl --request GET \
--url https://api.recopay.com.br/checkout/SEU_CHECKOUT_PUBLICO_ID/planos/42 \
--header "Accept: application/json"O comprador pode alterar o JavaScript ou o payload. Envie IDs e quantidades válidos; o servidor recalcula e revalida todo o contrato.
Trate cliente novo e cliente existente
Antes da revisão final, verifique documento e e-mail. A resposta orienta a experiência sem permitir que um cadastro existente seja apropriado por outra pessoa.
/checkout/{checkoutPublicoId}/clientes/verificacaocurl --request POST \
--url https://api.recopay.com.br/checkout/SEU_CHECKOUT_PUBLICO_ID/clientes/verificacao \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data '{
"documento": "12345678000190",
"email": "[email protected]"
}'{
"mensagem": "Cliente ja cadastrado para esta empresa. Faca login para continuar a contratacao.",
"dados": {
"clienteExistente": true,
"documentoEmUso": true,
"emailEmUso": true,
"requerLogin": true
}
}Quando requerLogin for verdadeiro, autentique o cliente e envie o token retornado apenas na contratação. Cliente novo não usa Bearer.
/clientes/auth/logincurl --request POST \
--url https://api.recopay.com.br/clientes/auth/login \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data '{
"checkoutPublicoId": "SEU_CHECKOUT_PUBLICO_ID",
"email": "[email protected]",
"senha": "SENHA_DO_CLIENTE"
}'{
"sucesso": true,
"mensagem": "Login realizado com sucesso.",
"dados": {
"token": "rct_TOKEN_DO_CLIENTE",
"expiraEm": "2026-08-31T19:00:00Z",
"trocaSenhaObrigatoria": false
}
}Crie a assinatura e a primeira cobrança
/checkout/{checkoutPublicoId}/contratacoesGere uma Idempotency-Key para a tentativa lógica de compra. Se houver timeout, repita o mesmo payload com a mesma chave. Uma nova intenção de compra recebe outra chave.
curl --request POST \
--url https://api.recopay.com.br/checkout/SEU_CHECKOUT_PUBLICO_ID/contratacoes \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "Idempotency-Key: venda-produto-a-20260831-000184" \
--data '{
"planoId": 42,
"cliente": {
"tipoPessoa": "J",
"nome": "Empresa Exemplo LTDA",
"nomeFantasia": "Empresa Exemplo",
"documento": "12345678000190",
"email": "[email protected]",
"telefone": "11999999999",
"endereco": {
"cep": "01001000",
"logradouro": "Praca da Se",
"numero": "100",
"bairro": "Se",
"municipio": "Sao Paulo",
"uf": "SP",
"codigoMunicipioIbge": "3550308"
}
},
"componentes": [
{ "planoPrecoId": 73, "quantidade": 1 }
],
"formaPagamento": "pix",
"nfseModoEmissao": "automatico_pagamento",
"descricao": "Contratacao pelo checkout do Produto A",
"dataInicio": "2026-08-31",
"diaVencimento": 10,
"termosUsoAceitos": true
}'No exemplo acima, o cliente é novo. Para cliente existente, acrescente Authorization: Bearer TOKEN_DO_CLIENTE e mantenha documento e e-mail compatíveis com a identidade autenticada.
{
"mensagem": "Contratacao criada com sucesso.",
"dados": {
"checkoutUrl": "https://recopay.app/cobrancas/visualizar/...",
"contratacaoReferencia": "550e8400-e29b-41d4-a716-446655440000",
"assinaturaReferencia": "b9df2f8020574c2d812863f59a86a264",
"pagamentoNecessario": true,
"statusAssinatura": "pendente"
}
}Quando a composição inicial resulta em valor zero, a resposta mantém os mesmos campos, mas retorna pagamentoNecessario: false, statusAssinatura: "ativa" e checkoutUrl apontando para a URL de boas-vindas com somente ?ref={contratacaoReferencia}. Essa compatibilidade permite que checkouts já publicados continuem usando a URL retornada sem alteração.
Redirecione e aguarde a confirmação real
Use exclusivamente dados.checkoutUrl e consulte dados.pagamentoNecessario para distinguir o fluxo. Não componha uma URL com IDs internos e não use o redirecionamento como prova de pagamento ou ativação.
window.location.assign(resultado.dados.checkoutUrl);| Meio | Comportamento esperado | Quando liberar |
|---|---|---|
| Valor inicial zero | A assinatura nasce ativa e nenhuma cobrança de R$ 0 é criada. | Após o webhook assinatura_ativada_sem_cobranca. |
| Pix | Pode confirmar em poucos segundos. | Somente após o webhook de primeiro pagamento. |
| Cartão | Pode aprovar, processar ou recusar. | Somente quando o pagamento for confirmado. |
| Boleto | Normalmente permanece pendente até a compensação. | Depois da confirmação assíncrona, nunca na emissão. |
Quando o valor inicial é positivo, o primeiro pagamento confirmado ativa a assinatura, processa as operações aplicáveis e dispara o webhook do produto. Quando o valor inicial é zero, a assinatura é ativada e o webhook correspondente é enfileirado na mesma transação, sem criar cobrança nem simular pagamento.
Receba a ativação da assinatura
Em Planos → Webhooks, gere um segredo exclusivo para o produto e copie-o para o cofre de segredos do sistema receptor. O endereço configurado recebe um POST assinado; a chave rpk_ da API não participa desta assinatura.
O mesmo endpoint recebe dois fatos de ativação: primeiro_pagamento_confirmado, quando uma cobrança inicial foi paga, e assinatura_ativada_sem_cobranca, quando o valor inicial é zero. Ambos podem iniciar o provisionamento; o segundo não possui objeto cobranca.
Requisição HTTP enviada pelo RecoPay
O padrão de transporte é um POST HTTPS com JSON em UTF-8. Em cada retentativa, o eventoId e o corpo permanecem iguais; o timestamp e a assinatura são recalculados.
POST /webhooks/recopay/plano-ativo HTTP/1.1
Host: api.produto-a.example
Content-Type: application/json; charset=utf-8
User-Agent: RecoPay-Webhooks/1.0
X-Recopay-Event-Id: 93a8f7a1-37e4-4ce5-9c1c-41b918b17765
X-Recopay-Timestamp: 1788197538
X-Recopay-Signature: v1=HMAC_SHA256_EM_HEXADECIMAL_MINUSCULO
{ ...corpo JSON documentado abaixo... }| Cabeçalho | Obrigatório | Conteúdo |
|---|---|---|
Content-Type | Sim | application/json; charset=utf-8. |
User-Agent | Sim | RecoPay-Webhooks/1.0. É informativo e não substitui a validação HMAC. |
X-Recopay-Event-Id | Sim | UUID estável do evento. Também aparece em eventoId. |
X-Recopay-Timestamp | Sim | Instante da tentativa em segundos Unix. |
X-Recopay-Signature | Sim | v1= + HMAC-SHA256 em hexadecimal minúsculo de timestamp + "." + corpo original. |
Corpo JSON recebido pelo seu backend
O corpo abaixo é o documento completo do evento com pagamento. Preserve seus bytes originais até concluir a validação da assinatura.
{
"eventoId": "93a8f7a1-37e4-4ce5-9c1c-41b918b17765",
"evento": "primeiro_pagamento_confirmado",
"ocorridoEm": "2026-08-31T17:32:18.114Z",
"contratacaoReferencia": "550e8400-e29b-41d4-a716-446655440000",
"assinaturaReferencia": "b9df2f8020574c2d812863f59a86a264",
"cliente": {
"nome": "Empresa Exemplo LTDA",
"documento": "12345678000190",
"email": "[email protected]"
},
"checkout": {
"publicoId": "79be63f8765b11f18848bc2411061702"
},
"produto": {
"codigo": "produto-a"
},
"plano": {
"id": 42
},
"cobranca": {
"id": 2310,
"valor": 149.90,
"moeda": "BRL",
"vencimento": "2026-08-31",
"formaPagamento": "pix",
"gateway": "mercadopago"
}
}| Caminho | Tipo JSON | Obrigatório | Descrição |
|---|---|---|---|
eventoId | string (UUID) | Sim | Identificador estável para idempotência, igual ao cabeçalho X-Recopay-Event-Id. |
evento | string | Sim | primeiro_pagamento_confirmado ou assinatura_ativada_sem_cobranca. |
ocorridoEm | string (date-time) | Sim | Instante UTC do fato, no padrão ISO 8601. |
contratacaoReferencia | string (UUID) | Sim | Referência opaca compartilhada com contratação, callback e ferramentas administrativas. |
assinaturaReferencia | string | Sim | Referência pública estável usada nas consultas financeiras. |
cliente | object | Sim | Dados do titular da contratação. |
cliente.nome | string | Sim | Nome ou razão social. |
cliente.documento | string | Sim | CPF ou CNPJ normalizado, sem máscara. |
cliente.email | string | Sim | E-mail principal do cliente. |
checkout | object | Sim | Contexto do Checkout público. |
checkout.publicoId | string ou null | Sim | Identificador público da empresa que originou a contratação. |
produto | object | Sim | Produto que será provisionado. |
produto.codigo | string | Sim | Código público estável do produto. |
plano | object | Sim | Plano contratado. |
plano.id | integer | Sim | ID do plano no RecoPay. |
cobranca | object | No evento de pagamento | Primeira cobrança cujo pagamento ativou a assinatura. Não existe no evento sem cobrança. |
cobranca.id | integer | No evento de pagamento | ID da cobrança no RecoPay. |
cobranca.valor | number (decimal) | No evento de pagamento | Valor na unidade monetária indicada; não é inteiro em centavos. |
cobranca.moeda | string | No evento de pagamento | Código da moeda, como BRL. |
cobranca.vencimento | string (date) | No evento de pagamento | Data de vencimento no formato AAAA-MM-DD. |
cobranca.formaPagamento | string ou null | No evento de pagamento | Forma de pagamento efetiva, como pix, quando conhecida. |
cobranca.gateway | string ou null | No evento de pagamento | Gateway que confirmou o pagamento, quando conhecido. |
Ativação sem cobrança inicial
Quando o valor inicial é zero, o RecoPay envia o mesmo contexto de contratação, substitui cobranca por ativacao e não cria uma cobrança de R$ 0.
{
"eventoId": "93a8f7a1-37e4-4ce5-9c1c-41b918b17765",
"evento": "assinatura_ativada_sem_cobranca",
"ocorridoEm": "2026-09-19T16:30:00Z",
"contratacaoReferencia": "550e8400-e29b-41d4-a716-446655440000",
"assinaturaReferencia": "b9df2f8020574c2d812863f59a86a264",
"cliente": {
"nome": "Empresa Exemplo LTDA",
"documento": "12345678000190",
"email": "[email protected]"
},
"checkout": {
"publicoId": "79be63f8765b11f18848bc2411061702"
},
"produto": {
"codigo": "produto-a"
},
"plano": {
"id": 42
},
"ativacao": {
"status": "ativa",
"motivo": "valor_inicial_zero",
"valorInicial": 0.00,
"moeda": "BRL"
}
}| Caminho | Tipo JSON | Obrigatório | Descrição |
|---|---|---|---|
ativacao | object | No evento sem cobrança | Contexto da ativação imediata. |
ativacao.status | string | No evento sem cobrança | Retorna ativa. |
ativacao.motivo | string | No evento sem cobrança | Retorna valor_inicial_zero. |
ativacao.valorInicial | number (decimal) | No evento sem cobrança | Retorna 0.00. |
ativacao.moeda | string | No evento sem cobrança | Código da moeda, como BRL. |
cobranca.valor é decimal na moeda indicada, e não inteiro em centavos. Neste exemplo, 149.90 significa R$ 149,90.
curl --request POST \
--url https://api.produto-a.example/webhooks/recopay/plano-ativo \
--header "Content-Type: application/json" \
--header "X-Recopay-Event-Id: 93a8f7a1-37e4-4ce5-9c1c-41b918b17765" \
--header "X-Recopay-Timestamp: 1788197538" \
--header "X-Recopay-Signature: v1=HMAC_HEXADECIMAL" \
--data @plano-ativo.jsonO RecoPay considera entregue somente um HTTP 2xx. Falhas de rede, 408, 425, 429 e 5xx recebem novas tentativas com intervalo progressivo. Erros permanentes ficam visíveis em Planos e na Central de Problemas para reprocessamento manual.
Processe rápido, de forma idempotente e auditável
O receptor não deve criar toda a conta dentro da requisição. Valide primeiro a assinatura sobre os bytes originais, persista o evento, coloque o trabalho em fila e devolva qualquer status 2xx.
- Exija HTTPS e aceite apenas
POSTcom JSON. - Leia o corpo sem transformá-lo e recuse timestamps com diferença superior a cinco minutos.
- Calcule o HMAC com o segredo do produto e compare em tempo constante.
- Confirme que o header
X-Recopay-Event-Idcoincide comeventoId. - Aceite somente
primeiro_pagamento_confirmadoeassinatura_ativada_sem_cobranca; valide tambémproduto.codigo,plano.ide as referências públicas. - Crie restrição única por
eventoId. - Na mesma transação, grave o evento e crie um job de provisionamento.
- Responda
204 No Contente processe o job fora da requisição. - Crie ou atualize o tenant, associe o plano e registre o vínculo pelas referências do RecoPay.
- Se o mesmo evento chegar novamente, retorne sucesso sem duplicar licença, workspace ou usuário.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.post('/webhooks/recopay/plano-ativo',
express.raw({ type: 'application/json' }),
async (req, res) => {
const eventId = req.get('X-Recopay-Event-Id') || '';
const timestamp = req.get('X-Recopay-Timestamp') || '';
const received = req.get('X-Recopay-Signature') || '';
const now = Math.floor(Date.now() / 1000);
if (!/^\d+$/.test(timestamp) || Math.abs(now - Number(timestamp)) > 300) {
return res.sendStatus(401);
}
const rawBody = req.body;
const expected = 'v1=' + crypto
.createHmac('sha256', process.env.RECOPAY_WEBHOOK_SECRET)
.update(timestamp + '.')
.update(rawBody)
.digest('hex');
const a = Buffer.from(expected, 'ascii');
const b = Buffer.from(received, 'ascii');
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.sendStatus(401);
}
const evento = JSON.parse(rawBody.toString('utf8'));
const eventosAceitos = new Set([
'primeiro_pagamento_confirmado',
'assinatura_ativada_sem_cobranca'
]);
if (evento.eventoId !== eventId || !eventosAceitos.has(evento.evento)) {
return res.sendStatus(400);
}
await persistirEventoEEnfileirarProvisionamento(evento); // UNIQUE(eventoId)
return res.sendStatus(204);
});Resposta devolvida ao RecoPay
O contrato de confirmação usa o status HTTP, não um JSON de resposta. A forma recomendada é responder 204 No Content sem corpo depois de persistir o evento e o job na mesma transação.
HTTP/1.1 204 No ContentTambém é válido devolver outro 2xx. Se o seu framework exigir um corpo, o JSON abaixo é apenas uma confirmação local: o RecoPay não o interpreta nem o persiste como contrato.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"recebido": true,
"eventoId": "93a8f7a1-37e4-4ce5-9c1c-41b918b17765"
}| Resposta do receptor | Tratamento pelo RecoPay |
|---|---|
Qualquer 2xx | Entrega concluída. O corpo, se existir, é ignorado. |
Falha de rede, 408, 425, 429 ou 5xx | Falha transitória; retorna à fila com intervalo progressivo. |
Demais respostas 4xx | Falha permanente; não há repetição automática até correção e reprocessamento manual. |
| Dado local recomendado | Finalidade |
|---|---|
recopay_evento_id | Idempotência da recepção e correlação com o reprocessamento. |
recopay_contratacao_referencia | Correlacionar checkout, boas-vindas, logs e onboarding. |
recopay_produto_codigo | Selecionar o provisionador correto em empresas com vários produtos. |
recopay_plano_id | Mapear direitos e limites do produto contratado. |
recopay_cobranca_id | Auxiliar a auditoria financeira quando o evento possuir cobrança; deve aceitar nulo na ativação sem cobrança. |
recopay_assinatura_referencia | Consultar a situação financeira pela API. |
Espaços, ordem de propriedades e escapes alteram o HMAC. A assinatura deve ser verificada sobre o corpo bruto recebido, antes de JSON.parse.
Use o retorno do navegador apenas para experiência
Depois da ativação, com ou sem primeira cobrança, o navegador pode seguir para a URL de boas-vindas. A query string transporta somente uma referência opaca:
https://app.produto-a.example/onboarding?ref=550e8400-e29b-41d4-a716-446655440000Use ref para procurar a contratação já registrada pelo webhook e montar a experiência. Ela não contém e-mail, documento, plano, cobrança nem ID interno. Ainda assim, não confie no navegador para liberar acesso: a URL pode ser alterada, repetida ou nem sequer aberta.
contratacaoReferencia aparece na resposta da contratação, no webhook, no redirecionamento, nos logs do RecoPay e na tela administrativa de entregas. Grave a mesma referência nos logs do seu sistema.
Deixe a recorrência no RecoPay e consulte fatos financeiros
As próximas cobranças, conciliação, régua de cobrança e NFS-e permanecem no RecoPay. Seu sistema define a política comercial de acesso: por exemplo, avisar no primeiro dia, aplicar sete dias de carência e bloquear somente depois disso.
A consulta protegida exige uma chave de Integrações Externas com o escopo financeiro:ler e a referência pública da assinatura.
/integracoes/assinaturas/{assinaturaReferencia}/situacao-financeiracurl --request GET \
--url https://api.recopay.com.br/integracoes/assinaturas/REFERENCIA_PUBLICA/situacao-financeira \
--header "Authorization: Bearer SUA_CHAVE_RPK" \
--header "Accept: application/json"{
"assinaturaReferencia": "REFERENCIA_PUBLICA",
"assinaturaStatus": "ativa",
"cliente": {
"id": 318,
"nome": "Empresa Exemplo LTDA",
"documento": "12345678000190",
"email": "[email protected]"
},
"situacaoFinanceira": "inadimplente",
"possuiCobrancasEmAtraso": true,
"quantidadeCobrancasEmAtraso": 1,
"valorTotalEmAtraso": 149.90,
"maiorAtrasoDias": 8,
"consultadoEm": "2026-09-18T10:20:00-03:00",
"cobrancasEmAtraso": [
{
"id": 2441,
"vencimento": "2026-09-10",
"diasEmAtraso": 8,
"valor": 149.90,
"linkPagamento": "https://recopay.app/cobrancas/visualizar/..."
}
]
}situacaoFinanceira e maiorAtrasoDias informam o estado. Dias de carência, redução de recursos, bloqueio e desbloqueio pertencem ao seu produto.
O webhook entrega assinaturaReferencia, a mesma referência exigida por esta rota. Persista-a junto ao tenant provisionado; não use o ID numérico da cobrança como substituto.
Planeje falhas antes de abrir o tráfego
| Situação | Tratamento correto |
|---|---|
| Timeout ao criar a contratação | Repetir o mesmo payload com a mesma Idempotency-Key. |
| HTTP 400 | Corrigir dados, componentes, termos ou forma de pagamento; não repetir automaticamente. |
| HTTP 401/403 | Renovar a autenticação do cliente ou revisar chave e escopo no backend. |
| HTTP 409 | Autenticar cliente existente ou revisar conflito de idempotência. |
| HTTP 429/5xx | Aplicar backoff exponencial com jitter e limite de tentativas. |
| Webhook duplicado | Retornar sucesso para o eventoId já processado, sem provisionar novamente. |
| Webhook falhou | O RecoPay repete falhas transitórias; revise as falhas permanentes em Planos e reprocese depois da correção. |
| Callback não ocorreu | Não fazer nada financeiro. O webhook e a reconciliação sustentam o estado. |
| API financeira indisponível | Usar cache curto e não bloquear exclusivamente por timeout ou erro 5xx. |
- Nunca exponha a chave
rpk_no Checkout, no app mobile ou no navegador. - Sanitize
termosUsoHtmlantes de renderizar conteúdo persistido. - Não registre senha, token, chave Bearer nem payload completo com dados pessoais.
- Restrinja o webhook ao produto e empresa esperados; rejeite eventos de outros contextos.
- Separe os estados assinatura ativa, pagamento confirmado e provisionamento concluído.
- Crie alertas para eventos persistidos que não concluíram o provisionamento.
Teste o ciclo como uma máquina de estados
| Cenário | Resultado esperado |
|---|---|
| Cliente novo + Pix aprovado | Uma assinatura, uma cobrança, um webhook e um provisionamento. |
| Plano apenas por uso, sem opcional inicial | Assinatura ativa, nenhuma cobrança de R$ 0, um webhook assinatura_ativada_sem_cobranca e um provisionamento. |
| Cliente existente sem login | Contratação recusada sem alterar o cadastro. |
| Retry com a mesma chave e payload | Mesmo resultado, sem segunda assinatura, cobrança ou entrega de webhook. |
| Mesma chave com payload diferente | Conflito explícito; nenhuma mutação silenciosa. |
| Cartão recusado | Sem webhook de plano ativo e sem acesso definitivo. |
| Boleto emitido e ainda pendente | Sem ativação até a compensação. |
| Webhook entregue duas vezes | Segundo processamento tratado como sucesso idempotente. |
| Receptor responde 500 | Falha observável e item de reconciliação criado no sistema terceiro. |
| Comprador fecha a página após ativação | Provisionamento continua independente do callback do navegador. |
| Cobrança recorrente atrasada e depois paga | Carência aplicada e acesso restaurado após nova consulta financeira. |
Checklist de produção
- Produto e planos ativos
- Gateways validados
- Termos revisados
- Origem HTTPS exata
- ID público configurado
- Código do produto fixado
- Catálogo vindo da API
- Cliente existente autenticado
- Idempotency-Key persistida
- Redirecionamento pela URL retornada
- Webhook em HTTPS
- Segredo separado da chave da API
- HMAC sobre o corpo bruto
- Janela antirreplay de cinco minutos
- Comparação em tempo constante
- Fila de provisionamento
- Restrição única por eventoId
- Dois eventos de ativação aceitos
- Auditoria sem segredos
- Callback tratado como UX
- Reprocessamento homologado
- Chave rpk_ somente no backend
- Escopo financeiro:ler
- Política de carência definida
- Reconciliação operacional
Contrato resumido para agentes de IA
OBJETIVO
Integrar um ciclo de venda de SaaS com Checkout, API e webhook do RecoPay.
CONFIGURACAO DO CHECKOUT
- VITE_CHECKOUT_PUBLICO_ID identifica a empresa e nao e segredo.
- VITE_PRODUTO_CODIGO fixa o produto exibido neste dominio.
- A origem deve ser HTTPS exata e estar vinculada ao produto no RecoPay.
FLUXO DE AQUISICAO
1. GET /checkout/{checkoutPublicoId}/produtos/{produtoCodigo}/planos
2. GET /checkout/{checkoutPublicoId}/planos/{planoId}
3. POST /checkout/{checkoutPublicoId}/clientes/verificacao
4. Se existente, POST /clientes/auth/login e usar o token do cliente.
5. POST /checkout/{checkoutPublicoId}/contratacoes com Idempotency-Key.
6. Ler dados.pagamentoNecessario e dados.statusAssinatura.
7. Redirecionar exclusivamente para dados.checkoutUrl, que permanece obrigatoria nos dois fluxos.
8. Nao ativar acesso pelo navegador ou pela emissao de Pix/boleto.
ATIVACAO
- Gerar um segredo de webhook por produto e guarda-lo somente no backend.
- Receber primeiro_pagamento_confirmado quando uma cobranca inicial for paga.
- Receber assinatura_ativada_sem_cobranca quando o valor inicial for zero.
- Tratar os dois eventos como fatos de ativacao capazes de iniciar o provisionamento.
- No evento sem cobranca, validar ativacao.status = ativa e ativacao.motivo = valor_inicial_zero; nao exigir cobranca.
- Antes do JSON, validar timestamp de ate 5 minutos e HMAC-SHA256 de timestamp + "." + corpo bruto.
- Comparar X-Recopay-Signature em tempo constante.
- Confirmar X-Recopay-Event-Id = eventoId e persistir com restricao unica.
- Enfileirar provisionamento e responder 2xx rapidamente.
- Persistir contratacaoReferencia e assinaturaReferencia.
- Tratar /boas-vindas?ref=contratacaoReferencia somente como experiencia.
POS-VENDA
- O RecoPay gera recorrencia, cobra, concilia, notifica e processa NFS-e.
- Consultar no backend:
GET /integracoes/assinaturas/{assinaturaReferencia}/situacao-financeira
- Usar chave Bearer rpk_ com escopo financeiro:ler.
- A API retorna fatos; carencia e bloqueio pertencem ao sistema consumidor.
- Nao bloquear exclusivamente por timeout ou erro 5xx.
ENTREGA
- O RecoPay entrega somente quando recebe HTTP 2xx.
- A resposta recomendada e 204 No Content; qualquer corpo de resposta e ignorado.
- Rede, 408, 425, 429 e 5xx recebem retentativa progressiva.
- Demais respostas 4xx sao falhas permanentes ate reprocessamento manual.
- Falhas permanentes ficam disponiveis para analise e reprocessamento em Planos.
- Reprocessamentos repetem o mesmo eventoId e corpo; o receptor deve ser idempotente.
GUARDRAILS
- Nao acessar o banco RecoPay.
- Nao inventar preco, plano, componente ou forma de pagamento.
- Nao expor rpk_, senha ou token de cliente em logs.
- Reutilizar Idempotency-Key apenas no retry do mesmo payload.
- Provisionamento deve ser idempotente, auditavel e reversivel.Continue sua integração
Valide primeiro o contrato, depois construa a experiência
Execute em homologação os caminhos com pagamento e com valor inicial zero. Quando o backend estiver idempotente, aceitar os dois eventos de ativação e estiver observável, conecte o onboarding e a política de acesso.