TrendPayHubDocs
StatusPainel
Começando

Uma API pra todos os gateways

Você conecta seus gateways no painel e integra só com a gente. Toda cobrança, resposta, erro e webhook tem o mesmo formato, não importa se passou pelo Mercado Pago, Asaas ou Woovi.

O dinheiro nunca passa pelo TrendPayHub: cada pagamento cai direto na sua conta do gateway que processou.

Valores sempre em centavos (inteiro): 4990 = R$ 49,90. Datas em ISO 8601. Corpo e resposta em JSON.

# Endereço base
{{BASE}}/v1
Começando

Autenticação

Mande a chave secreta no cabeçalho Authorization. Crie as chaves no painel, em Chaves e webhook.

Cabeçalhos
AuthorizationobrigatórioBearer sk_live_… (produção) ou Bearer sk_test_… (sandbox)
Idempotency-KeyrecomendadoUm id seu por cobrança (ex.: o número do pedido). Repetir a chamada com a mesma chave devolve a mesma cobrança e nunca cobra duas vezes.
A chave secreta é só do seu servidor. Nunca coloque no navegador, no app ou no código público.
curl {{BASE}}/v1/gateways \
  -H "Authorization: Bearer sk_test_..."
Começando

Sandbox

Com uma chave sk_test_, as cobranças vão pro ambiente de teste de cada gateway, usando as conexões que você cadastrou como Sandbox no painel. Nada é cobrado de verdade.

No painel, a tela Testar cobrança gera um Pix ou boleto de teste e mostra a resposta exata da API.

A chave de produção funciona com a assinatura ativa. A de sandbox funciona sempre.

sk_test_…  → conexões "Sandbox"
sk_live_…  → conexões "Produção"
Começando

Escolher o gateway

Três jeitos, e dá pra misturar:

Automático – mande "gateway": "auto" (ou não mande). Valem as regras de Roteamento do painel: o gateway principal de cada método e o fallback. O dono da loja muda as regras sem mexer no código.

Fixo – mande o id do gateway, ex.: "gateway": "mercadopago". Só ele é tentado.

O comprador escolhe – mostre botões na sua tela (Mercado Pago, Woovi…) e mande o id do que ele clicou.

Fallback: no Pix e no boleto, se o gateway principal não responder, tentamos o fallback na hora. No cartão não existe fallback: o token do cartão é gerado pelo gateway e só vale nele.
{ "valor": 4990, "metodo": "pix", "gateway": "auto" }

{ "valor": 4990, "metodo": "pix", "gateway": "woovi" }
POST /v1/cobrancas

Criar cobrança

Cria uma cobrança de Pix, boleto ou cartão.

Corpo
valorinteiro, obrigatórioEm centavos, mínimo 100
metodopix | boleto | cartaoForma de pagamento
gatewaytexto, padrão autoauto ou o id do gateway
descricaotextoAparece pro comprador quando o gateway mostra
clienteobjetonome, email, documento (CPF/CNPJ), telefone e, pra boleto no Mercado Pago, endereco { cep, rua, numero, bairro, cidade, uf }. Asaas exige nome e documento; Mercado Pago exige e-mail.
cartaoobjeto, só cartãotoken (gerado no navegador pelo MercadoPago.js com a sua chave pública), bandeira (ex.: visa), parcelas, emissor_id
pix_expira_em_minutosinteiro, padrão 60De 15 a 10080
boleto_vence_em_diasinteiro, padrão 3De 1 a 60
metadataobjetoSeus dados (ex.: id do pedido). Volta igual na consulta e no webhook. Até 4 KB.

Resposta 201 com a cobrança. Em tentativas aparece cada gateway tentado (útil pra ver quando o fallback entrou).

const r = await fetch("{{BASE}}/v1/cobrancas", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.TPH_CHAVE,
    "Content-Type": "application/json",
    "Idempotency-Key": "pedido-1042"
  },
  body: JSON.stringify({
    valor: 4990,
    metodo: "pix",
    gateway: "auto",
    cliente: { nome: "Ana Souza", documento: "52998224725", email: "ana@email.com" },
    metadata: { pedido: "1042" }
  })
});
const cobranca = await r.json();
// cobranca.pix.copia_e_cola → mostrar pro comprador
$ch = curl_init("{{BASE}}/v1/cobrancas");
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . getenv("TPH_CHAVE"),
    "Content-Type: application/json",
    "Idempotency-Key: pedido-1042",
  ],
  CURLOPT_POSTFIELDS => json_encode([
    "valor" => 4990, "metodo" => "pix", "gateway" => "auto",
    "cliente" => ["nome" => "Ana Souza", "documento" => "52998224725"],
    "metadata" => ["pedido" => "1042"],
  ]),
]);
$cobranca = json_decode(curl_exec($ch), true);
curl {{BASE}}/v1/cobrancas \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-1042" \
  -d '{"valor":4990,"metodo":"pix","gateway":"auto",
       "cliente":{"nome":"Ana Souza","documento":"52998224725"}}'
Resposta201 Created
{
  "id": "ch_9KdP2xQm4Lw8Tz1B",
  "status": "pendente",
  "valor": 4990,
  "metodo": "pix",
  "gateway": "asaas",
  "gateway_pedido": "auto",
  "ambiente": "producao",
  "estornado": 0,
  "pix": {
    "copia_e_cola": "00020126...",
    "qr_code_base64": "iVBORw0KGgo...",
    "expira_em": "2026-11-09T21:30:00-03:00"
  },
  "metadata": { "pedido": "1042" },
  "criada_em": "2026-11-09T20:30:00.000Z",
  "paga_em": null,
  "tentativas": [
    { "gateway": "asaas", "resultado": "ok", "latencia_ms": 412 }
  ]
}
GET /v1/cobrancas/:id

