Início rápido
Três passos até a primeira cobrança em produção. Todos os valores em centavos, datas em ISO 8601 (UTC).
curl -X GET https://finance.zendry.co/api/public/v1/balance \ -H "Authorization: Bearer gw_sua_chave_aqui"Autenticação
Toda requisição exige sua chave de API no cabeçalho Authorization: Bearer <chave> ou x-api-key: <chave> — os dois formatos são aceitos. Gere e revogue chaves em Integrações.
- Valores monetários sempre em centavos (inteiro). Datas em ISO 8601 (UTC).
- Com IPs permitidos cadastrados em Integrações, chamadas de outros IPs recebem 403.
- A confirmação de pagamentos é por webhook — os GETs servem para reconsulta, não para polling agressivo.
Pix
Criar cobrança Pix
Gera uma cobrança e retorna o código copia e cola. A confirmação do pagamento chega pelo webhook pix.received — não faça polling.
- status: pendente → pago (ou expirado se vencer sem pagamento).
- processor identifica o provedor que gerou o QR (ex.: traction, velopag). Pode vir null se o provedor não informar.
- external_id não deduplica: duas chamadas iguais criam duas cobranças. Guarde o id retornado.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| amount_cents | inteiro | Sim | Valor em centavos. De 1 a 100000000 (R$ 1 milhão). |
| description | texto | Não | Até 140 caracteres. |
| external_id | texto | Não | Seu identificador. Volta nos webhooks. Não deduplica. |
curl -X POST https://finance.zendry.co/api/public/v1/charges \ -H "Authorization: Bearer gw_sua_chave_aqui" \ -H "Content-Type: application/json" \ -d '{ "amount_cents": 15000, "description": "Pedido #1234", "external_id": "1234"}'{ "id": "3f1c...", "status": "pendente", "amount_cents": 15000, "description": "Pedido #1234", "external_id": "1234", "pix_code": "00020126...5303986540515.00...", "processor": "traction", "created_at": "2026-01-01T12:00:00Z"}Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.
Consultar cobrança
Retorna o estado atual de uma cobrança Pix ou boleto (o mesmo endpoint atende os dois — a resposta segue o formato do tipo da cobrança).
- 404 se o id não existir ou pertencer a outra conta.
curl -X GET https://finance.zendry.co/api/public/v1/charges/:id \ -H "Authorization: Bearer gw_sua_chave_aqui"{ "id": "3f1c...", "status": "pago", "amount_cents": 15000, "description": "Pedido #1234", "external_id": "1234", "pix_code": "00020126...", "created_at": "2026-01-01T12:00:00Z", "paid_at": "2026-01-01T12:04:11Z"}Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.
Enviar Pix (saque)
Debita o saldo disponível e envia um Pix para a chave informada. O valor + tarifa são reservados na hora; o resultado final chega pelo webhook pix.sent.
- status: enviando → concluido ou falhou (aviso via webhook pix.sent; em falha o valor reservado é devolvido ao saldo).
- 409 quando o saldo é insuficiente para valor + tarifa.
- IMPORTANTE — não repita a chamada após timeout sem antes consultar o extrato: o débito pode já ter sido reservado. A API não aceita chave de idempotência do cliente hoje.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| amount_cents | inteiro | Sim | Valor em centavos. De 1 a 100000000. |
| pix_key | texto | Sim | Chave do favorecido. |
| pix_key_type | cpf | cnpj | email | phone | evp | Não | Sem este campo, o tipo é inferido do formato da chave. |
| description | texto | Não | Até 140 caracteres. |
curl -X POST https://finance.zendry.co/api/public/v1/payouts \ -H "Authorization: Bearer gw_sua_chave_aqui" \ -H "Content-Type: application/json" \ -d '{ "amount_cents": 5000, "pix_key": "cliente@email.com", "pix_key_type": "email", "description": "Repasse"}'{ "id": "9ac2...", "status": "enviando", "amount_cents": 5000, "fee_cents": 149, "reserved_cents": 5149, "idempotent_id": "b7e1...", "created_at": "2026-01-01T12:00:00Z"}Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.
Boleto
Criar cobrança por boleto
Emite um boleto para o comprador informado. A confirmação chega pelo webhook boleto.paid (compensação bancária, normalmente D+1).
- Exige conta habilitada na adquirente (mesmo cadastro do cartão).
- data_limite é o último dia em que o boleto ainda é aceito após o vencimento.
- Consulte pelo mesmo GET /charges/:id.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| amount_cents | inteiro | Sim | Valor em centavos. |
| vencimento | data (AAAA-MM-DD) | Sim | Vencimento do boleto. |
| buyer | objeto | Sim | first_name, last_name, email e taxpayer_id (CPF ou CNPJ). |
| buyer.address | objeto | Não | line1, neighborhood, city, state, postal_code. Opcional inteiro. |
| description | texto | Não | Até 140 caracteres. |
| external_id | texto | Não | Seu identificador. |
curl -X POST https://finance.zendry.co/api/public/v1/charges/boleto \ -H "Authorization: Bearer gw_sua_chave_aqui" \ -H "Content-Type: application/json" \ -d '{ "amount_cents": 15000, "description": "Pedido #1234", "external_id": "1234", "vencimento": "2026-01-15", "buyer": { "first_name": "Maria", "last_name": "Souza", "email": "maria@email.com", "taxpayer_id": "12345678900", "address": { "line1": "Av. Paulista, 1000", "neighborhood": "Bela Vista", "city": "São Paulo", "state": "SP", "postal_code": "01310100" } }}'{ "id": "3f1c...", "status": "pendente", "amount_cents": 15000, "description": "Pedido #1234", "external_id": "1234", "vencimento": "2026-01-15", "data_limite": "2026-01-17", "linha_digitavel": "23791.23456 78900.123456 ...", "codigo_barras": "23797890000015000123456...", "url_pdf": "https://...boleto.pdf", "created_at": "2026-01-01T12:00:00Z"}Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.
Cartão
Checkout & 3DS — passo a passo
O 3DS da Zendry é inteligente (data-only): na maioria das vendas a autenticação acontece nos bastidores, sem tela — basta enviar os dados do dispositivo do comprador. O desafio no navegador (iframe do banco) só aparece quando o emissor exige, e o Zendry.js cuida dele. O SDK nunca vê o número do cartão nem credenciais.
Comprador
Preenche o cartão na sua página de checkout.
Seu front
Zendry.js coleta o device (idioma, tela, fuso). O cartão não passa pelo SDK.
Seu backend
POST /card_payments com cartão, device, ip_address e user_agent.
Zendry
Autentica com o emissor e responde ao seu servidor.
Emissor
Decide sem tela (data-only) ou pede desafio ao comprador.
200 approved
Data-only: autenticou nos bastidores, sem tela.
201 requires_action
O emissor pediu desafio — Zendry.js conclui no navegador.
402 declined
failure_reason traz o motivo. Ofereça outro cartão.
Webhook card.approved / card.declined
O desfecho oficial. Só libere o pedido aqui — nunca pelo retorno do SDK.
É o mesmo arquivo dos dois lados. O envio não chega à adquirente: o desfecho abaixo é escolhido por você. Numa venda real, quem confirma é sempre o webhook.
Carregue o SDK e colete o device
Coloque o script na página de checkout e chame Zendry.threeDS() logo antes de enviar o formulário. Ele devolve um objeto pequeno com idioma, tela e fuso do comprador — nenhum dado de cartão passa pelo SDK. A página inteira está no simulador acima, na aba Código.
<script src="https://api.zendry.co/sdk/v1/zendry.js"></script> const threeds = await Zendry.threeDS();// threeds.device → { language, screen_height, screen_width, time_zone_offset }Crie a venda no seu backend
Sua chave nunca vai ao navegador: quem chama a Zendry é o seu servidor. Repasse o device recebido do front e acrescente o ip_address e o user_agent do comprador — são eles que sustentam a autenticação sem tela.
app.post("/checkout/pagar", async (req, res) => { const r = await fetch("https://finance.zendry.co/api/public/v1/card_payments", { method: "POST", headers: { Authorization: `Bearer ${process.env.ZENDRY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ amount_cents: req.body.amount_cents, installments: 1, external_id: req.body.pedido_id, buyer: req.body.buyer, card: req.body.card, device: req.body.device, ip_address: req.headers["x-forwarded-for"]?.split(",")[0], user_agent: req.headers["user-agent"], }), }); const venda = await r.json(); switch (venda.status) { case "aprovada": return res.json({ status: "approved", id: venda.id }); case "requires_action": // devolva a action ao front — o Zendry.js conclui o desafio return res.json({ status: "requires_action", action: venda.action, id: venda.id }); default: return res.status(402).json({ status: "declined", motivo: venda.failure_reason }); }});Conclua o desafio quando vier 201 requires_action
O action é opaco: devolva-o ao seu front exatamente como veio e chame Zendry.completeThreeDS. Sem desafio de fato (frictionless), ele resolve em segundos, sem tela.
const desfecho = await Zendry.completeThreeDS(venda.action); if (desfecho.status === "authorized") mostrarSucesso();else if (desfecho.status === "expired") pedirNovaTentativa();else mostrarRecusa(); // isto é UX. O pedido só é liberado depois da confirmação do seu servidor.Confirme o desfecho no servidor
O retorno do SDK é para a tela; a verdade é o webhook (ou o GET /api/public/v1/card_payments/:id). Valide a assinatura HMAC antes de liberar qualquer pedido.
import crypto from "node:crypto"; app.post("/webhooks/zendry", express.raw({ type: "*/*" }), (req, res) => { const assinatura = req.headers["x-zendry-signature"]; const esperado = crypto .createHmac("sha256", process.env.ZENDRY_WEBHOOK_SECRET) .update(req.body) .digest("hex"); if (assinatura !== esperado) return res.status(401).end(); const evento = JSON.parse(req.body.toString()); if (evento.event === "card.approved") liberarPedido(evento.data.external_id); if (evento.event === "card.declined") cancelarPedido(evento.data.external_id); res.status(200).end();});| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| aprovada (200) | mostre o sucesso | Não | Webhook card.approved confirma. Libere o pedido nele. |
| requires_action (201) | chame completeThreeDS | Não | Após o desafio chega card.approved ou card.declined. Sem conclusão em 15 min, a venda expira. |
| recusada (200) | ofereça outro cartão | Não | failure_reason traz o motivo. Webhook card.declined. |
| 422 | corrija a requisição | Não | error_type nomeia a causa (ex.: ACQUIRER_NOT_APPROVED, device ausente com postura reforçada). |
- Envie device em TODA venda: aumenta a taxa de aprovação, e contas com postura de risco reforçada recusam a requisição sem ele (422).
- action.session_id é de uso único e expira em 15 minutos — se o comprador não concluir, a venda expira e é recusada.
- Nunca libere o pedido pelo retorno do SDK: só o webhook (ou o GET) é o resultado oficial.
- Não reaproveite o mesmo action em outra tentativa — crie uma nova venda.
No sandbox o cartão é simulado sem 3DS real: qualquer número válido aprova e 4000 0000 0000 0002 recusa (use o console do endpoint Pagamento com cartão). O desafio 3DS de verdade só acontece em produção.
Pagamento com cartão
Cobra um cartão de crédito server-to-server. Exige conta habilitada na adquirente. O número do cartão nunca é armazenado.
- Sandbox (chave gw_test_): a autorização é simulada — qualquer número de cartão válido APROVA e 4000 0000 0000 0002 RECUSA; sem 3DS; o estorno também é simulado.
- 201 + status requires_action: conclua no navegador com Zendry.completeThreeDS(resposta.action) — veja o guia Checkout & 3DS acima. O desfecho oficial chega pelo webhook card.approved / card.declined e pelo GET abaixo.
- Recusa NÃO é erro HTTP: vem 200 com status recusada e failure_reason legível.
- Erros de negócio vêm como 422 no formato { error, error_type, error_code, retry } — catálogo na seção Erros & limites.
- Se sua plataforma revende para lojistas (gateway em cadeia), envie ip_address e user_agent do comprador final — os cabeçalhos HTTP trazem o seu servidor, não o comprador.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| amount_cents | inteiro | Sim | Valor em centavos. |
| installments | inteiro | Não | 1 a 12. Padrão 1. |
| buyer | objeto | Sim | name, email e taxpayer_id (CPF, 11 dígitos). |
| card | objeto | Sim | holder_name, number, expiration_month (MM), expiration_year (AAAA) e security_code. |
| billing | objeto | Não | Endereço de cobrança. |
| device | objeto | Não | Dados do dispositivo do comprador coletados pelo Zendry.js (Zendry.threeDS()). Aumenta a aprovação — e contas com postura de risco reforçada EXIGEM este campo. Campos: language, screen_height, screen_width, time_zone_offset (horas, Brasil = -3). |
| ip_address | texto | Não | IP do COMPRADOR. Obrigatório na prática em integração em cadeia. |
| user_agent | texto | Não | User-agent do COMPRADOR. |
| external_id | texto | Não | Seu identificador. |
curl -X POST https://finance.zendry.co/api/public/v1/card_payments \ -H "Authorization: Bearer gw_sua_chave_aqui" \ -H "Content-Type: application/json" \ -d '{ "amount_cents": 15000, "installments": 1, "external_id": "1234", "buyer": { "name": "Maria Souza", "email": "maria@email.com", "taxpayer_id": "12345678900" }, "card": { "holder_name": "MARIA SOUZA", "number": "4111111111111111", "expiration_month": "12", "expiration_year": "2030", "security_code": "123" }, "ip_address": "203.0.113.10", "user_agent": "Mozilla/5.0 ..."}'{ "id": "7d21...", "charge_id": "1b8e...", "status": "aprovada", "amount_cents": 15000, "installments": 1, "external_id": "1234", "created_at": "2026-01-01T12:00:00Z"}Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.
Consultar transação de cartão
Retorna a transação pelo id devolvido na criação — inclusive para acompanhar o desfecho de um 3DS.
- status: aprovada | recusada | estornada | requires_action.
curl -X GET https://finance.zendry.co/api/public/v1/card_payments/:id \ -H "Authorization: Bearer gw_sua_chave_aqui"{ "id": "7d21...", "charge_id": "1b8e...", "status": "aprovada", "amount_cents": 15000, "gross_cents": 15000, "fee_cents": 899, "net_cents": 14101, "installments": 1, "external_id": "1234", "failure_reason": null, "refunded_at": null, "created_at": "2026-01-01T12:00:00Z"}Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.
Estornar transação
Estorno total. Disponível apenas para transações aprovadas e dentro de 24 horas da aprovação.
- Após 24h ou em transação não aprovada: 422 com a razão no campo error.
- O evento card.refunded é disparado no webhook de recebimento.
curl -X POST https://finance.zendry.co/api/public/v1/card_payments/:id/refund \ -H "Authorization: Bearer gw_sua_chave_aqui"{ "id": "7d21...", "status": "estornada", "refunded_at": "2026-01-01T18:22:40Z"}Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.
Disputas
Listar disputas
Disputas (MED e chargeback) da sua conta, das mais recentes para as mais antigas. A abertura chega pelo webhook dispute.opened; use a lista para acompanhar o estado.
- O valor da disputa é sempre o da transação inteira — não existe disputa parcial.
- Enquanto viva, o valor fica retido do saldo disponível; o desfecho chega pelo webhook dispute.closed (outcome won/lost).
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| status | open | closed | exato | Não | open = vivas (aberta, em_defesa, em_analise); closed = ganha e perdida; ou um status exato. |
| limit | inteiro | Não | De 1 a 100. Padrão 50. |
curl -X GET https://finance.zendry.co/api/public/v1/disputes \ -H "Authorization: Bearer gw_sua_chave_aqui"{ "disputes": [ { "id": "19ba...", "type": "med", "status": "aberta", "amount_cents": 15000, "currency": "BRL", "transaction_reference": "469208464", "reason": "MED REFUND_REQUEST", "opened_at": "2026-01-01T12:00:00Z", "defense_deadline": "2026-01-08T12:00:00Z", "responded_at": null, "closed_at": null } ]}Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.
Consultar disputa
Detalhe completo: dados da disputa, histórico de mensagens (você, administração, provedor) e evidências já anexadas.
- 404 se o id não existir ou pertencer a outra conta.
curl -X GET https://finance.zendry.co/api/public/v1/disputes/:id \ -H "Authorization: Bearer gw_sua_chave_aqui"{ "id": "19ba...", "type": "med", "status": "em_defesa", "amount_cents": 15000, "currency": "BRL", "transaction_reference": "469208464", "reason": "MED REFUND_REQUEST", "opened_at": "2026-01-01T12:00:00Z", "defense_deadline": "2026-01-08T12:00:00Z", "responded_at": "2026-01-02T09:30:00Z", "closed_at": null, "messages": [ { "author": "seller", "text": "Serviço prestado, comprovante anexo.", "created_at": "2026-01-02T09:30:00Z" } ], "evidence": [ { "name": "comprovante-entrega.pdf", "content_type": "application/pdf", "size_bytes": 182044, "created_at": "2026-01-02T09:29:00Z" } ]}Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.
Enviar defesa
Registra a sua defesa por escrito e move a disputa para em_defesa. Anexe as evidências antes ou depois — a defesa pode ser complementada enquanto a disputa estiver viva.
- 422 se a disputa já estiver encerrada (ganha ou perdida).
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| text | texto | Sim | A argumentação da defesa. De 5 a 4000 caracteres. |
curl -X POST https://finance.zendry.co/api/public/v1/disputes/:id/defense \ -H "Authorization: Bearer gw_sua_chave_aqui" \ -H "Content-Type: application/json" \ -d '{ "text": "Serviço prestado em 01/01, aceite do cliente no app. Comprovante anexo."}'{ "id": "19ba...", "status": "em_defesa"}Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.
Anexar evidência
Envia UM arquivo de evidência (comprovante de entrega, aceite, conversa com o cliente). O corpo é multipart/form-data com o campo `file` — o arquivo em si, não base64. Para vários arquivos, repita a chamada.
- Exemplo: curl -X POST .../disputes/{id}/evidence -H "Authorization: Bearer …" -F "file=@comprovante.pdf"
- 413 acima de 15MB; 422 se a disputa já estiver encerrada.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| file | arquivo | Sim | Até 15MB. PDF e imagens são os formatos usuais. |
curl -X POST https://finance.zendry.co/api/public/v1/disputes/:id/evidence \ -H "Authorization: Bearer gw_sua_chave_aqui"{ "id": "19ba...", "name": "comprovante-entrega.pdf", "size_bytes": 182044}Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.
Não contestar
Declara que você não vai se defender desta disputa. Ela segue para análise e o desfecho é comunicado pelo webhook dispute.closed.
- Aceita apenas disputas em aberta ou em_defesa (422 nos demais estados).
curl -X POST https://finance.zendry.co/api/public/v1/disputes/:id/accept \ -H "Authorization: Bearer gw_sua_chave_aqui"{ "id": "19ba...", "status": "em_analise"}Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.
Consultas
Consultar saldo
Retorna o saldo disponível da conta autenticada.
curl -X GET https://finance.zendry.co/api/public/v1/balance \ -H "Authorization: Bearer gw_sua_chave_aqui"{ "balance_cents": 84851, "currency": "BRL"}Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.
Extrato
Lista as movimentações da conta, das mais recentes para as mais antigas (até 200 itens).
- Limite fixo de 200 itens por chamada — use from/to para paginar por período.
- Para conciliação em tempo real prefira os webhooks; o extrato é a fonte para fechamento e auditoria.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| from | data (AAAA-MM-DD) | Não | Dia inicial, fuso de Brasília. |
| to | data (AAAA-MM-DD) | Não | Dia final, fuso de Brasília. |
| type | texto | Não | Filtra pelo campo "type" das respostas (ex.: pagamento_confirmado). |
curl -X GET https://finance.zendry.co/api/public/v1/statement \ -H "Authorization: Bearer gw_sua_chave_aqui"{ "transactions": [ { "id": "9ac2...", "type": "pagamento_confirmado", "gross_cents": 15000, "fee_cents": 299, "net_cents": 14701, "end_to_end_id": "E18236120202601011204a1b2c3", "description": "Pedido #1234", "counterparty": "Maria Souza", "created_at": "2026-01-01T12:04:11Z" } ]}Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.
Webhooks
Como funcionam
Configure em Integrações duas URLs: a de recebimento (cobranças, cartão, disputas, assinaturas) e a de saque (pix.sent e demais saídas). Enviamos um POST JSON com os cabeçalhos X-Zendry-Event e X-Zendry-Signature: sha256=<hmac>.
{ "event": "pix.received", "sent_at": "2026-01-01T12:04:12Z", "data": { ...campos do evento... }}{ "charge_id": "3f1c...", "external_id": "1234", "amount_cents": 15000, "description": "Pedido #1234", "end_to_end_id": "E1823612...", "environment": "producao", "status": "pago"}const crypto = require("node:crypto"); function assinaturaValida(corpoBruto, cabecalho, segredo) { const esperada = "sha256=" + crypto.createHmac("sha256", segredo).update(corpoBruto).digest("hex"); return ( cabecalho?.length === esperada.length && crypto.timingSafeEqual(Buffer.from(esperada), Buffer.from(cabecalho)) );}// assinaturaValida(rawBody, req.headers["x-zendry-signature"], SEGREDO)- Responda 2xx em até 10 segundos; processe de forma assíncrona se precisar.
- Em falha (rede, timeout ou status ≥ 400) tentamos até 3 vezes, com espera crescente.
- Toda entrega e reentrega fica registrada em Integrações — de lá você reenvia um evento perdido.
- Trate entregas por charge_id/payout_id de forma idempotente: reentregas do mesmo evento podem ocorrer.
Catálogo de eventos
- pix.receivedCobrança Pix paga (canal de recebimento)
- pix.sentSaque Pix concluído ou falhou (canal de saque)
- boleto.paidBoleto compensado
- card.approvedTransação aprovada (inclusive após 3DS)
- card.declinedTransação recusada
- card.refundedTransação estornada
- charge.expiredCobrança expirou sem pagamento
- charge.canceledCobrança cancelada
- dispute.openedContestação (chargeback/MED) aberta
- dispute.closedContestação encerrada
- subscription.createdAssinatura criada
- subscription.charge_approvedCobrança recorrente aprovada
- subscription.charge_declinedCobrança recorrente recusada
- subscription.retry_scheduledNova tentativa agendada
- subscription.retries_exhaustedTentativas esgotadas
- subscription.canceledAssinatura cancelada
- subscription.completedAssinatura concluída
- crypto.sentEnvio cripto concluído
- crypto.failedEnvio cripto falhou
- intl.deposit.receivedDepósito internacional recebido
- intl.payout.sentEnvio internacional concluído
Sandbox
Ambiente de testes
Gere uma chave de teste na página Integrações (prefixo gw_test_). Ela usa a mesma URL base e as mesmas rotas, mas opera um ambiente isolado: saldo, extrato e cobranças de teste ficam separados de produção e nenhum dinheiro real circula — o Pix é simulado internamente, sem tocar provedores.
- Cobertura: Pix completo, boleto e cartão (criação, consulta, pagamento/autorização simulados, estorno) — com webhooks e saldo de teste.
- Boleto: emite com números de teste (linha digitável/código de barras fake) e paga pelo mesmo POST /charges/:id.
- Cartão: simulado sem 3DS — qualquer número válido aprova; 4000 0000 0000 0002 recusa; estorno também é simulado.
- Os webhooks são reais (mesma URL e assinatura HMAC), com environment: "sandbox" no payload.
- Chave de teste não enxerga dados de produção, e vice-versa.
- Saque de teste conclui na hora (webhook pix.sent). Valor terminado em 99 centavos (ex.: 10099) simula falha — o valor volta ao saldo.
Simular pagamento (sandbox)
Marca a cobrança de teste (Pix OU boleto) como paga: lança no saldo sandbox e dispara o webhook real (pix.received ou boleto.paid), com assinatura HMAC. Exclusivo da chave de teste — em produção retorna 403.
- Só funciona com chave gw_test_ e cobrança do ambiente sandbox (senão 403/404).
- Idempotente: cobrança já paga responde com already_paid: true.
- Cobrança expirada não pode ser paga (422).
curl -X POST https://finance.zendry.co/api/public/v1/charges/:id \ -H "Authorization: Bearer gw_sua_chave_aqui"{ "id": "3f1c...", "status": "pago", "paid_at": "2026-01-01T12:04:11Z"}Crie sua conta para simular pagamentos no sandbox com sua chave de teste.
Erros & limites
Códigos HTTP
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| 401 | auth | Não | Credencial ausente, inválida ou revogada. |
| 403 | acesso | Não | IP fora da lista de permitidos, ou conta bloqueada. |
| 404 | recurso | Não | Recurso inexistente ou de outra conta. |
| 409 | saldo | Não | Saldo insuficiente (saques). |
| 422 | validação | Não | Payload inválido ou regra de negócio. Traz details por campo. |
{ "error": "Payload inválido", "details": [ { "path": ["amount_cents"], "message": "Number must be greater than or equal to 1" } ]}Erros de cartão
As rotas de cartão usam um formato próprio no 422 — retry indica se vale tentar novamente a mesma requisição.
{ "error": "Conta não habilitada para cartão de crédito", "error_type": "ACQUIRER_NOT_APPROVED", "error_code": null, "retry": false}| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| INVALID_REQUEST | retry: false | Não | Dado inválido (número do cartão, CPF etc.). |
| ACQUIRER_NOT_APPROVED | retry: false | Não | Conta ainda não habilitada na adquirente. |
| PAYMENT_DECLINED | retry: false | Não | Recusa do emissor. |
| PENDING_CHECK | retry: false | Não | Transação em verificação; consulte o GET antes de repetir. |
| PROVIDER_ERROR | retry: variável | Não | Falha na adquirente; retry: true quando reprocessável. |
| INTERNAL_ERROR | retry: true | Não | Falha nossa; tente novamente. |
Limites & boas práticas
- Idempotência: a API não aceita chave de idempotência do cliente. Em timeout de um POST que movimenta dinheiro (saque, cartão), consulte o extrato/GET antes de repetir.
- Confirmação de pagamento é por webhook; se fizer reconsulta, limite a frequência (ex.: a cada 30s) — tráfego abusivo pode ser bloqueado pelo firewall.