Índice

API MagicPay

Esta é a referência da API v1 do MagicPay para integrar pagamentos PIX ao seu checkout. Com ela você cria cobranças, mostra o QR code (ou manda o cliente para a nossa página de pagamento), acompanha o status e recebe webhooks assinados quando o pagamento é confirmado.

Visão geral

Item Valor
URL base https://api.amagicpay.tech/api/v1
Formato JSON (UTF-8) na requisição e na resposta
Autenticação Authorization: Bearer <sua chave de API>
Método de pagamento PIX
Valores inteiros em centavos (9990 = R$ 99,90)
Moeda BRL
Datas ISO 8601 em UTC (2026-09-15T18:30:00.000Z)
Ids cobrança ch_ + 24 caracteres; conta mer_ + 16; entrega de webhook whd_ + 16

Regras gerais:

  • Toda requisição com corpo usa Content-Type: application/json.
  • Toda resposta é JSON e vem com Cache-Control: no-store.
  • Campos desconhecidos no corpo são ignorados.
  • Nunca use float para dinheiro. amount, fee e net_amount são sempre centavos inteiros.
  • Chamadas à API são de servidor para servidor. Nunca coloque a chave de API em aplicativo, página web ou qualquer código que rode no dispositivo do cliente.

Fluxo típico de integração

  1. O cliente fecha o pedido na sua loja.
  2. Seu servidor chama POST /api/v1/charges com o valor, os dados do cliente e um Idempotency-Key.
  3. Você mostra o PIX: o pix.qr_code (copia e cola) e a imagem pix.qr_code_image_url no seu checkout, ou redireciona o cliente para payment_url.
  4. O cliente paga no app do banco.
  5. O MagicPay confirma o pagamento na adquirente e envia o webhook charge.paid para a sua URL, assinado com o seu segredo.
  6. Seu servidor valida a assinatura, confere o valor e libera o pedido.

Autenticação

A chave de API é gerada no painel do MagicPay, em Integração. Ela aparece uma única vez ao ser gerada: guarde num cofre de segredos ou numa variável de ambiente do seu servidor. Guardamos só um hash, então não conseguimos mostrá-la de novo. Se perder a chave ou suspeitar de vazamento, use Rotacionar: a chave antiga para de funcionar na hora.

O formato é mp_live_ seguido de 40 letras e números (ou mp_test_ numa chave de teste). Envie em toda requisição:

curl https://api.amagicpay.tech/api/v1/me \
  -H "Authorization: Bearer mp_live_SUA_CHAVE_AQUI"

A chave só funciona quando:

  • a sua conta foi aprovada (cadastro e KYC concluídos e analisados);
  • a conta não está suspensa;
  • o gateway MagicPay está ativo.

Quando alguma dessas condições falha, a API responde 401 com um código que diz o motivo:

HTTP code Quando
401 unauthorized Cabeçalho Authorization ausente, chave em formato inválido, chave inexistente ou rotacionada, conta desativada
401 merchant_not_approved Conta ainda em cadastro, em análise ou rejeitada
401 merchant_suspended Conta suspensa. Fale com o suporte
401 gateway_suspended O gateway MagicPay está suspenso. Fale com o suporte

Cobranças

Criar uma cobrança

POST /api/v1/charges

Cria uma cobrança PIX e já devolve o QR code. Responde 201 quando cria e 200 quando é uma repetição com a mesma Idempotency-Key (veja Idempotência).

Cabeçalhos

Cabeçalho Obrigatório Descrição
Authorization sim Bearer <chave>
Content-Type sim application/json
Idempotency-Key recomendado Até 128 caracteres. Use um valor único por tentativa de pagamento, por exemplo o id do pedido

Corpo

