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.
Valores sempre em centavos (inteiro): 4990 = R$ 49,90. Datas em ISO 8601. Corpo e resposta em JSON.
# Endereço base
{{BASE}}/v1Autenticação
Mande a chave secreta no cabeçalho Authorization. Crie as chaves no painel, em Chaves e webhook.
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.curl {{BASE}}/v1/gateways \
-H "Authorization: Bearer sk_test_..."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"
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.
{ "valor": 4990, "metodo": "pix", "gateway": "auto" }
{ "valor": 4990, "metodo": "pix", "gateway": "woovi" }Criar cobrança
Cria uma cobrança de Pix, boleto ou cartão.
valorinteiro, obrigatórioEm centavos, mínimo 100metodopix | boleto | cartaoForma de pagamentogatewaytexto, padrão autoauto ou o id do gatewaydescricaotextoAparece pro comprador quando o gateway mostraclienteobjetonome, 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_idpix_expira_em_minutosinteiro, padrão 60De 15 a 10080boleto_vence_em_diasinteiro, padrão 3De 1 a 60metadataobjetoSeus 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"}}'{
"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 }
]
}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_..."Listar cobranças
limite1 a 100, padrão 20statusopcionalFiltra por statusantes_deidPra paginar: passe o id da última cobrança recebida{ "dados": [ … ], "tem_mais": true }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, … }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…).
cobranca.pendenteCriada, esperando o pagamentocobranca.pagaPagamento confirmado: libere o pedido aquicobranca.falhouRecusada ou nenhum gateway conseguiu processarcobranca.expiradaPix ou boleto venceu sem pagamentocobranca.estornadaEstorno total ou parcialConfira 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);{
"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" }
}
}Erros
Todo erro vem no mesmo formato. Quando o gateway recusou, mandamos também o código original dele em original.
dados_invalidos422Algum campo errado; veja campocartao_recusado · saldo_insuficiente · risco_alto402Recusa do emissorgateway_indisponivel503Nenhum gateway da regra respondeu (o fallback já foi tentado)gateway_nao_conectado400Conecte o gateway no painel, no ambiente certoassinatura_inativa402Chave de produção com a assinatura pendentelimite_plano429Limite de transações do mês atingidonao_autorizado401Chave ausente, inválida ou revogadalimite_requisicoes429Mais de 300 requisições por minutoHTTP 402
{
"erro": {
"codigo": "saldo_insuficiente",
"mensagem": "Pagamento recusado pelo emissor",
"cobranca_id": "ch_…",
"gateway": "mercadopago",
"original": { "codigo": "cc_rejected_insufficient_amount" }
}
}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.
| Gateway | id | Métodos |
|---|---|---|
| Mercado Pago | mercadopago | Pix, boleto, cartão |
| Asaas | asaas | Pix, boleto |
| Woovi (OpenPix) | woovi | Pix |
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" }
]