Pix e Cripto em produção em minutos
REST + JSON, snake_case, webhooks assinados por HMAC-SHA256 com retentativas automáticas e IDs prefixados (chg_, pyt_, bol_). Feito para plugar em qualquer stack, incluindo agentes de IA (ChatGPT, Claude, Cursor, n8n, Zapier).
Começar
Quickstart em 3 passos
- 1
Gere sua API key
Vá em API Keys e clique em Criar chave. Ela já vem com acesso completo — Pix in/out, Cripto e Saldo. Sem seleção de escopo, sem consent screen. - 2
Faça sua primeira cobrança
cURLcurl -X POST https://api.phanterpay.com.br/v1/charges \ -H "Authorization: Bearer bp_XXXX_xxx..." \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 3d9d1f9e-9c9d-4d3e-b1a1-1234" \ -d '{ "amount": 49.90, "description": "Pedido #1042", "payer_name": "João da Silva", "payer_document": "12345678909", "expires_in": 3600, "external_id": "pedido_1042" }' - 3
Receba a confirmação por webhook
Cadastre um endpoint em API & Integrações, assine o eventocharge.paide valide a assinatura HMAC. Se preferir polling, useGET /charges/:id.
Para IAs
Integrar com agentes (ChatGPT, Claude, Cursor…)
Cole o bloco abaixo no seu agente. Ele tem tudo o que a IA precisa para gerar código sem alucinar endpoint ou payload.
Você é um agente que integra pagamentos via API PhanterPay v1.
Base URL: https://api.phanterpay.com.br/v1
Autenticação: header "Authorization: Bearer <API_KEY>" (também aceita "x-api-key: <API_KEY>").
Formato da chave: começa com "bp_" (ex.: bp_a1b2_c3d4...48hex). NÃO existem chaves "pk_live_" ou "sk_live_".
Padrão: REST + JSON, snake_case em todos os campos (request e response). Valores em BRL com decimais.
IDs de resposta são prefixados: chg_ (cobrança), pyt_ (payout), evt_ (webhook event).
Envie sempre "Idempotency-Key: <uuid-v4>" em requisições POST.
Envie "external_id" (string opcional até 120 chars) para deduplicar por sua chave de negócio.
Rate limit: aplicado por endpoint e por credencial. Se estourar, HTTP 429 com header Retry-After (segundos). Respeite Retry-After e use backoff exponencial. Prefira webhooks a polling.
Modo de teste: valores <= R$ 5,00 marcam "test_mode": true no response e no payload do webhook. É apenas um sinal — o dinheiro se move normalmente.
Paginação: GET /charges e GET /payouts aceitam ?limit=N (max 200) e ?starting_after=<id>. Response inclui "has_more" e "next_starting_after".
Webhooks: entregues a partir da borda global (Cloudflare Workers) — os IPs de origem NÃO são fixos. A validação canônica é a assinatura HMAC, nunca allowlist de IP.
Endpoints:
POST /charges -> cria cobrança Pix. Body: { amount, description?, payer_name?, payer_document?, expires_in?, external_id? }
GET /charges/:id -> consulta cobrança (aceita chg_..., txid legado, ou "ext:<external_id>")
GET /charges -> lista cobranças (query: limit, starting_after)
POST /payouts -> envia Pix. Body: { amount, pix_key, pix_key_type?, recipient_name?, recipient_document?, description?, external_id? }
IMPORTANTE: "amount" = valor LÍQUIDO que o favorecido recebe. A taxa é somada por cima
e o total (gross_amount = amount + fee_amount) é debitado do seu saldo. Ex.: amount=100,
fee=R$ 3,50 → precisa ter R$ 103,50 de saldo, favorecido recebe R$ 100,00.
Response inclui gross_amount, net_amount e fee_amount para auditoria.
GET /payouts/:id -> consulta payout (aceita pyt_..., uuid, ou "ext:<external_id>")
GET /payouts -> lista saques (query: limit, starting_after)
GET /balance -> saldos { available_balance, pending_balance, blocked_balance, total_balance, currency }
GET /med -> lista infrações PIX (MED). Query: status?, limit?, starting_after?
GET /med/:id -> consulta infração
POST /med/:id -> envia defesa. Body: { defense_text, defense_evidence_url? }
Regras:
- Nunca invente uma API key. Use variável de ambiente PHANTERPAY_KEY.
- Idempotency-Key (UUID v4) é obrigatória apenas em POST /charges e POST /payouts. Endpoints de configuração (med/:id) NÃO usam Idempotency-Key.
- Split de pagamentos: recurso previsto para versões futuras. Não existe endpoint disponível hoje.
- Erros: sempre { "success": false, "error": { "code", "message", "retryable", "issues"? }, "request_id" }.
Trate pelo STATUS HTTP + error.code:
400 VALIDATION_ERROR · 401 UNAUTHORIZED · 402 INSUFFICIENT_BALANCE · 403 FORBIDDEN
· 404 NOT_FOUND · 409 IDEMPOTENCY_CONFLICT · 422 AMOUNT_LIMIT_EXCEEDED · 423 ACCOUNT_LOCKED
· 429 RATE_LIMIT_EXCEEDED (respeite Retry-After) · 502 PROVIDER_UNAVAILABLE
Toda resposta (sucesso ou erro) inclui header X-Request-Id — use no suporte para rastrear a chamada.
- Status charge: pending → paid | expired | refunded
- Status payout: pending → processing → completed | failed | reversed
IMPORTANTE: payout NUNCA volta "completed" na resposta síncrona do POST — sempre "processing" ou "failed".
Confirmação final chega apenas via webhook payout.completed OU polling em GET /payouts/:id.
- Para receber notificações, cadastre um webhook em /api-keys (assinado com HMAC-SHA256 do material "<t>.<raw_body>", header X-PhanterPay-Signature: t=<unix>,v1=<hex>).
Tipos: charge.created, charge.paid, charge.expired, charge.refunded, charge.failed,
payout.created, payout.processing, payout.completed, payout.failed, payout.reversed,
med.opened, med.updated, med.resolved
- Métodos suportados hoje: Pix e cripto. CARTÃO NÃO EXISTE na API — não gere código para /card_payments.
- Compatibilidade (v1 estável): campos e endpoints nunca são removidos/renomeados; novos campos são opcionais;
novos métodos de pagamento entram como endpoints novos (nunca alteram /charges); ignore chaves e tipos de
evento desconhecidos com um default neutro para nunca quebrar quando algo novo for lançado.
- Referência oficial: https://phanterpay.com.br/docs/apiBase URL & ambientes
https://api.phanterpay.com.br/v1Endpoint canônico da API v1. Todos os endpoints são HTTPS-only. Não há sandbox separado — use valores baixos (ex.: R$ 1,00) na sua chave para testar em produção.
Convenções da API
snake_case em todos os campos
Requests e responses usam snake_case. Se você tem código legado em camelCase(payerName, pixKeyDest) ele continua sendo aceito na entrada — a documentação e as respostas usam apenas a forma nova.
IDs prefixados
chg_— cobrança Pix (charges)pyt_— payout / cash-out Pixevt_— evento de webhook
Você pode consultar qualquer recurso por: o id novo (chg_…, pyt_…), o id interno legado (txid ou uuid) ou o prefixo ext: seguido do seu external_id. Exemplo: GET /charges/ext:pedido_1042.
Datas em ISO-8601 (UTC)
Todos os timestamps são strings ISO-8601 com sufixo Z. Ex.: 2026-07-27T20:32:14Z.
Autenticação
API Key
Envie a chave em todas as requisições no header. Formato aceito: bp_XXXX_... (48 hex após o segundo underscore). Não existem chaves pk_live_ ou sk_live_.
Authorization: Bearer bp_XXXX_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxAlternativas aceitas: Authorization: bp_XXXX_... (sem "Bearer") ou x-api-key: bp_XXXX_....
O que a chave libera
Chave única, todos os recursos liberados — sem seleção de escopo pelo cliente:
- POST /charges · GET /charges/:id · GET /charges
- POST /payouts · GET /payouts/:id · GET /payouts
- GET /balance
Idempotency-Key
Envie um UUID único em Idempotency-Key em requisições POST de criação financeira — /charges e /payouts. Requisições repetidas com a mesma chave e o mesmo body retornam a resposta original — mesmo em caso de retry por timeout. Se você reutilizar a chave com um body diferente, recebe 409.
Endpoints de configuração (defesa MED) não usam Idempotency-Key — são operações naturalmente idempotentes por recurso.
Idempotency-Key: 3d9d1f9e-9c9d-4d3e-b1a1-1234567890abexternal_id (dedupe semântico)
Diferente da Idempotency-Key (que dedupe por HTTP retry), o external_iddedupe pela sua chave de negócio: o número do pedido, id da fatura, hash do carrinho, o que fizer sentido.
- String de até 120 caracteres.
- Se você tentar criar uma cobrança/payout com um
external_idque já existe pra sua conta, devolvemos o registro anterior com200. - Aparece em toda resposta, em todo webhook e você pode consultar por ele (
GET /charges/ext:pedido_1042). - É a forma recomendada de casar eventos com sua base de dados.
Segurança
Rate limiting
A PhanterPay aplica limites de requisição por endpoint e credencial para garantir estabilidade, segurança e disponibilidade da plataforma. Os limites podem variar de acordo com o endpoint, perfil da operação e capacidade contratada.
Operações com alto volume ou necessidades específicas de throughput podem solicitar capacidade dedicada junto ao time PhanterPay.
Resposta ao atingir o limite
Quando um limite é atingido, a API responde com HTTP 429 Too Many Requests e inclui o header Retry-After, informando quantos segundos aguardar antes de nova tentativa.
Retry-AfterheaderSegundos para aguardar antes de repetir a chamada — presente apenas em 429.
Exemplo de resposta 429
{
"error": "Rate limit exceeded"
}Retry-After: 10Boas práticas
- Retry com backoff exponencial. Em
429, respeiteRetry-Afterantes de retentar. Para502/503, use backoff exponencial (1s → 2s → 4s → 8s).node.jsasync function withRetry(fn, maxRetries = 4) { for (let attempt = 0; attempt < maxRetries; attempt++) { const res = await fn(); if (res.status === 429) { const wait = Number(res.headers.get("retry-after") ?? 2 ** attempt); await new Promise(r => setTimeout(r, wait * 1000)); continue; } if (res.status === 502 || res.status === 503) { await new Promise(r => setTimeout(r, (2 ** attempt) * 1000)); continue; } return res; } throw new Error("max_retries exceeded"); } - Webhook em vez de polling. Polling em
GET /chargesdesperdiça quota. Cadastre um endpoint em API & Integrações e reaja aos eventoscharge.paid/payout.completed. Reconcilie via listagem só periodicamente (1× por hora). - Idempotência. Use o header
Idempotency-Keyem toda criação de cobrança ou saque. Retentativas com a mesma chave devolvem a transação original — sem cobrar quota extra nem duplicar operação. - Cache de leituras estáveis. Saldo, taxas e limites mudam com baixa frequência — cacheie por 5–30s no seu lado para não consumir quota à toa.
Respostas relacionadas
| HTTP | Cenário | Ação |
|---|---|---|
| 429 | Limite de requisições atingido | Espere Retry-After segundos e retente |
| 403 | IP bloqueado temporariamente (anti-abuso) | Espere 5 minutos — não retente imediatamente |
| 403 | IP em blacklist permanente | Contate o suporte |
| 423 | Conta sob revisão de segurança | Contate o suporte para liberação |
Modo de teste (≤ R$ 5,00)
Não temos ambiente de sandbox separado — sua API key é sempre real. Para você distinguir integração de produção, marcamos automaticamente toda transação com valor menor ou igual a R$ 5,00 com o campo test_mode: true.
- Aparece no response de
POST /charges,/payoutse em todo webhook. - O dinheiro se move normalmente — taxa, saldo e liquidação são reais.
- Serve para você separar logs de testes internos da sua contabilidade.
{
"id": "chg_...",
"amount": 1.00,
"test_mode": true,
"status": "pending"
}Paginação por cursor
As listagens GET /charges e GET /payouts usam paginação por cursor — estável mesmo quando novos registros chegam em tempo real.
limitnumberTamanho da página (padrão 50, máximo 200).
starting_afterstringID do último item da página anterior (ex.: chg_... ou pyt_...). Aceita também uma data ISO 8601.
Resposta
{
"data": [ { "id": "chg_...", "amount": 49.90, "status": "paid" } ],
"has_more": true,
"next_starting_after": "chg_9c4e2f8a..."
}Quando has_more é false, você chegou ao fim. Ordenação padrão: mais recente primeiro.
Cash-in
Criar cobrança Pix
/chargesGera um QR Code Pix (copia-e-cola + PNG base64) com vencimento configurável. Roteia entre adquirentes com failover automático.
Body
amountnumberObrigatórioValor em reais (ex.: 49.90).
descriptionstringDescrição livre (até 140 caracteres).
payer_namestringNome do pagador. Aceita também aninhado: payer: { name }.
payer_documentstringCPF/CNPJ do pagador (só números).
expires_innumber (segundos)Validade do QR (60 a 86400, padrão 3600).
external_idstringSua chave de negócio (deduplica).
curl -X POST https://api.phanterpay.com.br/v1/charges \
-H "Authorization: Bearer bp_XXXX_xxx..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3d9d1f9e-9c9d-4d3e-b1a1-1234" \
-d '{
"amount": 49.90,
"description": "Pedido #1042",
"payer_name": "João da Silva",
"payer_document": "12345678909",
"expires_in": 3600,
"external_id": "pedido_1042"
}'Resposta 201
{
"id": "chg_9c4e2f8a1b3d4c5e6f7a8b9c0d1e2f3a",
"txid": "9c4e2f8a1b3d4c5e6f7a8b9c0d1e2f3a",
"external_id": "pedido_1042",
"amount": 49.90,
"test_mode": false,
"status": "pending",
"description": "Pedido #1042",
"pix": {
"copy_paste": "00020126580014BR.GOV.BCB.PIX0136...6304ABCD",
"qr_code_image": "data:image/png;base64,iVBORw0K..."
},
"end_to_end_id": null,
"paid_at": null,
"expires_at": "2026-07-13T18:30:00Z",
"created_at": "2026-07-13T17:30:00Z"
}Consultar cobrança
/charges/{id}Aceita o chg_... retornado, o txid legado, ou ext:<external_id>.
curl https://api.phanterpay.com.br/v1/charges/chg_9c4e2f... \
-H "Authorization: Bearer bp_XXXX_xxx..."
# ou por external_id:
curl https://api.phanterpay.com.br/v1/charges/ext:pedido_1042 \
-H "Authorization: Bearer bp_XXXX_xxx..."Resposta 200
{
"id": "chg_9c4e2f8a1b3d4c5e6f7a8b9c0d1e2f3a",
"external_id": "pedido_1042",
"amount": 49.90,
"test_mode": false,
"status": "paid",
"description": "Pedido #1042",
"pix": {
"copy_paste": "00020126580014BR.GOV.BCB.PIX...",
"qr_code_image": "data:image/png;base64,iVBORw0K..."
},
"end_to_end_id": "E12345678202607272032abcdef123",
"paid_at": "2026-07-27T20:32:14Z",
"expires_at": "2026-07-27T21:30:00Z",
"created_at": "2026-07-27T20:30:00Z"
}Listar cobranças
/charges?limit=50curl "https://api.phanterpay.com.br/v1/charges?limit=100" \
-H "Authorization: Bearer bp_XXXX_xxx..."Cash-out
Enviar Pix
/payoutsEnvia Pix para qualquer chave (CPF, CNPJ, e-mail, telefone ou aleatória). As condições comerciais aplicáveis ao seu perfil são descontadas automaticamente. Saques via API não passam por aprovação manual — o limite máximo por transação é o campo api_tx_limitdo seu perfil. Saques manuais pelo painel acima do limite configurado passam por aprovação da equipe PhanterPay.
"status": "completed". O status inicial é sempre processing(ou failed). A confirmação final chega apenas via webhook payout.completedou polling em GET /payouts/:id.Body
amountnumberObrigatórioValor bruto em reais.
pix_keystringObrigatórioChave Pix do destinatário. Aceita também aninhado: pix: { key }.
pix_key_type'cpf' | 'cnpj' | 'email' | 'phone' | 'random'Opcional — inferido quando ausente.
recipient_namestringNome do destinatário.
recipient_documentstringCPF/CNPJ do destinatário.
descriptionstringDescrição (até 140 caracteres).
external_idstringSua chave de negócio (deduplica).
curl -X POST https://api.phanterpay.com.br/v1/payouts \
-H "Authorization: Bearer bp_XXXX_xxx..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: <uuid>" \
-d '{
"amount": 250.00,
"pix_key": "cliente@email.com",
"pix_key_type": "email",
"recipient_name": "Maria Souza",
"recipient_document": "98765432100",
"description": "Pagamento fornecedor",
"external_id": "fatura_2025_07"
}'Resposta 201
{
"id": "pyt_01hxy8k4z9a0b1c2d3e4f5g6h7",
"external_id": "fatura_2025_07",
"amount": 250.00,
"gross_amount": 257.00,
"net_amount": 250.00,
"fee_amount": 7.00,
"test_mode": false,
"status": "processing",
"pix": {
"key": "cliente@email.com",
"recipient_name": "Maria Souza",
"recipient_document": "98765432100"
},
"description": "Pagamento fornecedor",
"end_to_end_id": null,
"error_message": null,
"created_at": "2026-07-27T20:15:00Z",
"completed_at": null
}Consultar saque
/payouts/{id}Aceita pyt_..., o uuid legado, ou ext:<external_id>. Devolve o mesmo shape do POST — incluindo end_to_end_id quando o pagamento é liquidado.
curl https://api.phanterpay.com.br/v1/payouts/pyt_01hxy... \
-H "Authorization: Bearer bp_XXXX_xxx..."Resposta 200
{
"id": "pyt_01hxy8k4z9a0b1c2d3e4f5g6h7",
"external_id": "fatura_2025_07",
"amount": 250.00,
"test_mode": false,
"status": "completed",
"pix": {
"key": "cliente@email.com",
"recipient_name": "Maria Souza",
"recipient_document": "98765432100"
},
"description": "Pagamento fornecedor",
"end_to_end_id": "E60701190202607272045abcdef123",
"error_message": null,
"created_at": "2026-07-27T20:15:00Z",
"completed_at": "2026-07-27T20:15:47Z"
}Listar saques
/payouts?limit=50curl "https://api.phanterpay.com.br/v1/payouts?limit=100" \
-H "Authorization: Bearer bp_XXXX_xxx..."Saldo
Consultar saldo
/balancecurl https://api.phanterpay.com.br/v1/balance \
-H "Authorization: Bearer bp_XXXX_xxx..."Resposta 200
{
"available_balance": 12847.55,
"pending_balance": 1840.20,
"blocked_balance": 500.00,
"total_balance": 15187.75,
"currency": "BRL"
}MED
MED — Mecanismo Especial de Devolução
O MED é o processo do BACEN em que um pagador contesta uma transação Pix suspeita e solicita devolução. A PhanterPay recebe a infração do adquirente, congela o valor no seu saldo bloqueado e você tem até 7 dias corridos para apresentar defesa.
open— infração aberta, defesa pendenteunder_review— defesa enviada, aguardando análiseaccepted— devolução autorizada (valor debitado)rejected— defesa aceita, valor liberadocancelled— pagador desistiu
Você também pode gerenciar visualmente em MED / Infrações.
Listar infrações
/medFiltros: status, limit (máx 100), starting_after.
curl "https://api.phanterpay.com.br/v1/med?status=open&limit=20" \
-H "Authorization: Bearer bp_XXXX_xxx..."Consultar infração
/med/{id}curl https://api.phanterpay.com.br/v1/med/inf_abc123... \
-H "Authorization: Bearer bp_XXXX_xxx..."Enviar defesa
/med/{id}Envia sua defesa e move a infração para under_review.
curl -X POST https://api.phanterpay.com.br/v1/med/inf_abc123... \
-H "Authorization: Bearer bp_XXXX_xxx..." \
-H "Content-Type: application/json" \
-d '{
"defense_text": "Transação legítima. Cliente adquiriu o produto...",
"defense_evidence_url": "https://drive.exemplo.com/nota-fiscal.pdf"
}'Webhooks
Como funciona
Cadastre endpoints em API & Integrações, escolha os eventos que quer receber e guarde o whsec_... retornado (ele é exibido uma única vez). A PhanterPay envia POST assinado em JSON toda vez que um evento assinado ocorre.
- Content-Type:
application/json - Assinatura: header
X-PhanterPay-Signatureno formatot=<unix>,v1=<hex> - Metadados: headers
X-PhanterPay-Event-Id,X-PhanterPay-Event-Type,X-PhanterPay-Attempt - Timeout: 10s. Responda com
2xxrápido; processamento pesado deve ir para uma fila local.
Payload de exemplo
{
"id": "evt_9c4e2f8a1b3d4c5e6f7a8b9c",
"type": "charge.paid",
"created_at": "2026-07-27T20:32:15Z",
"data": {
"id": "chg_9c4e2f8a1b3d4c5e6f7a8b9c0d1e2f3a",
"external_id": "pedido_1042",
"amount": 49.90,
"status": "paid",
"pix": { "copy_paste": "...", "qr_code_image": "..." },
"end_to_end_id": "E12345678202607272032abcdef123",
"paid_at": "2026-07-27T20:32:14Z",
"created_at": "2026-07-27T20:30:00Z"
}
}Eventos disponíveis
| Tipo | Quando dispara |
|---|---|
| charge.created | Cobrança criada |
| charge.paid | Pagamento confirmado pelo adquirente |
| charge.expired | QR Code passou do expires_at sem pagamento |
| charge.refunded | Estorno aplicado |
| charge.failed | Cobrança rejeitada antes de gerar QR |
| payout.created | Payout aceito para processamento |
| payout.processing | Payout enviado ao BACEN |
| payout.completed | Liquidação Pix confirmada (end_to_end_id disponível) |
| payout.failed | Falha antes da liquidação |
| payout.reversed | Devolução do BACEN após liquidação |
| med.opened | Infração MED aberta contra sua conta |
| med.updated | Status da infração mudou (defesa/decisão) |
| med.resolved | MED finalizado (accepted/rejected/cancelled) |
Assinatura & verificação
A assinatura é HMAC-SHA256 do material <t>.<raw_body> — concatenamos o timestamp t do header, um ponto, e o body cru (bytes exatos que chegam na requisição), assinados com o secret do endpoint. Reformatar o JSON quebra a assinatura — sempre calcule sobre o buffer original. Incluir o t no HMAC bloqueia ataques de replay onde só o timestamp do header é trocado.
import { createHmac, timingSafeEqual } from "crypto";
import express from "express";
const app = express();
// IMPORTANTE: precisamos do body cru (Buffer), NÃO do JSON parseado —
// qualquer reformatação quebra a assinatura HMAC.
app.post("/webhooks/phanterpay", express.raw({ type: "application/json" }), (req, res) => {
const secret = process.env.PHANTERPAY_WEBHOOK_SECRET; // whsec_...
const header = req.header("x-phanterpay-signature") ?? "";
// Formato: "t=<unix>,v1=<hex>"
const parts = Object.fromEntries(
header.split(",").map(kv => kv.split("=") as [string, string]),
);
const t = parts.t;
const v1 = parts.v1;
if (!t || !v1) return res.status(400).send("missing signature");
// Defesa anti-replay: rejeita eventos com mais de 5 minutos
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) {
return res.status(400).send("timestamp too old");
}
// O material assinado é "<t>.<raw_body>" — o timestamp faz parte do HMAC,
// então trocar apenas o `t` do header invalida a assinatura.
const signedPayload = Buffer.concat([Buffer.from(`${t}.`), req.body]);
const expected = createHmac("sha256", secret).update(signedPayload).digest("hex");
const a = Buffer.from(v1, "hex");
const b = Buffer.from(expected, "hex");
if (a.length !== b.length || !timingSafeEqual(a, b)) {
return res.status(401).send("invalid signature");
}
const event = JSON.parse(req.body.toString());
switch (event.type) {
case "charge.paid": await creditOrder(event.data.external_id); break;
case "payout.completed": await markPayoutDone(event.data.external_id); break;
// ... demais tipos
}
res.status(200).send("ok"); // devolva 2xx rápido; processamento pesado em fila
});Retentativas
Se o seu endpoint não responder com 2xx em até 10 segundos, agendamos até 5 retentativas com backoff exponencial:
| Tentativa | Espera após falha |
|---|---|
| 1 → 2 | 1 minuto |
| 2 → 3 | 5 minutos |
| 3 → 4 | 30 minutos |
| 4 → 5 | 2 horas |
| 5 → 6 | 12 horas |
Todas as tentativas (bem-sucedidas ou não) ficam auditadas em API & Integrações → Webhooks.
Testando webhooks localmente
Como o PhanterPay precisa alcançar sua URL pela internet pública, o jeito mais rápido de testar em localhost é expor sua porta com um túnel HTTP. Fluxo recomendado:
- Suba seu servidor local (ex.:
http://localhost:3000/webhooks/phanterpay). - Abra um túnel público:
ngrok http 3000(oucloudflared tunnel --url http://localhost:3000). Copie a URLhttps://.... - Em API & Integrações → Webhooks, cadastre a URL do túnel +
/webhooks/phanterpaye guarde owhsec_.... - Crie uma cobrança em modo de teste (valor ≤ R$ 5,00) e pague pelo QR — o evento
charge.paidchega no seu endpoint em segundos. - Para reprocessar sem gerar transação nova, use o botão "Reenviar" na tela do webhook — ele dispara o payload original com um novo
X-PhanterPay-Attempt.
Dica: valide a assinatura já no ambiente local — assim você não descobre um bug de HMAC só em produção. O secret é o mesmo em teste e produção porque o webhook é cadastrado por endpoint, não por ambiente.
whsec_ do túnel: como o secret vale tanto em teste quanto em produção, quem capturar essa string em um túnel público, log de terminal, print de tela ou histórico de shell consegue forjar webhooks reais para o seu endpoint. Não deixe o túnel exposto após o teste, não commit o secret e prefira cadastrar um endpoint de webhook dedicado só para o ambiente de desenvolvimento — assim você pode rotacionar o whsec_ sem afetar produção.Referência
Status de cada recurso
Cobrança (charge)
pending → paid · expired · refunded
Payout (cash-out)
pending → processing → completed · failed · reversed
A resposta síncrona do POST /payouts nunca devolve completed — esse status só aparece no webhook payout.completed após confirmação do BACEN.
Códigos de erro HTTP
Todos os erros retornam o mesmo envelope estruturado — nunca uma string simples. O envelope combina success: false, um objeto errorcom code / message / retryable, e um request_id para rastreio no suporte.
{
"success": false,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Saldo insuficiente para completar o saque.",
"retryable": false
},
"request_id": "req_9c4e2f8a1b3d4c5e6f7a8b9c0d1e2f3a"
}Em erros de validação (400) vem também issues com o path normalizado (não é mais um array Zod):
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation error",
"retryable": false,
"issues": [
{ "path": "amount", "message": "Expected number, received string" }
]
},
"request_id": "req_..."
}Trate erros pelo status HTTP + error.code (fonte de verdade). O texto de error.message pode mudar para melhorar clareza — não faça parsing por string. Toda resposta inclui o header X-Request-Id com o mesmo valor de request_id — cite-o em qualquer contato com o suporte.
| HTTP | error.code | Significado |
|---|---|---|
| 400 | VALIDATION_ERROR | Body inválido, JSON malformado ou campos faltando (ver issues) |
| 401 | UNAUTHORIZED | API key ausente ou inválida |
| 402 | INSUFFICIENT_BALANCE | Saldo insuficiente para cash-out |
| 403 | FORBIDDEN | Recurso desabilitado no perfil ou escopo insuficiente |
| 404 | NOT_FOUND | Recurso inexistente (id/external_id não encontrado) |
| 409 | IDEMPOTENCY_CONFLICT | Idempotency-Key reusada com body diferente |
| 422 | AMOUNT_LIMIT_EXCEEDED | Valor acima do limite por transação (api_tx_limit) |
| 423 | ACCOUNT_LOCKED | Conta sob revisão — contate o suporte |
| 429 | RATE_LIMIT_EXCEEDED | Retry-After segundos e retente (retryable: true) |
| 502 | PROVIDER_UNAVAILABLE | Todas as adquirentes falharam — repita a requisição |
SDKs & ferramentas
Não publicamos SDK oficial ainda — mas a API é 100% REST + JSON, então qualquer cliente HTTP funciona.
- Postman / Insomnia / Bruno — cole qualquer cURL desta doc.
- n8n / Make / Zapier — nó HTTP Request com Bearer token, sem plugin proprietário.
- Agentes de IA (ChatGPT, Claude, Cursor) — cole o prompt da seção Integrar com IA.
Dúvidas de integração? Fale com nosso suporte.
Referência
Política de compatibilidade
A API v1 é estável. Nenhuma integração existente precisa ser alterada quando lançamos algo novo — seguimos estas garantias:
- Nunca removemos nem renomeamos campos, endpoints ou valores de
statusjá documentados na v1. - Novos campos são sempre opcionais e aditivos. Trate objetos JSON como abertos: ignore chaves que você ainda não conhece em vez de falhar a leitura.
- Novos métodos de pagamento entram como novos endpoints (ex.:
/crypto_payments, futuramente/card_payments) — nunca mudando o comportamento de/charges, que continua exclusivo de Pix. - Novos eventos de webhook podem surgir a qualquer momento. Sua rota deve responder
200e ignorar tipos desconhecidos, sem quebrar. - Novos códigos de erro mantêm o mesmo envelope (
error.code,error.message) e o mesmo padrãosnake_case. - Qualquer mudança incompatível viraria uma nova versão (
/v2) com anúncio prévio e a v1 permanecendo ativa.
Recomendação prática: ao processar respostas e webhooks, faça switch por payment_method / type com um caso default neutro. Assim, quando um novo método (cartão, por exemplo) for ativado na sua conta, seu código continua rodando sem deploy.
Roadmap
Cartão de crédito/débito (ainda não disponível)
Status: não implementado. Hoje a PhanterPay opera Pix e cripto. Nenhum adquirente conectado oferece cartão via API. Esta seção existe apenas para você já modelar sua integração de forma que o dia do lançamento não exija reescrita.
Quando (e se) cartão for lançado, o desenho será este — reservado desde já:
- Novo recurso isolado:
POST /card_paymentseGET /card_payments/:id./chargesnão muda. - Novo prefixo de ID:
crd_, seguindo o mesmo padrão dechg_/pyt_. - Mesma autenticação (Bearer API Key), mesma
Idempotency-Keyobrigatória, mesmoexternal_id. - Novos eventos aditivos:
card_payment.authorized,card_payment.paid,card_payment.refused,card_payment.refunded,card_payment.chargeback. - Dados sensíveis do cartão nunca trafegariam pela sua aplicação: tokenização no front (PCI) e envio apenas do token.
- Taxas de cartão seriam configuradas por conta, exatamente como já ocorre hoje com Pix e cripto — sem impacto nas taxas vigentes.
Enquanto isso, chamadas a /card_payments retornam 404 not_found. Não escreva lógica que dependa desse endpoint até o anúncio oficial no Changelog.
Roadmap
Split de pagamentos (previsto)
Split de pagamentos: recurso previsto para versões futuras. Caso sua operação necessite dessa funcionalidade, entre em contato com a equipe da PhanterPay.
Referência
Changelog
- 2026-07-27v1
Rate limiting por endpoint, modo de teste e paginação por cursor
- Limites de requisição aplicados por endpoint e credencial; capacidade dedicada sob solicitação. Em 429, a API retorna o header
Retry-After. - Campo
test_mode: booleanpara valores ≤ R$ 5,00. starting_afteremGET /chargeseGET /payouts.
- Limites de requisição aplicados por endpoint e credencial; capacidade dedicada sob solicitação. Em 429, a API retorna o header
- 2026-07-20v1
Webhooks assinados + external_id
- Assinatura HMAC-SHA256 no header
X-PhanterPay-Signature. - Dedupe semântico via
external_idem todos os recursos. - IDs prefixados
chg_/pyt_/bol_/evt_.
- Assinatura HMAC-SHA256 no header