Campo Tipo Obrigatório Regras
amount inteiro sim Centavos. Mínimo 100 (R$ 1,00), máximo 50000000 (R$ 500.000,00)
customer objeto sim Dados do pagador. A adquirente exige todos os campos abaixo
customer.name texto sim 2 a 120 caracteres
customer.document texto sim CPF (11 dígitos) ou CNPJ (14 dígitos), com dígito verificador válido. A pontuação é removida (123.456.789-09 vira 12345678909)
customer.email texto sim E-mail válido, até 160 caracteres. Gravado em minúsculo
customer.phone texto sim DDD + número, 10 ou 11 dígitos. A pontuação é removida
description texto não Até 120 caracteres. Aparece na página de pagamento
external_id texto não Até 80 caracteres. O id do pedido no seu sistema. Não precisa ser único: serve para busca
expires_in inteiro não Segundos até expirar. Mínimo 300 (5 min), máximo 259200 (72 h). Padrão 3600 (1 h)
metadata objeto não Pares livres, até 2 KB em JSON. Devolvido como veio
success_url texto não URL https:// para onde a página de pagamento leva o cliente depois de pagar. Se omitida, usa a URL de sucesso configurada no painel (se houver)

Exemplo

curl -X POST https://api.amagicpay.tech/api/v1/charges \
  -H "Authorization: Bearer mp_live_SUA_CHAVE_AQUI" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-123" \
  -d '{
    "amount": 9990,
    "description": "Chapinha Portátil",
    "external_id": "pedido-123",
    "customer": {
      "name": "Maria Silva",
      "document": "12345678909",
      "email": "maria@exemplo.com",
      "phone": "84999999999"
    },
    "expires_in": 3600,
    "metadata": { "utm_source": "meta" },
    "success_url": "https://sualoja.com/obrigado"
  }'

Resposta 201 Created:

{
  "id": "ch_7k3m9q2x4v6b8n1p5r0t2w4y",
  "status": "pending",
  "amount": 9990,
  "currency": "BRL",
  "description": "Chapinha Portátil",
  "external_id": "pedido-123",
  "acquirer": "podpay",
  "customer": {
    "name": "Maria Silva",
    "document": "12345678909",
    "document_type": "cpf",
    "email": "maria@exemplo.com",
    "phone": "84999999999"
  },
  "pix": {
    "qr_code": "00020126580014br.gov.bcb.pix...",
    "qr_code_image_url": "https://pay.amagicpay.tech/pay/ch_7k3m9q2x4v6b8n1p5r0t2w4y/qr",
    "expires_at": "2026-09-15T19:30:00.000Z"
  },
  "payment_url": "https://pay.amagicpay.tech/pay/ch_7k3m9q2x4v6b8n1p5r0t2w4y",
  "fee": 599,
  "net_amount": 9391,
  "metadata": { "utm_source": "meta" },
  "paid_at": null,
  "expires_at": "2026-09-15T19:30:00.000Z",
  "created_at": "2026-09-15T18:30:00.000Z",
  "updated_at": "2026-09-15T18:30:01.000Z"
}

Se a adquirente falhar

A cobrança é gravada antes de ir para a adquirente. Se a adquirente recusar ou não responder, a cobrança vira failed, a API responde 502 provider_error e, se você tiver webhook configurado, recebe charge.failed. A cobrança com falha continua visível no painel e em GET /api/v1/charges?external_id=....

Para tentar de novo depois de um 502, use uma nova Idempotency-Key. Repetir a mesma chave devolve a cobrança que falhou (200, status: "failed"), sem nova tentativa na adquirente.

Erros possíveis

HTTP code Motivo
400 validation_error Corpo que não é JSON, campo inválido ou ausente, Idempotency-Key vazia ou maior que 128 caracteres
401 unauthorized, merchant_not_approved, merchant_suspended, gateway_suspended Veja Autenticação
409 idempotency_conflict Idempotency-Key já usada com outro corpo
429 rate_limited Mais de 60 requisições por minuto com a mesma chave
500 internal_error Erro inesperado do nosso lado
502 provider_error A adquirente falhou ao criar o PIX
503 no_acquirer O gateway está sem adquirente ativa. Fale com o suporte

O objeto cobrança

É o mesmo formato em todas as respostas de cobrança e dentro de data nos webhooks.

