Documentação da API · Reven Pay

API de pagamentos PIX na edge

Conecte o back-end da sua loja à Reven: gere cobranças PIX, receba o status por webhook assinado, concilie e dispare saques (PIX OUT) — tudo autenticado por X-Api-Key por workspace.

Base URL https://app.revenpayments.com.br/api Moeda reais (decimal) · sem centavos
~3spara confirmar um PIX
99.9%uptime na edge Cloudflare
HMACwebhooks e saques assinados
RESTJSON · envelope {success}

Visão geral #

Esta é a API do seller da Reven — a superfície pública que integradores externos consomem. Toda requisição é autenticada por X-Api-Key (uma chave por workspace) e todo corpo/resposta segue o envelope { success, data } / { success, error }.

R$

Todos os valores são em reais (decimal, ex.: 149.90) — no request (product.value, amount) e nas respostas/webhooks (amount, feeAmount, netAmount). Não existe centavo na API do seller.

!

A X-Api-Key e o Webhook Secret são segredos de servidor. Guarde-os em variável de ambiente — nunca no front-end, nunca no controle de versão, nunca num app mobile.

Endpoints

OperaçãoEndpointAuth
Criar cobrança PIXPOST /gateway/chargesX-Api-Key
Listar / conciliarGET /gateway/transactionsX-Api-Key
Receber statuswebhook na callbackUrlassinado (HMAC)
Sacar (PIX OUT)POST /gateway/withdrawalsX-Api-Key + assinatura dedicada
Consultar saqueGET /gateway/withdrawals/:idX-Api-Key

Credenciais & rate limit #

O admin gera uma API Key por workspace no painel. Ela vai no header de toda requisição:

http
X-Api-Key: sua_api_key_aqui
401 · sem/errada X-Api-Key 403 · workspace/company inativo 429 · flood de chave inválida

Barreira de flood (429)

Depois de 30 respostas 401 de credencial (header ausente ou chave inexistente) do mesmo IP em 5 minutos, as tentativas seguintes com chave ausente/inválida desse IP passam a receber 429 com Retry-After (segundos):

json · 429
{ "success": false, "error": "Too many requests. Please try again later." }

Uma chave válida nunca recebe 429 por esta barreira e nunca consome o limite — mesmo compartilhando IP de saída (NAT) com outra integração que erra a chave. Tomou 429 com chave que acredita ser válida? Ela foi revogada/rotacionada: corrija e aguarde o Retry-Afternão repita em loop.

Webhook Secret

Cada workspace tem também um Webhook Secret (no painel), usado para verificar a assinatura dos webhooks (§ Verificar assinatura) e assinar os saques (§ Criar saque).


Assinatura de requisição #

Distinta da assinatura de webhook. Quando requireSignature está ativo no seu workspace, além da X-Api-Key toda requisição que você envia precisa de assinatura HMAC — inclusive as de leitura.

i

Workspaces criados a partir de agosto/2026 já nascem com requireSignature ativo. Workspace mais antigo continua só com X-Api-Key até o dono pedir a mudança. Integrando um workspace novo? Implemente esta seção desde o começo.

http
X-Signature: <hmac_hex>
X-Timestamp: <epoch_ms>   # dentro de ±5 min
X-Nonce: <uuid_unico>     # nunca reutilizar

HMAC-SHA256 em hex, chave = sua API Key, mensagem canônica:

canônico
canonical = `${METHOD}\n${path}\n${body}\n${timestamp}\n${nonce}`
# body = "" em GET; path = pathname sem query (ex.: /api/gateway/charges)

Se o workspace não exige assinatura, omita X-Signature/X-Timestamp/X-Nonce e mantenha só a X-Api-Key. Exemplo completo em Node na § Criar cobrança.


Criar cobrança PIX #

POST/gateway/chargesX-Api-Key
curl
curl -X POST 'https://app.revenpayments.com.br/api/gateway/charges' \
  -H 'X-Api-Key: SUA_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "product":  { "name": "Plano Pro", "value": 149.90 },
    "customer": {
      "name": "João Silva",
      "email": "joao@exemplo.com",
      "doc": "11122233344",
      "docType": "cpf",
      "phone": "11912345678"
    },
    "expiracaoSegundos": 3600,
    "callbackUrl": "https://seusite.com/webhooks/pix",
    "metadata": { "pedido_id": "A-1001" }
  }'

