Desenvolvedores

Início rápido

finance.zendry.co

Três passos até a primeira cobrança em produção. Todos os valores em centavos, datas em ISO 8601 (UTC).

1 · Chave
Abra sua conta
Exibida uma única vez na criação. Restrinja por IP se puder.
2 · Teste
GET /balance
Confirma que a credencial e o IP estão liberados.
3 · Webhook
URLs de recebimento e saque
A confirmação de pagamento chega por evento, não por polling.
Primeira chamada
200
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

POST
/api/public/v1/charges

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.
Parâmetros
CampoTipoObrigatórioDescrição
amount_centsinteiro
Sim
Valor em centavos. De 1 a 100000000 (R$ 1 milhão).
descriptiontextoNãoAté 140 caracteres.
external_idtextoNãoSeu identificador. Volta nos webhooks. Não deduplica.
Requisição
finance.zendry.co
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"}'
Resposta
200
{  "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"}
Experimentar

Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.

GET
/api/public/v1/charges/:id

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.
Requisição
finance.zendry.co
curl -X GET https://finance.zendry.co/api/public/v1/charges/:id \  -H "Authorization: Bearer gw_sua_chave_aqui"
Resposta
200
{  "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"}
Experimentar

Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.

POST
/api/public/v1/payouts

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.
Parâmetros
CampoTipoObrigatórioDescrição
amount_centsinteiro
Sim
Valor em centavos. De 1 a 100000000.
pix_keytexto
Sim
Chave do favorecido.
pix_key_typecpf | cnpj | email | phone | evpNãoSem este campo, o tipo é inferido do formato da chave.
descriptiontextoNãoAté 140 caracteres.
Requisição
finance.zendry.co
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"}'
Resposta
200
{  "id": "9ac2...",  "status": "enviando",  "amount_cents": 5000,  "fee_cents": 149,  "reserved_cents": 5149,  "idempotent_id": "b7e1...",  "created_at": "2026-01-01T12:00:00Z"}
Experimentar

Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.

Boleto

POST
/api/public/v1/charges/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.
Parâmetros
CampoTipoObrigatórioDescrição
amount_centsinteiro
Sim
Valor em centavos.
vencimentodata (AAAA-MM-DD)
Sim
Vencimento do boleto.
buyerobjeto
Sim
first_name, last_name, email e taxpayer_id (CPF ou CNPJ).
buyer.addressobjetoNãoline1, neighborhood, city, state, postal_code. Opcional inteiro.
descriptiontextoNãoAté 140 caracteres.
external_idtextoNãoSeu identificador.
Requisição
finance.zendry.co
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"    }  }}'
Resposta
200
{  "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"}
Experimentar

Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.

Cartão

Checkout & 3DS — passo a passo

Zendry.js

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.

Como o dado circula
  1. Comprador

    Preenche o cartão na sua página de checkout.

  2. Seu front

    Zendry.js coleta o device (idioma, tela, fuso). O cartão não passa pelo SDK.

  3. Seu backend

    POST /card_payments com cartão, device, ip_address e user_agent.

  4. Zendry

    Autentica com o emissor e responde ao seu servidor.

  5. Emissor

    Decide sem tela (data-only) ou pede desafio ao comprador.

Resposta ao seu servidor

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.

Simulador da página de checkout
simulação

É 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.

1

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.

Na sua página de checkout
<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 }
2

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.

Seu endpoint /checkout/pagar
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 });  }});
3

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.

No navegador
201
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.
4

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.

Recebendo card.approved
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();});
Desfechos
CampoTipoObrigatórioDescrição
aprovada (200)mostre o sucessoNãoWebhook card.approved confirma. Libere o pedido nele.
requires_action (201)chame completeThreeDSNãoApós o desafio chega card.approved ou card.declined. Sem conclusão em 15 min, a venda expira.
recusada (200)ofereça outro cartãoNãofailure_reason traz o motivo. Webhook card.declined.
422corrija a requisiçãoNãoerror_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.
Experimentar

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.