Campo Tipo Descrição
id texto Id da cobrança (ch_...)
status texto pending, paid, expired, refunded ou failed. Veja Status da cobrança
amount inteiro Valor em centavos
currency texto Sempre BRL
description texto ou null Descrição enviada
external_id texto ou null Id do pedido no seu sistema
acquirer texto Adquirente que processa esta cobrança (podpay, bpx, ou mock em ambiente de teste). Informativo
customer objeto name, document (só dígitos), document_type (cpf ou cnpj), email, phone (só dígitos)
pix.qr_code texto ou null BR Code PIX (copia e cola). null quando a cobrança falhou antes de chegar na adquirente
pix.qr_code_image_url texto PNG do QR code (320 px), público
pix.expires_at texto Igual a expires_at
payment_url texto Página de pagamento pronta, pública
fee inteiro ou null Taxa do MagicPay sobre esta cobrança, em centavos, calculada pelo seu plano
net_amount inteiro ou null amount - fee. Não desconta a reserva de segurança nem a taxa de saque
metadata objeto O que você enviou ({} se nada)
paid_at texto ou null Quando o pagamento foi confirmado
expires_at texto Prazo para pagar
created_at texto Criação
updated_at texto Última alteração

No exemplo acima, com um plano de 4,99% + R$ 1,00: fee = 100 + arredondamento(9990 × 4,99%) = 100 + 499 = 599 e net_amount = 9990 - 599 = 9391.

Consultar uma cobrança

GET /api/v1/charges/{id}

curl https://api.amagicpay.tech/api/v1/charges/ch_7k3m9q2x4v6b8n1p5r0t2w4y \
  -H "Authorization: Bearer mp_live_SUA_CHAVE_AQUI"

Responde 200 com o objeto cobrança. Responde 404 not_found se o id não existir, estiver malformado ou for de outra conta (nunca revelamos cobranças de terceiros).

Listar cobranças

GET /api/v1/charges

Lista as cobranças da sua conta, das mais novas para as mais antigas.

Parâmetro Tipo Descrição
status texto Filtra por pending, paid, expired, refunded ou failed
external_id texto Filtra pelo id do pedido (igualdade exata, até 80 caracteres)
limit inteiro 1 a 100. Padrão 20
starting_after texto Id de uma cobrança sua (ch_...). Devolve as cobranças que vêm depois dela na ordenação
curl "https://api.amagicpay.tech/api/v1/charges?status=paid&limit=50" \
  -H "Authorization: Bearer mp_live_SUA_CHAVE_AQUI"

Resposta (cada item de data é um objeto cobrança completo):

{
  "data": [
    { "id": "ch_7k3m9q2x4v6b8n1p5r0t2w4y", "status": "paid", "amount": 9990 }
  ],
  "has_more": true
}

Para paginar, repita a chamada com starting_after igual ao id do último item de data enquanto has_more for true:

curl "https://api.amagicpay.tech/api/v1/charges?status=paid&limit=50&starting_after=ch_7k3m9q2x4v6b8n1p5r0t2w4y" \
  -H "Authorization: Bearer mp_live_SUA_CHAVE_AQUI"

Um starting_after que não seja uma cobrança da sua conta responde 400 validation_error.

Sincronizar com a adquirente

POST /api/v1/charges/{id}/sync

Consulta a adquirente agora e aplica o resultado (por exemplo, marca como paga). Útil para um botão "Já paguei" no seu checkout ou para conferir um pedido sem esperar o webhook. Não precisa de corpo.

curl -X POST https://api.amagicpay.tech/api/v1/charges/ch_7k3m9q2x4v6b8n1p5r0t2w4y/sync \
  -H "Authorization: Bearer mp_live_SUA_CHAVE_AQUI"
  • Responde 200 com o objeto cobrança já atualizado.
  • Cada cobrança consulta a adquirente no máximo uma vez a cada 5 segundos. Chamadas dentro desse intervalo devolvem o estado já gravado, sem nova consulta.
  • Uma cobrança que falhou antes de chegar na adquirente volta como está.
  • 404 not_found se a cobrança não for sua; 502 provider_error se a adquirente não responder.

