Skip to Content

Erros

Toda resposta 4xx/5xx carrega o mesmo envelope JSON:

{ "code": 400, "message": "invalid_input", "details": [ { "field": "items[0].unit_price", "message": "must be a positive decimal string" } ] }
  • code — o status HTTP numérico (400, 401, 404, …). Útil para tratamento genérico na camada HTTP, mas para lógica de ramificação faça switch em message em vez disso — code não vai desambiguar entre, digamos, invalid_input e payment_method_not_supported (ambos 400).
  • message — o nome sentinela em lower-snake-case, derivado da constante interna em errors.go (por exemplo, INVALID_INPUT → "invalid_input"). Estável entre releases — faça switch nisso.
  • details — preenchido em erros de validação. Array de objetos { field, message } para que o cliente possa amarrar erros a inputs. Omitido caso contrário.

No momento a gente não retorna trace_id nem timestamp no body. Rascunhos anteriores desta página prometeram os dois — isso era aspiracional. Se você precisa correlacionar um log do servidor a uma requisição, capture o header Date da resposta e os headers de rate-limit do lado do gateway (X-RateLimit-*) e cite isso em um ticket de suporte.

Rejeições na borda do gateway usam um formato diferente. O envelope acima é o que os serviços de backend emitem. Requisições rejeitadas no gateway antes de chegar a um serviço — um X-Signature ausente/inválido, um X-Client-ID desconhecido ou um X-Timestamp velho numa chamada /b2b/v1/* — voltam como { "error": "...", "message": "..." }, onde error é um slug grosseiro (unauthorized / bad_request / service_unavailable) e message carrega as especificidades. Não há code numérico nem details. Então um verificador deve ramificar pelo status HTTP primeiro, depois ler message, e só tratar code/details como presentes uma vez que a requisição passou pelo gateway. Exemplo de body do gateway (401):

{ "error": "unauthorized", "message": "invalid signature" }

Status HTTP → códigos típicos

HTTPValores típicos de codeO que significa
400INVALID_INPUT, MISSING_REQUIRED, INVALID_FORMAT, INVALID_LENGTH, INVALID_VALUE, INVALID_USER_STATUS, INVALID_USER_ROLE, PAYMENT_METHOD_NOT_SUPPORTED, AMOUNT_BELOW_MINIMUMRequisição ruim — olhe details
401INVALID_CREDENTIALS, INVALID_TOKEN, TOKEN_EXPIRED, INVALID_SIGNATURE (B2B); INVALID_OTP, OTP_EXPIRED, SESSION_EXPIRED (só dashboard / JWT)Auth falhou — chave ruim, timestamp expirado, assinatura errada. Os códigos OTP/SESSION_EXPIRED só aparecem em rotas de dashboard-JWT (/payment/v1/*); integrações B2B puras não vão ver.
402INSUFFICIENT_CREDITSaldo pré-pago do lojista acabou — recarregue antes de retentar
403FORBIDDEN, IP_BLOCKEDChave é válida, mas não tem o escopo/permissão de IP para esta chamada
404NOT_FOUND, RECORD_NOT_FOUND, USER_NOT_FOUND, SESSION_NOT_FOUNDRecurso não existe (ou não existe para este lojista)
409ALREADY_EXISTS, USER_ALREADY_EXISTS, SESSION_ALREADY_EXISTSReplay de idempotência com body diferente, ou a máquina de estados recusou a transição
429TOO_MANY_REQUESTS, TOO_MANY_ATTEMPTSRate limit batido; backoff e retente
500INTERNAL_SERVER_ERROR, DATABASE_CONNECTION_ERROR, DATABASE_QUERY_ERROR, DATABASE_TRANSACTION_ERROR, REDIS_CONNECTION_ERROR, REDIS_OPERATION_ERROR, EXTERNAL_SERVICE_ERRORErro nosso; seguro retentar com backoff. (Códigos específicos de provedor como TWILIO_SERVICE_ERROR / SENDGRID_SERVICE_ERROR existem internamente mas só aparecem em fluxos de notificação do lado do dashboard, não em endpoints B2B.)
503SERVICE_UNAVAILABLEUma dependência downstream caiu. Retente com backoff

Referência completa de códigos

O conjunto completo de valores de code que você pode ver (bate com payment-service/pkg/errors/errors.go):

Auth e sessões (401)

  • INVALID_CREDENTIALS — combinação de usuário/senha ou chave de API rejeitada
  • INVALID_TOKEN — JWT/session token não-parseável ou adulterado
  • TOKEN_EXPIRED — JWT além de exp
  • INVALID_OTP — OTP não combina
  • OTP_EXPIRED — OTP emitido há mais que a janela de tolerância
  • SESSION_EXPIRED — sessão do dashboard expirou
  • INVALID_SIGNATURE — assinatura HMAC não bate em chamadas B2B / webhook

Autorização (403)

  • FORBIDDEN — autenticado mas a fronteira de role/escopo/lojista bloqueia a ação
  • IP_BLOCKED — IP está na lista de abuso

Não encontrado (404)

  • NOT_FOUND — genérico
  • RECORD_NOT_FOUND — linha ausente para o ID dado
  • USER_NOT_FOUND — busca de usuário falhou
  • SESSION_NOT_FOUND — ID de sessão do dashboard não reconhecido

Conflito (409)

  • ALREADY_EXISTS — genérico
  • USER_ALREADY_EXISTS — cadastro bateu numa constraint de unicidade
  • SESSION_ALREADY_EXISTS — insert de sessão duplicada

Validação (400)

  • INVALID_INPUT — genérico; cheque details
  • MISSING_REQUIRED — um campo obrigatório estava ausente
  • INVALID_FORMAT — valor não combinou com o formato esperado (por exemplo, UUID, URL, e-mail)
  • INVALID_LENGTH — valor muito curto ou muito longo
  • INVALID_VALUE — valor fora do enum/range permitido
  • INVALID_USER_STATUS — usuário está num estado que não permite a ação
  • INVALID_USER_ROLE — role não tem permissão para a ação
  • PAYMENT_METHOD_NOT_SUPPORTED — combinação provider/ativo não está habilitada para o lojista
  • AMOUNT_BELOW_MINIMUM — valor do pedido abaixo do piso por rede ou por ambiente. A resposta traz details.floor_usd com o piso configurado (em USD), então você pode exibi-lo diretamente; o texto de message também o informa.
  • INSUFFICIENT_BALANCE — a carteira do comprador não tem saldo suficiente do ativo de pagamento para cobrir a transferência.
  • INSUFFICIENT_GAS — a carteira do comprador não tem gas nativo suficiente para transmitir a transferência.

Pagamento / faturamento (402)

  • INSUFFICIENT_CREDIT — saldo pré-pago do lojista não consegue cobrir o gas / taxa de plataforma. Recarregue pelo dashboard, depois retente

Rate limiting (429)

  • TOO_MANY_REQUESTS — rate-limit de IP no gateway
  • TOO_MANY_ATTEMPTS — tentativas falhadas repetidas no mesmo recurso (por exemplo, OTP) dispararam um throttle

Armazenamento / infraestrutura (500)

  • DATABASE_CONNECTION_ERROR — não conseguiu alcançar o banco
  • DATABASE_QUERY_ERROR — plano de query falhou em runtime
  • DATABASE_TRANSACTION_ERROR — commit/rollback falhou
  • REDIS_CONNECTION_ERROR — não conseguiu alcançar o Redis
  • REDIS_OPERATION_ERROR — comando Redis falhou
  • EXTERNAL_SERVICE_ERROR — falha genérica de terceiro (provedor não categorizado abaixo)
  • TWILIO_SERVICE_ERROR — chamada de SMS / Verify do Twilio falhou
  • SENDGRID_SERVICE_ERROR — envio de e-mail do SendGrid falhou
  • INTERNAL_SERVER_ERROR — fall-through inesperado; capture o header Date da resposta + X-RateLimit-* e fale com a gente

Disponibilidade (503)

  • SERVICE_UNAVAILABLE — um downstream crítico está reportando como não saudável; backoff + retry

Erros de validação (400)

Quando o problema é um body de requisição malformado, details é um array para que você possa mapear erros de volta para campos:

{ "code": 400, "message": "invalid_input", "details": [ { "field": "items[0].unit_price", "message": "must be a positive decimal string" }, { "field": "success_url", "message": "must be a valid https URL" } ] }

Erros de auth (401)

INVALID_SIGNATURE cobre três modos distintos de falha — a única forma de distinguir é por bissecção:

  • Chave secreta errada (re-cheque a env var)
  • Defasagem de timestamp > 5 min (sincronize NTP)
  • String canônica errada (mais comum: esqueceu os separadores \n ou assinou um body parseado-e-re-stringificado que difere do que você mandou)

Veja Autenticação para o algoritmo exato de assinatura.

Rate limits (429)

SuperfícieLimite
Toda rota de gateway (incl. /b2b/v1/*)Por IP no gateway — 500 req/min, bucket compartilhado. Não por lojista.
Checkout público (/checkout/*)Sub-bucket mais estrito — 20 req/min por IP

X-RateLimit-Limit e X-RateLimit-Remaining são emitidos em toda resposta com rate-limit do gateway (não só nas de sucesso). Trate como o orçamento vivo para o seu IP.

Respostas com rate-limit incluem um header Retry-After (segundos até a janela resetar) — honre-o. Como fallback, backoff com jitter — 1s base + exponencial até 30s.

Rate limits estão sujeitos a mudança. Se você está batendo neles legitimamente (por exemplo, reconciliando uma faixa histórica grande), fale com a gente — endpoints de bulk estão no roadmap.

Crédito insuficiente (402)

INSUFFICIENT_CREDIT (HTTP 402 Payment Required) significa que o saldo pré-pago do lojista não consegue cobrir o próximo gas-e-taxa-de-plataforma para a operação que você tentou — tipicamente liquidar um pagamento cripto ou executar uma ação on-chain patrocinada por gas. Recarregue pelo dashboard do lojista (Billing → Add credit), depois retente a operação; trabalho em voo espera e segue automaticamente quando o saldo libera.

Erros de servidor (5xx)

Um 500 significa que a gente não conseguiu processar a requisição. Retente com backoff — a sua idempotency_key garante que você não vai cobrar em dobro se a requisição original parcialmente teve sucesso.

Se as retentativas não recuperam em um minuto, exiba um genérico “pagamento temporariamente indisponível” para o comprador e fale com o suporte com o endpoint que falhou, o ID do seu lojista, o header Date da resposta e os valores X-RateLimit-*, e o horário aproximado da requisição — isso é o suficiente para a gente girar para a requisição relevante nos nossos logs.

Erros de entrega de webhook

Entregas de webhook são um canal de falha separado — não aparecem como erros de API porque não é o seu servidor que está chamando. Quando uma entrega retorna não-2xx (ou dá timeout), ela é enfileirada para retry com backoff exponencial em 0s, 1min, 5min, 15min, 1h, 6h (seis tentativas no total — mesmo cronograma de Webhooks → Visão geral). O histórico completo por entrega aparece em Developers → Webhooks → [endpoint] → Delivery log no dashboard. Depois da sexta tentativa a entrega vai para dead-letter — o dashboard mostra um badge “Failed” que você pode replayar manualmente assim que o seu servidor estiver saudável.