POST
/api/public/v1/card_payments

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.
Parâmetros
CampoTipoObrigatórioDescrição
amount_centsinteiro
Sim
Valor em centavos.
installmentsinteiroNão1 a 12. Padrão 1.
buyerobjeto
Sim
name, email e taxpayer_id (CPF, 11 dígitos).
cardobjeto
Sim
holder_name, number, expiration_month (MM), expiration_year (AAAA) e security_code.
billingobjetoNãoEndereço de cobrança.
deviceobjetoNãoDados 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_addresstextoNãoIP do COMPRADOR. Obrigatório na prática em integração em cadeia.
user_agenttextoNãoUser-agent do COMPRADOR.
external_idtextoNãoSeu identificador.
Requisição
finance.zendry.co
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 ..."}'
Resposta — aprovada ou recusada
200
{  "id": "7d21...",  "charge_id": "1b8e...",  "status": "aprovada",  "amount_cents": 15000,  "installments": 1,  "external_id": "1234",  "created_at": "2026-01-01T12:00:00Z"}
Experimentar

Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.

GET
/api/public/v1/card_payments/:id

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.
Requisição
finance.zendry.co
curl -X GET https://finance.zendry.co/api/public/v1/card_payments/:id \  -H "Authorization: Bearer gw_sua_chave_aqui"
Resposta
200
{  "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"}
Experimentar

Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.

POST
/api/public/v1/card_payments/:id/refund

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.
Requisição
finance.zendry.co
curl -X POST https://finance.zendry.co/api/public/v1/card_payments/:id/refund \  -H "Authorization: Bearer gw_sua_chave_aqui"
Resposta
200
{  "id": "7d21...",  "status": "estornada",  "refunded_at": "2026-01-01T18:22:40Z"}
Experimentar

Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.

Disputas

GET
/api/public/v1/disputes

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).
Parâmetros
CampoTipoObrigatórioDescrição
statusopen | closed | exatoNãoopen = vivas (aberta, em_defesa, em_analise); closed = ganha e perdida; ou um status exato.
limitinteiroNãoDe 1 a 100. Padrão 50.
Requisição
finance.zendry.co
curl -X GET https://finance.zendry.co/api/public/v1/disputes \  -H "Authorization: Bearer gw_sua_chave_aqui"
Resposta
200
{  "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    }  ]}
Experimentar

Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.

GET
/api/public/v1/disputes/:id

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.
Requisição
finance.zendry.co
curl -X GET https://finance.zendry.co/api/public/v1/disputes/:id \  -H "Authorization: Bearer gw_sua_chave_aqui"
Resposta
200
{  "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" }  ]}
Experimentar

Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.

POST
/api/public/v1/disputes/:id/defense

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).
Parâmetros
CampoTipoObrigatórioDescrição
texttexto
Sim
A argumentação da defesa. De 5 a 4000 caracteres.
Requisição
finance.zendry.co
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."}'
Resposta
200
{  "id": "19ba...",  "status": "em_defesa"}
Experimentar

Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.

POST
/api/public/v1/disputes/:id/evidence

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.
Form-data
CampoTipoObrigatórioDescrição
filearquivo
Sim
Até 15MB. PDF e imagens são os formatos usuais.
Requisição
finance.zendry.co
curl -X POST https://finance.zendry.co/api/public/v1/disputes/:id/evidence \  -H "Authorization: Bearer gw_sua_chave_aqui"
Resposta
200
{  "id": "19ba...",  "name": "comprovante-entrega.pdf",  "size_bytes": 182044}
Experimentar

Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.

POST
/api/public/v1/disputes/:id/accept

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).
Requisição
finance.zendry.co
curl -X POST https://finance.zendry.co/api/public/v1/disputes/:id/accept \  -H "Authorization: Bearer gw_sua_chave_aqui"
Resposta
200
{  "id": "19ba...",  "status": "em_analise"}
Experimentar

Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.

Consultas

GET
/api/public/v1/balance

Consultar saldo

Retorna o saldo disponível da conta autenticada.