Não use o sync em laço para descobrir pagamentos: o webhook chega sozinho e as cobranças pendentes também são conferidas periodicamente do nosso lado.

Conta

GET /api/v1/me

Confirma que a chave funciona e mostra para qual ambiente vão as suas cobranças novas.

curl https://api.amagicpay.tech/api/v1/me \
  -H "Authorization: Bearer mp_live_SUA_CHAVE_AQUI"
{
  "merchant": {
    "id": "mer_4f8k2m6q9t3v7x1z",
    "name": "Sua Loja",
    "webhook_url": "https://sualoja.com/webhooks/pix",
    "env": "production"
  },
  "acquirer": { "kind": "podpay", "env": "production" }
}

env é production ou sandbox. Quando o gateway está sem adquirente ativa, acquirer e merchant.env vêm null e a criação de cobranças responde 503 no_acquirer.

Idempotência

Rede cai e timeouts acontecem. Para não criar dois PIX para o mesmo pedido, envie o cabeçalho Idempotency-Key em POST /api/v1/charges.

Situação Resposta
Chave nova 201, cobrança criada
Mesma chave e mesmo corpo 200 com a mesma cobrança, no estado atual (pode já estar paid), e o cabeçalho Idempotent-Replayed: true
Mesma chave e corpo diferente 409 idempotency_conflict

Detalhes que importam:

  • A chave vale por conta e não expira. Use algo único por tentativa de pagamento, como pedido-123 ou um UUID guardado junto do pedido.
  • A comparação do corpo é feita depois da validação e da normalização: a ordem das chaves do JSON não importa, e "123.456.789-09" equivale a "12345678909". Mudar expires_in ou metadata conta como corpo diferente.
  • Se duas requisições com a mesma chave chegarem ao mesmo tempo, só uma cria a cobrança. A outra recebe 200 com a mesma cobrança, que pode ainda estar sem pix.qr_code. Nesse caso, consulte GET /api/v1/charges/{id} em seguida.
  • Depois de um 502 provider_error, a cobrança fica failed e a chave fica ligada a ela. Para tentar de novo, use outra chave (por exemplo pedido-123-2).
  • Sem o cabeçalho, cada chamada cria uma cobrança nova.

Status da cobrança

Status Significado Final?
pending Aguardando pagamento não
paid Pagamento confirmado na adquirente quase: só pode virar refunded
expired O prazo (expires_at) passou sem pagamento confirmado não: ainda pode virar paid
refunded Pagamento estornado sim
failed A adquirente recusou ou falhou ao criar o PIX sim

Transições possíveis e o webhook de cada uma:

De Para Webhook
pending paid charge.paid
pending expired charge.expired
pending failed charge.failed
expired paid charge.paid
paid refunded charge.refunded

Pontos de atenção:

  • Pagamento depois de expirar existe. Um PIX pode ser pago no limite do prazo e a confirmação chegar depois. Nesse caso a cobrança vai de expired para paid e você recebe charge.paid depois de charge.expired. Decida no seu sistema o que fazer (entregar o pedido ou pedir a devolução ao suporte).
  • A expiração é processada em segundo plano: uma cobrança pode continuar pending por alguns segundos depois de expires_at.
  • Só marcamos paid depois de confirmar o pagamento diretamente na adquirente. Um aviso de pagamento não confirmado nunca muda o status.
  • A API v1 não tem cancelamento nem estorno. Cobranças não pagas simplesmente expiram. Estornos são feitos pelo suporte e geram charge.refunded.

Página de pagamento