Corpo do request

CampoTipoObrig.Observação
product.namestringsimNome do produto/serviço
product.valuenumber (reais)sim> 0 e finito. Ex.: 149.90
customer.namestringsim
customer.emailstring (email)sim
customer.docstringsimCPF/CNPJ (mín. 11 dígitos)
customer.docTypecpf|cnpjnãodefault cpf
customer.phone / zip / street / district / citystringnão
expiracaoSegundosnumbernãodefault 3600 — validade do QR
callbackUrlstring (url)nãoRecebe os webhooks desta cobrança
metadataobjectnãoEcoado nos webhooks — casa com seu pedido

Resposta 201

json · 201
{
  "success": true,
  "data": {
    "transactionId": "b3f1…",
    "pixCode": "00020126580014br.gov.bcb.pix…",
    "expiresAt": "2026-07-23T13:00:00.000Z",
    "amount": 149.90,
    "feeAmount": 5.25,
    "netAmount": 144.65
  }
}

pixCode é o copia-e-cola (EMV) — gere o QR a partir dele. Erros seguem { "success": false, "error": "<mensagem>" }.

Exemplo Node — com assinatura de requisição

typescript
import { createHmac, randomUUID } from 'node:crypto'

const API = 'https://app.revenpayments.com.br/api'
const API_KEY = process.env.CHECKOUT_API_KEY!

async function createCharge(body) {
  const path = '/api/gateway/charges'
  const raw = JSON.stringify(body)
  const ts = Date.now().toString()
  const nonce = randomUUID()
  const canonical = `POST\n${path}\n${raw}\n${ts}\n${nonce}`
  const signature = createHmac('sha256', API_KEY).update(canonical).digest('hex')

  const res = await fetch(`${API}/gateway/charges`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': API_KEY,
      'X-Signature': signature,   // omita se o workspace não exige assinatura
      'X-Timestamp': ts,
      'X-Nonce': nonce,
    },
    body: raw,
  })
  const json = await res.json()
  if (!json.success) throw new Error(json.error)
  return json.data
}

Rastreio de anúncio no metadata #

Chaves opcionais que a plataforma passa a ler e armazenar quando enviadas no metadata da cobrança. Como metadata sempre foi objeto livre, isto não altera o contrato — não mandar nenhuma = comportamento de hoje, byte a byte.

O envio server-side já existe. Ao pagar, a plataforma envia a conversão para os destinos que você configurar no painel (Integrações → Destinos de conversão): GA4 (Measurement Protocol), Meta (Conversions API) e TikTok (Events API). Nada é enviado sem credencial sua, e cada destino tem botão de teste.

Deduplicação: use o transactionId

Se você também dispara a compra pelo pixel do navegador, use o mesmo transactionId devolvido pelo POST /gateway/charges no campo de dedupe. Sem isso, cada venda é contada duas vezes.

DestinoEventoCampo de dedupe
GA4 (Measurement Protocol)purchasetransaction_id
Meta (Conversions API)Purchaseevent_id
TikTok (Events API)CompletePaymentevent_id

Identificadores de clique aceitos

Todos string. Envie só os que você tem; ausente e null equivalem. Só existem no momento do clique — se não capturar e enviar, não são recuperáveis depois.

ChaveOrigemComo obter
gclidGoogle Ads?gclid= da URL
gbraidGoogle Ads (app/iOS)?gbraid=
wbraidGoogle Ads (web/iOS)?wbraid=
fbclidMeta?fbclid=
fbpMetacookie _fbp (valor cru)
fbcMetacookie _fbc (valor cru)
ttclidTikTok?ttclid=
ttpTikTokcookie _ttp
gaClientIdGA4cookie _ga (client id)
clickAtvocêISO-8601 UTC da primeira captura

Aceitamos a forma plana ou aninhada em trackingParameters — as duas produzem o mesmo resultado:

json
{ "metadata": { "pedido_id": "A-1001", "gclid": "Cj0KCQ…", "fbp": "fb.1.1690000000000.1234567890" } }
{ "metadata": { "pedido_id": "A-1001", "trackingParameters": { "gclid": "Cj0KCQ…" } } }
×

