Pular para o conteúdo

Endpoint de preço

Um produto de preço variável não tem valor de tabela: quem sabe quanto cobrar em cada ciclo é o seu sistema. Você cadastra uma URL no produto e, na virada de cada ciclo, o motor faz um POST nela perguntando o valor daquele assinante naquele período.

POST https://seusistema.com.br/pricing
Content-Type: application/json
X-Revtriever-Signature: t=1756500000,v1=1fa9c2…
{
"version": 1,
"mode": "live",
"pricingRequestId": "prq_9f8e77",
"product": { "externalRef": "garrafas-extras" },
"subscription": { "externalRef": "clube-4512" },
"customer": { "externalRef": "cliente-4512" },
"period": { "start": "2026-09-01", "end": "2027-02-28" },
"cycle": 2,
"currency": "BRL"
}

Os externalRef são os seus IDs — o cálculo do seu lado é um lookup no seu próprio banco. mode é "test" quando a chamada vem do botão de teste ou de uma simulação.

Em até 10 segundos, HTTP 200 com:

{ "amount": 18760, "description": "Garrafas extras — 4 un." }
  • amount: centavos, inteiro, sempre. Nada de float, nada de vírgula.
  • description (opcional): vira a linha da fatura que o seu assinante lê.
  • Para não cobrar o item neste ciclo: { "skip": true }. (Diferente de amount: 0, que é uma linha grátis na fatura.)

Toda chamada nossa leva X-Revtriever-Signature: t=<timestamp>,v1=<hmac>. Recalcule o HMAC-SHA256 sobre t + "." + corpo cru com o seu secret de integração e rejeite timestamps com mais de 5 minutos:

const crypto = require('crypto');
function verificar(corpoCru, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const esperado = crypto.createHmac('sha256', secret).update(`${t}.${corpoCru}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(v1));
}

Os dois erros clássicos que esse snippet evita:

  1. Verificar sobre o JSON re-serializado em vez dos bytes crus do corpo — re-serializar muda a ordem das chaves e a assinatura nunca bate. Use o corpo antes de qualquer parse.
  2. Comparar com === em vez de comparação constant-time.

O secret é um por conta (o mesmo dos webhooks) e vive em Cobrança → Integração, com rotação sem downtime: ao gerar um novo, o anterior continua aceito por 24 horas.

  • Perguntamos uma vez por ciclo. A resposta é congelada na fatura com request e response crus — retentativas e reemissões não chamam de novo. Seu endpoint não precisa ser idempotente.
  • Teto por cobrança. No cadastro do produto você define um valor máximo; resposta acima dele é tratada como falha. É a proteção contra um bug seu virar uma cobrança absurda no seu cliente.
  • Se o endpoint falhar, retentamos com intervalos crescentes por pelo menos 12 horas. Você recebe um aviso rápido (~15 min, com o erro cru) enquanto ainda retentamos; se esgotar, a fatura fica retida — atrasar é melhor que cobrar errado — e sai o alerta crítico. Destrave pelo painel ou pela API: POST /v1/invoices/{id}/retry-pricing ou informe o valor manualmente.
  • A fatura é gerada com folga antes do vencimento, então uma retenção curta é invisível para o seu assinante.

No cadastro do produto (ou via POST /v1/products/{id}/pricing-test) disparamos uma chamada real com mode: "test" e mostramos o resultado cru — status, latência e validação do formato. Ninguém descobre integração quebrada na primeira fatura de verdade.