Toda cobrança tem uma página pública pronta em payment_url (https://pay.amagicpay.tech/pay/{id}). Você pode redirecionar o cliente para ela ou mostrar o PIX dentro do seu próprio checkout.

A página:

  • mostra o nome da sua loja, o valor, a descrição, o QR code, o copia e cola com botão "Copiar" e a contagem regressiva até expires_at;
  • mostra do cliente apenas o primeiro nome;
  • se atualiza sozinha a cada 3 segundos e exibe "Pagamento confirmado" quando a cobrança vira paid;
  • se a cobrança tiver success_url, leva o cliente para ela 3 segundos depois da confirmação;
  • avisa com clareza quando a cobrança expirou ou falhou;
  • funciona bem no celular.

Para montar o PIX no seu checkout, use pix.qr_code (texto do copia e cola) e pix.qr_code_image_url (PNG que pode ir direto numa tag de imagem).

O success_url é só uma conveniência de navegação. Nunca libere um pedido porque o cliente chegou na success_url: qualquer pessoa pode abrir essa URL. Libere pelo webhook charge.paid validado ou pela consulta GET /api/v1/charges/{id}.

Webhooks

Os webhooks avisam o seu servidor quando uma cobrança muda de status, sem você precisar consultar a API.

Configuração

No painel, em Integração:

  1. Cadastre a URL de webhook (use https://).
  2. Gere o segredo de webhook. Ele aparece uma única vez: guarde no seu servidor. Sem segredo configurado, as entregas falham.
  3. Use Enviar webhook de teste para conferir se o seu endpoint recebe e valida a assinatura.

As entregas vão para a URL configurada no momento em que o evento acontece.

Eventos

Evento Quando
charge.paid Pagamento confirmado na adquirente (inclusive depois de expirada)
charge.expired O prazo passou sem pagamento
charge.failed A adquirente recusou ou falhou ao criar o PIX
charge.refunded Pagamento estornado
test Disparado por você no painel. Responda 2xx e não aplique efeito em pedidos

Cada evento é enviado no máximo uma vez por cobrança. As retentativas reenviam a mesma entrega, com o mesmo id.

Formato

POST para a sua URL, com este corpo:

{
  "id": "whd_9c2h5k8n3r6t1w4y",
  "event": "charge.paid",
  "created_at": "2026-09-15T18:34:12.000Z",
  "data": {
    "id": "ch_7k3m9q2x4v6b8n1p5r0t2w4y",
    "status": "paid",
    "amount": 9990,
    "currency": "BRL",
    "description": "Chapinha Portátil",
    "external_id": "pedido-123",
    "acquirer": "podpay",
    "customer": {
      "name": "Maria Silva",
      "document": "12345678909",
      "document_type": "cpf",
      "email": "maria@exemplo.com",
      "phone": "84999999999"
    },
    "pix": {
      "qr_code": "00020126580014br.gov.bcb.pix...",
      "qr_code_image_url": "https://pay.amagicpay.tech/pay/ch_7k3m9q2x4v6b8n1p5r0t2w4y/qr",
      "expires_at": "2026-09-15T19:30:00.000Z"
    },
    "payment_url": "https://pay.amagicpay.tech/pay/ch_7k3m9q2x4v6b8n1p5r0t2w4y",
    "fee": 599,
    "net_amount": 9391,
    "metadata": { "utm_source": "meta" },
    "paid_at": "2026-09-15T18:34:10.000Z",
    "expires_at": "2026-09-15T19:30:00.000Z",
    "created_at": "2026-09-15T18:30:00.000Z",
    "updated_at": "2026-09-15T18:34:12.000Z"
  }
}
Campo Descrição
id Id da entrega (whd_...). Igual em todas as retentativas: use para não processar duas vezes
event Nome do evento
created_at Quando o evento foi gerado
data O objeto cobrança no momento do evento

Cabeçalhos

Cabeçalho Valor
Content-Type application/json
User-Agent MagicPay-Webhooks/1.0
X-MagicPay-Event Nome do evento, igual a event do corpo
X-MagicPay-Delivery-Id Id da entrega, igual a id do corpo
X-MagicPay-Timestamp Momento do envio desta tentativa, em segundos Unix
X-MagicPay-Signature v1=<assinatura em hex>

Os nomes dos cabeçalhos são técnicos e iguais para todos os gateways da plataforma.

Assinatura

A assinatura prova que a requisição veio do MagicPay e que o corpo não foi alterado:

assinatura = HMAC-SHA256(segredo, "<X-MagicPay-Timestamp>.<corpo bruto>"), em hex minúsculo
X-MagicPay-Signature: v1=<assinatura>
  • segredo: o segredo de webhook exatamente como aparece no painel (64 caracteres hex), usado como texto. Não converta de hex para bytes.
  • corpo bruto: o corpo exatamente como foi recebido. Valide antes de fazer o parse do JSON; reserializar o objeto muda espaços e ordem e quebra a assinatura.
  • Compare em tempo constante (crypto.timingSafeEqual), nunca com ===.
  • Rejeite timestamps com mais de 5 minutos de diferença do seu relógio (proteção contra reenvio de requisições capturadas). Mantenha o relógio do servidor sincronizado (NTP).
  • Cada tentativa é assinada na hora do envio, com timestamp novo, então as retentativas também passam na janela de 5 minutos.
  • O cabeçalho pode trazer mais de uma assinatura separada por vírgula (v1=abc,v1=def), por exemplo durante uma troca de segredo. Aceite se qualquer uma conferir.

Verificação em Node.js

Exemplo com Express, sem outras dependências:

import crypto from "node:crypto";
import express from "express";

const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; // segredo de webhook do painel
const TOLERANCE_SECONDS = 5 * 60;

export function verifyWebhook(rawBody, timestampHeader, signatureHeader, secret = WEBHOOK_SECRET) {
  if (!secret || !timestampHeader || !signatureHeader) return false;

  const timestamp = Number.parseInt(timestampHeader, 10);
  if (!Number.isFinite(timestamp)) return false;

  const nowSeconds = Math.floor(Date.now() / 1000);
  if (Math.abs(nowSeconds - timestamp) > TOLERANCE_SECONDS) return false;

  const digest = crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
  const expected = Buffer.from(`v1=${digest}`, "utf8");

  return signatureHeader
    .split(",")
    .map((part) => part.trim())
    .some((candidate) => {
      const received = Buffer.from(candidate, "utf8");
      return received.length === expected.length && crypto.timingSafeEqual(received, expected);
    });
}

const app = express();

// express.raw mantém o corpo como Buffer: a assinatura é conferida sobre o corpo exato.
app.post("/webhooks/pix", express.raw({ type: "application/json", limit: "1mb" }), async (req, res) => {
  const rawBody = req.body.toString("utf8");
  const ok = verifyWebhook(rawBody, req.get("X-MagicPay-Timestamp"), req.get("X-MagicPay-Signature"));
  if (!ok) return res.status(400).json({ error: "assinatura inválida" });

  const delivery = JSON.parse(rawBody);

  // Responda rápido e processe de forma idempotente pelo id da entrega.
  res.status(200).json({ received: true });

  if (await jaProcessado(delivery.id)) return;
  if (delivery.event === "charge.paid") {
    await liberarPedido(delivery.data.external_id, delivery.data.amount);
  }
  await marcarProcessado(delivery.id);
});

app.listen(3000);

jaProcessado, marcarProcessado e liberarPedido são funções do seu sistema. Em produção, prefira gravar a entrega numa fila ou tabela antes de responder e processar depois.

Com Next.js (App Router), leia o corpo com await request.text(), passe esse texto para verifyWebhook e só então faça JSON.parse.

Entrega e retentativas

  • Consideramos entregue qualquer resposta 2xx recebida em até 10 segundos. O conteúdo da resposta é ignorado.
  • Redirecionamentos (3xx) não são seguidos e contam como falha. Cadastre a URL final.
  • Qualquer outra resposta, timeout ou erro de conexão gera nova tentativa, até 10 tentativas no total:
Tentativa Quando
Logo após o evento (em geral em segundos)
1 minuto após a falha anterior
5 minutos depois
15 minutos depois
1 hora depois
3 horas depois
6 horas depois
12 horas depois
24 horas depois
10ª 24 horas depois

Depois da 10ª falha (cerca de 2 dias e 22 horas após a primeira), a entrega é marcada como falha definitiva. O suporte pode reenviá-la pelo painel, com o mesmo id.

Boas práticas:

  • Responda 2xx rápido e processe em segundo plano.
  • Seja idempotente: guarde o id da entrega (ou o par cobrança + evento) e ignore repetições.
  • Não dependa da ordem de chegada. Um charge.paid pode chegar depois de um charge.expired. Use data.status e, na dúvida, consulte GET /api/v1/charges/{id}.
  • Confira o valor: compare data.amount e data.external_id com o seu pedido antes de liberar.
  • Mesmo com webhooks, tenha uma rotina que consulta pela API os pedidos pendentes antigos, para o caso de o seu endpoint ficar fora do ar por muito tempo.

Erros

Toda resposta de erro tem este formato:

{
  "error": {
    "code": "validation_error",
    "message": "Dados inválidos.",
    "details": [
      { "path": "customer.document", "message": "document inválido (dígito verificador não confere)" },
      { "path": "amount", "message": "amount precisa ser no mínimo 100 (R$ 1,00)" }
    ]
  }
}
  • code é estável: use no seu código.
  • message é em português e pode mudar: use só para exibir ou registrar.
  • details aparece em erros de validação, com o caminho do campo em path.
HTTP code Significado O que fazer
400 validation_error Corpo não é JSON, campo ausente ou inválido, parâmetro de listagem inválido, Idempotency-Key inválida Corrija a requisição. Não repita igual
401 unauthorized Chave ausente, malformada, inexistente ou rotacionada, ou conta desativada Confira o cabeçalho e a chave no painel
401 merchant_not_approved Conta ainda não aprovada Conclua o cadastro e aguarde a análise
401 merchant_suspended Conta suspensa Fale com o suporte
401 gateway_suspended Gateway suspenso Fale com o suporte
404 not_found Cobrança inexistente ou de outra conta, ou rota inexistente Confira o id e o caminho
409 idempotency_conflict Idempotency-Key já usada com outro corpo Use outra chave ou reenvie o corpo original
429 rate_limited Limite de requisições por minuto excedido Espere os segundos indicados no cabeçalho Retry-After
500 internal_error Erro inesperado Tente de novo com a mesma Idempotency-Key; se persistir, fale com o suporte
502 provider_error Falha ao falar com a adquirente Na criação, tente de novo com uma nova Idempotency-Key. No sync, tente mais tarde
503 no_acquirer Gateway sem adquirente ativa Fale com o suporte

Um método HTTP não suportado numa rota existente (por exemplo DELETE /api/v1/charges) responde 405 sem corpo.

Limites

Limite Valor
Requisições por chave de API 60 por minuto (janela deslizante). Acima disso, 429 com Retry-After em segundos
Sync por cobrança 1 consulta à adquirente a cada 5 segundos
amount 100 a 50.000.000 centavos (R$ 1,00 a R$ 500.000,00)
expires_in 300 a 259.200 segundos (5 minutos a 72 horas); padrão 3.600
description 120 caracteres
external_id 80 caracteres
customer.name 2 a 120 caracteres
customer.email 160 caracteres
metadata 2 KB em JSON
success_url https://, até 2.048 caracteres
Idempotency-Key 128 caracteres
limit na listagem 1 a 100; padrão 20
Corpo da requisição 1 MB
Tempo de resposta do seu webhook 10 segundos
Tentativas de entrega de webhook 10

Checklist antes de ir para produção

  • A chave de API está só no servidor, em variável de ambiente ou cofre de segredos.
  • GET /api/v1/me mostra "env": "production".
  • Toda criação de cobrança envia Idempotency-Key.
  • O endpoint de webhook valida a assinatura sobre o corpo bruto, com timingSafeEqual e tolerância de 5 minutos.
  • O processamento do webhook é idempotente pelo id da entrega.
  • O pedido só é liberado com charge.paid validado (ou consulta à API), nunca pela success_url.
  • O sistema trata charge.paid recebido depois de charge.expired.
  • O valor e o external_id são conferidos antes de liberar o pedido.
  • Respostas 429 respeitam o Retry-After e respostas 5xx são registradas.