fbp e fbc não podem ser hasheados nem inventados: mande o valor cru do cookie ou nada. E não coloque valor, moeda, produto ou dados do comprador nessas chaves — isso já vem em product/customer e a transação é a fonte da verdade.


Listar transações #

GET/gateway/transactionsX-Api-Key

Retorna as transações do seu workspace (paginado). Cada item traz status, amount, feeAmount, netAmount, endToEndId, paidAt, metadata. Use para reconciliação/polling se não puder receber webhook.

curl
curl 'https://app.revenpayments.com.br/api/gateway/transactions?page=1&limit=20' \
  -H 'X-Api-Key: SUA_API_KEY'
i

Janela padrão de 30 dias. Sem from, a listagem cobre só os últimos 30 dias — e meta.total/meta.pages contam o mesmo recorte. Para período mais antigo, mande from/to (ISO-8601, opcionais).

Query paramDefaultRegra
page1inválido → cai no default
limit20máx. 100; inválido → default
fromcreated_at >= from; data ilegível = ausente
tocreated_at < to (fechado-aberto)

Compare status exatamente em MAIÚSCULO (ex.: status === 'PAID'). Ver § Status & erros.


Receber o status por webhook #

Se você enviou callbackUrl, o gateway faz POST nessa URL a cada mudança de status. Headers: Content-Type: application/json, User-Agent: Reven-Webhook-Dispatcher/1.0 e X-Signature. Acompanham X-Webhook-Id, X-Timestamp e X-Signature-V2 (novos e opcionais — § V2).

json · payload
{
  "event": "transaction.paid",
  "data": {
    "transactionId": "b3f1…",
    "acquirerTxId": "abc123",
    "endToEndId": "E1234567890202607231240abcdef123",
    "status": "PAID",
    "amount": 149.90,
    "feeAmount": 5.25,
    "netAmount": 144.65,
    "productName": "Plano Pro",
    "customer": { "name": "João Silva", "email": "joao@exemplo.com" },
    "metadata": { "pedido_id": "A-1001" },
    "paidAt": "2026-07-23T12:40:00.000Z",
    "createdAt": "2026-07-23T12:00:00.000Z"
  }
}
CampoValores / formato
eventtransaction.created · transaction.paid · transaction.failed · transaction.expired
data.statusMAIÚSCULOPENDING/PAID/FAILED/EXPIRED/MED
data.endToEndIdE2E do PIX no Bacen (E+31). Pode vir null antes do pagamento

Verificar a assinatura (X-Signature) #

O X-Signature é o HMAC-SHA256 do corpo bruto (o JSON exatamente como recebido, sem re-serializar) com o Webhook Secret do workspace. Recompute e compare antes de confiar no evento:

typescript
import { createHmac, timingSafeEqual } from 'node:crypto'

const WEBHOOK_SECRET = process.env.CHECKOUT_WEBHOOK_SECRET! // do painel, por workspace

function verify(rawBody, signatureHeader) {
  const expected = createHmac('sha256', WEBHOOK_SECRET).update(rawBody).digest('hex')
  const a = Buffer.from(expected), b = Buffer.from(signatureHeader || '')
  return a.length === b.length && timingSafeEqual(a, b)
}

// No seu endpoint: leia o corpo CRU (string) antes de parsear.
// if (!verify(rawBody, req.headers['x-signature'])) return res.status(401).end()

Boas práticas: verifique o X-Signature sobre o corpo bruto antes de processar · responda 200 rápido e processe assíncrono · seja idempotente por transactionId · só libere o pedido em event === 'transaction.paid' (e data.status === 'PAID') · case pelo seu metadata.


Assinatura V2 do webhook (anti-replay) #

O X-Signature cobre só o corpo — e, como nada nele envelhece, uma entrega interceptada tem assinatura válida para sempre. Todo webhook passa a trazer também três headers novos. É recomendado, não obrigatório: quem só valida o X-Signature segue suportado, sem prazo de corte.

http + canônico
X-Webhook-Id:   <uuid da entrega>   # igual em todas as retentativas
X-Timestamp:    <epoch_ms>          # instante da assinatura
X-Signature-V2: <hmac_hex>