Requisição
finance.zendry.co
curl -X GET https://finance.zendry.co/api/public/v1/balance \  -H "Authorization: Bearer gw_sua_chave_aqui"
Resposta
200
{  "balance_cents": 84851,  "currency": "BRL"}
Experimentar

Crie sua conta para executar esta chamada no sandbox, com chave de teste e dados isolados.

GET
/api/public/v1/statement

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.
Parâmetros de query
CampoTipoObrigatórioDescrição
fromdata (AAAA-MM-DD)NãoDia inicial, fuso de Brasília.
todata (AAAA-MM-DD)NãoDia final, fuso de Brasília.
typetextoNãoFiltra pelo campo "type" das respostas (ex.: pagamento_confirmado).
Requisição
finance.zendry.co
curl -X GET https://finance.zendry.co/api/public/v1/statement \  -H "Authorization: Bearer gw_sua_chave_aqui"
Resposta
200
{  "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"    }  ]}
Experimentar

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>.

Envelope
{  "event": "pix.received",  "sent_at": "2026-01-01T12:04:12Z",  "data": { ...campos do evento... }}
Exemplos de data
{  "charge_id": "3f1c...",  "external_id": "1234",  "amount_cents": 15000,  "description": "Pedido #1234",  "end_to_end_id": "E1823612...",  "environment": "producao",  "status": "pago"}
Verificando a assinatura (HMAC-SHA256 sobre o corpo bruto)
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

21 eventos
Pix
  • pix.receivedCobrança Pix paga (canal de recebimento)
  • pix.sentSaque Pix concluído ou falhou (canal de saque)
Boleto
  • boleto.paidBoleto compensado
Cartão
  • card.approvedTransação aprovada (inclusive após 3DS)
  • card.declinedTransação recusada
  • card.refundedTransação estornada
Cobrança
  • charge.expiredCobrança expirou sem pagamento
  • charge.canceledCobrança cancelada
Disputas
  • dispute.openedContestação (chargeback/MED) aberta
  • dispute.closedContestação encerrada
Assinaturas
  • 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
Cripto & Internacional
  • 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.
POST
/api/public/v1/charges/:id

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).
Requisição
finance.zendry.co
curl -X POST https://finance.zendry.co/api/public/v1/charges/:id \  -H "Authorization: Bearer gw_sua_chave_aqui"
Resposta
200
{  "id": "3f1c...",  "status": "pago",  "paid_at": "2026-01-01T12:04:11Z"}
Experimentar

Crie sua conta para simular pagamentos no sandbox com sua chave de teste.

Erros & limites

Códigos HTTP

Status
CampoTipoObrigatórioDescrição
401authNãoCredencial ausente, inválida ou revogada.
403acessoNãoIP fora da lista de permitidos, ou conta bloqueada.
404recursoNãoRecurso inexistente ou de outra conta.
409saldoNãoSaldo insuficiente (saques).
422validaçãoNãoPayload inválido ou regra de negócio. Traz details por campo.
422 — payload inválido
422
{  "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.

Formato
422
{  "error": "Conta não habilitada para cartão de crédito",  "error_type": "ACQUIRER_NOT_APPROVED",  "error_code": null,  "retry": false}
error_type
CampoTipoObrigatórioDescrição
INVALID_REQUESTretry: falseNãoDado inválido (número do cartão, CPF etc.).
ACQUIRER_NOT_APPROVEDretry: falseNãoConta ainda não habilitada na adquirente.
PAYMENT_DECLINEDretry: falseNãoRecusa do emissor.
PENDING_CHECKretry: falseNãoTransação em verificação; consulte o GET antes de repetir.
PROVIDER_ERRORretry: variávelNãoFalha na adquirente; retry: true quando reprocessável.
INTERNAL_ERRORretry: trueNãoFalha nossa; tente novamente.

Limites & boas práticas

Valor por operação
R$ 0,01 a R$ 1.000.000,00
1 a 100.000.000 centavos.
Cartão
1 a 12 parcelas
Estorno total em até 24h da aprovação.
Textos
140 caracteres
description e external_id.
Extrato
200 itens por chamada
Pagine por from/to.
  • 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.