Consultar cobrança

Devolve o estado atual e as tentativas. Status possíveis: pendente, paga, falhou, expirada, cancelada, estornada, estornada_parcial.

Não precisa ficar consultando: o webhook avisa quando muda. Mesmo se um webhook do gateway se perder, a gente reconsulta as pendentes sozinho.

curl {{BASE}}/v1/cobrancas/ch_9KdP2xQm4Lw8Tz1B \
  -H "Authorization: Bearer sk_live_..."
GET /v1/cobrancas

Listar cobranças

Parâmetros (query)
limite1 a 100, padrão 20
statusopcionalFiltra por status
antes_deidPra paginar: passe o id da última cobrança recebida
{ "dados": [ … ], "tem_mais": true }
POST /v1/cobrancas/:id/estorno

Estornar

Estorno total (sem valor) ou parcial (valor em centavos). A gente chama o gateway que processou a cobrança, você não precisa saber qual foi.

curl -X POST {{BASE}}/v1/cobrancas/ch_9KdP2x.../estorno \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"valor":1000}'

→ { "status": "estornada_parcial", "estornado": 1000, … }
Eventos

Webhooks

Cadastre uma URL no painel. Enviamos um POST com JSON a cada mudança. Responda 2xx em até 10 segundos; se falhar, tentamos de novo por até 3 dias (1 min, 5 min, 15 min, 1 h…).

Eventos
cobranca.pendenteCriada, esperando o pagamento
cobranca.pagaPagamento confirmado: libere o pedido aqui
cobranca.falhouRecusada ou nenhum gateway conseguiu processar
cobranca.expiradaPix ou boleto venceu sem pagamento
cobranca.estornadaEstorno total ou parcial

Confira a assinatura. O cabeçalho X-Assinatura vem como t=<unix>,v1=<hmac>, onde hmac = HMAC-SHA256(segredo, t + "." + corpo_bruto). O segredo está no painel. Recuse se a diferença de tempo passar de 5 minutos.

O mesmo evento pode chegar mais de uma vez: use o id do evento (cabeçalho X-Evento-Id) pra não processar duas vezes.

import crypto from "node:crypto";

// Use o corpo BRUTO (texto), antes do JSON.parse
app.post("/webhooks/pagamentos", express.text({ type: "*/*" }), (req, res) => {
  const partes = Object.fromEntries(req.get("X-Assinatura").split(",").map(p => p.split("=")));
  const esperado = crypto.createHmac("sha256", process.env.TPH_WEBHOOK_SEGREDO)
    .update(partes.t + "." + req.body).digest("hex");
  const recente = Math.abs(Date.now() / 1000 - Number(partes.t)) < 300;
  if (!recente || !crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(partes.v1))) {
    return res.sendStatus(401);
  }
  const evento = JSON.parse(req.body);
  if (evento.tipo === "cobranca.paga") liberarPedido(evento.dados.metadata.pedido);
  res.sendStatus(200);
});
$corpo = file_get_contents("php://input");
parse_str(str_replace(",", "&", $_SERVER["HTTP_X_ASSINATURA"]), $p);
$esperado = hash_hmac("sha256", $p["t"] . "." . $corpo, getenv("TPH_WEBHOOK_SEGREDO"));
if (abs(time() - (int)$p["t"]) > 300 || !hash_equals($esperado, $p["v1"])) {
  http_response_code(401); exit;
}
$evento = json_decode($corpo, true);
if ($evento["tipo"] === "cobranca.paga") liberarPedido($evento["dados"]["metadata"]["pedido"]);
http_response_code(200);
Corpo enviadoPOST
{
  "id": "ev_7Hq2Lm9TzR4vW8nB",
  "tipo": "cobranca.paga",
  "criado_em": "2026-11-09T20:31:02.000Z",
  "dados": {
    "id": "ch_9KdP2xQm4Lw8Tz1B",
    "status": "paga",
    "valor": 4990,
    "gateway": "asaas",
    "metadata": { "pedido": "1042" }
  }
}
Referência

Erros

Todo erro vem no mesmo formato. Quando o gateway recusou, mandamos também o código original dele em original.

Códigos
dados_invalidos422Algum campo errado; veja campo
cartao_recusado · saldo_insuficiente · risco_alto402Recusa do emissor
gateway_indisponivel503Nenhum gateway da regra respondeu (o fallback já foi tentado)
gateway_nao_conectado400Conecte o gateway no painel, no ambiente certo
assinatura_inativa402Chave de produção com a assinatura pendente
limite_plano429Limite de transações do mês atingido
nao_autorizado401Chave ausente, inválida ou revogada
limite_requisicoes429Mais de 300 requisições por minuto
HTTP 402
{
  "erro": {
    "codigo": "saldo_insuficiente",
    "mensagem": "Pagamento recusado pelo emissor",
    "cobranca_id": "ch_…",
    "gateway": "mercadopago",
    "original": { "codigo": "cc_rejected_insufficient_amount" }
  }
}
GET /v1/gateways

Gateways suportados

Use o id pra forçar um gateway. Gateways novos entram sem mudar sua integração. A rota devolve só os conectados na sua conta, com a saúde de agora.

GatewayidMétodos
Mercado PagomercadopagoPix, boleto, cartão
AsaasasaasPix, boleto
Woovi (OpenPix)wooviPix

Próximos: PagBank, Stripe, Pagar.me, Cielo, Rede e outros.

[
  { "id": "mercadopago", "nome": "Mercado Pago", "metodos": ["pix","boleto","cartao"], "saude": "operacional" },
  { "id": "asaas", "nome": "Asaas", "metodos": ["pix","boleto"], "saude": "operacional" }
]