canonical = `${X-Timestamp}\n${X-Webhook-Id}\n${corpo bruto}`
typescript
function verifyV2(rawBody, h) {
  const ts = Number(h['x-timestamp'])
  if (!Number.isFinite(ts) || Math.abs(Date.now() - ts) > 5 * 60_000) return false // fora da janela
  const canonical = `${h['x-timestamp']}\n${h['x-webhook-id']}\n${rawBody}`
  const expected = createHmac('sha256', WEBHOOK_SECRET).update(canonical).digest('hex')
  const a = Buffer.from(expected), b = Buffer.from(h['x-signature-v2'] || '')
  return a.length === b.length && timingSafeEqual(a, b)
}

Rejeite X-Timestamp fora de ±5 min e guarde o X-Webhook-Id já processado (10 min bastam) para descartar repetição. Retentativa automática reusa o mesmo X-Webhook-Id; reenvio manual pedido ao suporte vem com id novo e deve ser processado.


Criar saque — cashout / PIX OUT #

POST/gateway/withdrawalsX-Api-Key + assinatura dedicada

Dispara um saque programático do seu saldo disponível para uma chave PIX. Diferente das cobranças, tem verificação de assinatura dedicada e sempre obrigatória — a X-Api-Key sozinha nunca saca.

  1. X-Api-Key do workspace identifica você (middleware).
  2. Allowlist de IP (opt-in) — IP fora da lista recebe 403. Sem cadastro, sem restrição.
  3. Assinatura HMAC com o Webhook Secret (NÃO a api key) — assim uma api key vazada sozinha não saca. Headers X-Signature/X-Timestamp/X-Nonce.
  4. Idempotency-Key (header obrigatório) — dedup atômico. Reenviar a MESMA key devolve o MESMO saque (200), sem debitar de novo.

Corpo do request

json
{ "amount": 100.00, "callbackUrl": "https://sua-loja.com/wh/saque", "pixKey": "loja@exemplo.com", "pixKeyType": "email" }
CampoObrig.Regra
amountsim> 0, reais. Debitado do saldo. Pode haver mínimo configurado (default: sem mínimo)
callbackUrlnãorecebe os webhooks withdrawal.*
pixKey + pixKeyTypenãoambos ou nenhum. Destino livre (rotação por saque). Omitindo, usa a chave do painel

pixKeyType canônico ∈ cpf | cnpj | email | phone | random (case-insensitive). Aliases normalizados:

Você enviaVira
evp, aleatoria, aleatória, chave_aleatoriarandom
telefone, celularphone
e-mailemail

Assinatura do saque

Igual à assinatura de requisição, mas a chave é o Webhook Secret e o Idempotency-Key entra no canônico:

canônico
canonical = `${METHOD}\n${path}\n${body}\n${timestamp}\n${nonce}\n${idempotencyKey}`
X-Signature = HMAC-SHA256(chave = webhook_secret, mensagem = canonical)  # hex
typescript
import { createHmac, randomUUID } from 'node:crypto'

const API = 'https://app.revenpayments.com.br/api'
const API_KEY = process.env.CHECKOUT_API_KEY!
const WEBHOOK_SECRET = process.env.CHECKOUT_WEBHOOK_SECRET! // o mesmo do painel

async function withdraw(amount, dest) {
  const path = '/api/gateway/withdrawals'
  const raw = JSON.stringify({ amount, ...dest })
  const ts = Date.now().toString()
  const nonce = randomUUID()
  const idempotencyKey = randomUUID() // reusar no retry do MESMO saque
  const canonical = `POST\n${path}\n${raw}\n${ts}\n${nonce}\n${idempotencyKey}`
  const signature = createHmac('sha256', WEBHOOK_SECRET).update(canonical).digest('hex')

  const res = await fetch(`${API}/gateway/withdrawals`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': API_KEY,
      'X-Signature': signature,
      'X-Timestamp': ts,
      'X-Nonce': nonce,
      'Idempotency-Key': idempotencyKey,
    },
    body: raw,
  })
  const json = await res.json()
  if (!json.success) throw new Error(json.error)
  return json.data // { withdrawalId, status, amount, feeAmount, netAmount } — reais
}

Taxa de saque (feeAmount / netAmount)

A plataforma pode cobrar taxa por saque, descontada do valor sacado. Campos aditivos e opcionais: com taxa zero (default) valem 0 e netAmount == amount — resposta idêntica à de sempre.

CampoO que é
amounto valor que você pediu — e o que debita do saldo
feeAmounta taxa cobrada neste saque, em reais
netAmounto que cai na chave PIX: amount − feeAmount
!

Taxa valor pedido → saque recusado com 400 + { "success": false, "error": "Valor do saque não cobre a taxa de saque" }, nada debitado. O admin pode bloquear saques da conta → 403 "Saques bloqueados por determinação administrativa."

Webhooks do saque (se enviar callbackUrl): withdrawal.created, withdrawal.paid, withdrawal.failed — assinados no X-Signature com o mesmo Webhook Secret e com os headers V2. A pixKey vem mascarada no payload.


Consultar um saque #

GET/gateway/withdrawals/:idX-Api-Key

Retorna o saque, desde que pertença à company da sua X-Api-Key (isolamento por workspace).

json · 200
{
  "success": true,
  "data": {
    "withdrawalId": "…",
    "status": "ENVIADO",
    "amount": 100.00,
    "pixKeyType": "email",
    "createdAt": "2026-07-23T12:00:00.000Z"
  }
}
PENDINGENVIADORECUSADO CANCELADOBLOQUEADO
i

Se requireSignature estiver ativo, esta leitura também exige a assinatura de requisição (§ Assinatura), assinada com a API Key.


Allowlist de IP (opcional) #

Você pode restringir as rotas de saque (POST /gateway/withdrawals e GET /gateway/withdrawals/:id) a um conjunto de IPs de saída. IP fora da lista responde 403. Sem cadastro, nenhuma restrição.

i

O cadastro é feito no seu painel (segurança do workspace), com a sua sessão do painel — não faz parte da superfície X-Api-Key//gateway/*. Alterar a lista exige confirmar a senha atual (step-up); sem ela, 401 STEP_UP_REQUIRED.


Status & erros #

Status de transação (sempre MAIÚSCULO)

StatusSignifica
PENDINGaguardando pagamento
PAIDpago e confirmado — único gatilho para liberar o pedido
FAILEDfalhou
EXPIREDPIX venceu (pixExpiresAt) — reportado mesmo antes do aviso do adquirente; deixa de contar no saldo pendente
MEDcontestado/chargeback (foi pago e revertido) — trate como não-pago

Status de saque

StatusSignifica
PENDINGcriado, aguardando processamento
ENVIADOPIX enviado
RECUSADOrecusado pelo liquidante
CANCELADOcancelado
BLOQUEADOretido/bloqueado

Códigos HTTP

CódigoQuando
401X-Api-Key ausente/inválida; assinatura ausente/errada; timestamp fora da janela; nonce reusado
403workspace/company inativo; IP fora da allowlist; saques bloqueados pelo admin
400body inválido; saldo insuficiente; abaixo do mínimo; taxa ≥ valor; falta destino/idempotency-key
429flood de credencial inválida por IP — respeite o Retry-After
503verificação de nonce indisponível em produção (raro)

Todo erro segue o envelope { "success": false, "error": "<mensagem>" }.


Checklist de integração #

  • X-Api-Key e Webhook Secret guardados como segredo (env), nunca no cliente.
  • POST /gateway/charges gerando pixCode e exibindo o QR/copia-e-cola.
  • Valores tratados em reais (não centavos) no request e nas respostas/webhooks.
  • Endpoint de callbackUrl verifica o X-Signature (HMAC do corpo bruto), responde 200 e é idempotente por transactionId.
  • (Recomendado) X-Signature-V2 verificado, X-Timestamp dentro de ±5 min e X-Webhook-Id deduplicado.
  • status comparado em MAIÚSCULO (PAID etc.); pedido liberado só em transaction.paid.
  • metadata casa o pagamento com o pedido interno; endToEndId guardado para conciliação bancária.
  • (Se exigido) assinatura HMAC de requisição implementada e testada na janela de ±5 min.
  • Saque: assinatura com o Webhook Secret (não a api key) incluindo o Idempotency-Key no canônico; key reusada no retry; (opcional) IPs de saída na allowlist.