Pular para o conteúdo
MOONIPdocs
IntegraçãoManual
PTEN
Ir para o painel

Erros

Todo erro da Moonip é um objeto JSON com formato estável — programe seu handler contra ele uma vez e pare de adivinhar. Toda resposta, de sucesso ou de erro, também traz o cabeçalho X-Request-Id (gerado quando você não manda um, devolvido igual quando você manda).

#Formato

#Criação com resultado ainda desconhecido

409 idempotency_outcome_unknown significa que a criação original pode ter chegado ao provedor, mas o resultado ainda não foi confirmado. Repita com a mesma Idempotency-Key e o mesmo corpo, aumentando o intervalo entre tentativas. Não gere outra chave para essa cobrança: isso poderia criar outro pagamento.

A operação original permanece reservada mesmo após cinco minutos ou 24 horas. A repetição recupera o resultado salvo quando disponível; caso contrário, retorna esse erro sem criar outra cobrança. Uma consulta sem resultado não prova que a cobrança não foi criada. Se a situação persistir, contate o suporte com o request_id. Respostas concluídas ficam disponíveis por pelo menos 24 horas.

#Estrutura da resposta

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_request",
    "message": "Validation failed",
    "details": [
      { "param": "amount", "message": "must be a positive integer" }
    ]
  },
  "request_id": "req_01J..."
}
CampoSempre presenteDescrição
error.typesimCategoria estável — uma de invalid_request_error, authentication_error, permission_error, not_found_error, conflict_error, rate_limit_error, api_error.
error.codesimCódigo de máquina estável, em snake_case. É nele que seu handler deve ramificar, não na message.
error.messagesimExplicação para humano. Pode logar; não é contrato para automação.
error.paramquando exatamente um campo falhaIdentifica esse campo. Com vários campos falhando, ele está ausente e só o error.details os lista.
error.detailsem falha de validaçãoLista { param, message }[] para erro em vários campos.
request_idsimCite ao falar com o suporte — ele rastreia todo log, span e mensagem Kafka. Também vem no cabeçalho X-Request-Id.

Por que code e não message: a mensagem é escrita para gente ler e muda sem aviso — inclusive de idioma. O code é contrato. Um if (e.message === "Invalid API key") quebra na primeira vez que alguém melhorar a frase.

Apelidos de compatibilidade: o corpo da resposta também traz alguns campos planos no nível superior — statusCode, message, requestId e (quando existem) code / fieldErrors — ao lado do objeto error, para clientes (incluindo o painel) que os leem direto. Leia o objeto error aninhado; os apelidos planos não têm garantia de sobreviver a uma versão major futura.

#Mapa de status

HTTPerror.typeO que o cliente deve fazer
200——
201—Guarde o id devolvido e siga.
400invalid_request_errorCorrija o corpo/cabeçalhos e tente de novo. Não repita às cegas.
401authentication_errorConfira se o cabeçalho Authorization: Bearer leva uma chave correta e ativa.
403permission_errorBloqueado por escopo, pela guarda antifraude/velocidade, por uma tentativa de reembolso com chave de API (marcador de mensagem REFUND_ADMIN_ONLY) ou pela allowlist de IP. Revise a requisição ou fale com o suporte.
404not_found_errorConfira o id que você mandou.
409conflict_errorMesma Idempotency-Key com corpo diferente (idempotency_key_reused), primeira tentativa ainda em processamento (idempotency_key_in_flight) ou estado não cancelável (payment_not_cancellable).
422invalid_request_errorViolação de regra de negócio — leia a message, corrija a entrada, não repita.
429rate_limit_errorReduza o ritmo. Repita com atraso exponencial + jitter; respeite o Retry-After quando vier.
5xxapi_errorSeguro repetir com a mesma Idempotency-Key — a idempotência é o que te protege de duplicar.

#Erros comuns

#400 Bad Request

Validação falhou — o error.code é invalid_request e o error.details lista cada campo que falhou (error.param só vem quando exatamente um campo falha).

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_request",
    "message": "Validation failed",
    "details": [
      { "param": "amount", "message": "must be a positive integer" },
      { "param": "payment_method", "message": "Invalid enum value" }
    ]
  },
  "request_id": "req_01J..."
}

Também dá 400 quando falta o cabeçalho Idempotency-Key numa escrita financeira. Em POST /v1/charges e POST /v1/pix/charges o cabeçalho é o único canal. Em POST /payments o campo idempotencyKey no corpo é aceito como apelido — se ambos forem enviados, precisam coincidir (divergência é 400) e o valor do cabeçalho é o que vale. POST /payments/{id}/refund é exclusivo do painel administrativo, então nunca recebe nenhuma das duas formas de uma chave de API.

Ação Corrija a requisição e tente de novo. Nunca em laço.


#401 Unauthorized

json
{
  "error": {
    "type": "authentication_error",
    "code": "unauthorized",
    "message": "Invalid API key"
  },
  "request_id": "req_01J..."
}

Causas possíveis:

  • Cabeçalho ausente — confira se o Authorization: Bearer ak_live_... está sendo enviado.
  • A chave foi girada — veja no painel.
  • Espaço em branco — a chave é comparada exatamente, sem espaço na frente ou atrás.

Ação Verifique a chave. Se ela estiver certa, pode ter sido desativada — fale com o suporte.


#403 Forbidden

A guarda de fraude/velocidade barra a cobrança antes de ela chegar no provedor — o error.code começa com fraud.:

json
{
  "error": {
    "type": "permission_error",
    "code": "fraud.customer_velocity_exceeded",
    "message": "Too many payment attempts for this customer in a short window"
  },
  "request_id": "req_01J..."
}

Existe um segundo código fraud.* para o limite de velocidade da própria conta.

Ação Revise o cliente ou reduza o ritmo das tentativas. Só cobranças vivas contam no limite — cobranças expiradas ou canceladas (ex.: um QR Pix que venceu sem ser pago) são ignoradas, então tentar de novo nesses casos é seguro. Se o bloqueio parecer errado, escreva para suporte@moonip.io com o request_id.

#Reembolsos — marcador de mensagem REFUND_ADMIN_ONLY

POST /payments/{id}/refund recusa toda chamada com chave de API com 403, seja qual for o corpo: reembolsos são emitidos apenas no painel administrativo da Moonip. Isso não é um valor de code — o error.code é o "forbidden" comum e o token sobrevive só como prefixo da mensagem, então ramifique pelo código para a recusa e combine o prefixo apenas como marcador (ex.: message.startsWith("REFUND_ADMIN_ONLY")):

json
{
  "error": {
    "type": "permission_error",
    "code": "forbidden",
    "message": "REFUND_ADMIN_ONLY: reembolsos devem ser solicitados pelo painel/admin, ..."
  },
  "request_id": "req_01J..."
}

Veja Referência da API — Reembolsos.

#Allowlist de IP

Quando a conta tem uma allowlist de IP configurada, uma requisição vinda de um endereço fora dela é recusada com 403 — o mesmo envelope permission_error, com error.code "forbidden". A checagem é uma guarda por conta nas rotas centrais da conta (carteiras, saques, transações, casos MED, api-keys e vizinhas); as rotas de movimentação de dinheiro dos serviços de pagamento e webhooks (/v1/charges, /v1/pix/charges, /v1/payments, /v1/webhooks) não são cobertas por ela. Coloque na allowlist todo endereço de saída das suas integrações (incluindo CI e hosts de failover), ou a requisição nem chega à checagem da chave. Detalhes e como atualizar a lista: Chaves de API.

#account_suspended

A conta está suspensa ou bloqueada: as requisições com chave de API a cobranças, pagamentos, saques, carteira e transações são recusadas até o suporte liberar a conta. A chave continua válida — trocar de chave não resolve.

json
{
  "error": {
    "type": "permission_error",
    "code": "account_suspended",
    "message": "Conta suspensa: a API não aceita requisições desta conta. Fale com o suporte."
  },
  "request_id": "req_01J..."
}

Ação Não tente de novo. Escreva para suporte@moonip.io com o request_id.


#404 Not Found

json
{
  "error": {
    "type": "not_found_error",
    "code": "not_found",
    "message": "Charge ch_a1b2c3d4-0000-0000-0000-000000000009 not found"
  },
  "request_id": "req_01J..."
}

Ação Confira o id. Se você acabou de criar o recurso, espere 1 a 2 segundos e tente de novo.


#409 Conflict

json
{
  "error": {
    "type": "conflict_error",
    "code": "idempotency_key_reused",
    "message": "Idempotency key 'ORD-7821' already used with a different request body"
  },
  "request_id": "req_01J..."
}

Você reenviou uma Idempotency-Key com corpo diferente do original. A Moonip recusa sobrescrever em silêncio.

Dois outros códigos de conflict_error caem em 409:

error.codeQuandoAção
idempotency_key_reusedMesma chave, corpo diferente.Use uma chave nova — causa comum é reaproveitar o id do pedido depois que o cliente mexeu no carrinho.
idempotency_key_in_flightA primeira requisição com essa chave ainda está em processamento.Espere ela terminar (ou repita depois de alguns segundos) — não troque de chave.
payment_not_cancellablePOST /payments/{id}/cancel numa cobrança em PROCESSING.Espere a cobrança assentar e então reembolse pelo painel administrativo.

#Recusa por regra de negócio

Falha de formato (campo faltando ou inválido) é 400 com error.type: "invalid_request_error". Algumas falhas de configuração mais abaixo — por exemplo, nenhum provedor configurado para o payment_method e currency pedidos — vêm repassadas do orquestrador interno com o status dele e um código de máquina em UPPER_SNAKE_CASE, normalmente 422:

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "NO_PROVIDER_CONFIGURED",
    "message": "No payment provider is configured for this payment method and currency."
  },
  "request_id": "req_01J..."
}

Códigos dessa família do lado de cobrança: PROVIDER_UNAVAILABLE, PROVIDER_ERROR, PAYMENT_REJECTED, NO_PROVIDER_CONFIGURED, UNKNOWN_METHOD, MOCK_PROVIDER_IN_PRODUCTION, PAYER_IDENTITY_REQUIRED (quando expected_payer_tax_id trava a cobrança e o documento do pagador não bate).

Códigos do lado de saque/withdrawal: WALLET_NOT_FOUND, WALLET_NOT_SPENDABLE, AMOUNT_BELOW_MINIMUM, AMOUNT_ABOVE_MAXIMUM, DAILY_PAYOUT_LIMIT_EXCEEDED (422), INSUFFICIENT_BALANCE, WITHDRAWAL_NOT_CANCELLABLE. Note a exceção no caminho de saque: INSUFFICIENT_BALANCE chega como 400, não 422 — ramifique por error.code, não pelo status.

Ação Leia o error.code e a message. Não repita — corrija a entrada, ou fale com o suporte se a conta deveria ter provedor configurado.


#429 Too Many Requests

json
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limited",
    "message": "Rate limit exceeded"
  },
  "request_id": "req_01J..."
}

Cabeçalhos nessa resposta (veja os limites atuais em Primeiros passos):

text
Retry-After: 12
X-RateLimit-Limit-Minute: 5000
X-RateLimit-Remaining-Minute: 0

Ação Reduza o ritmo — respeite o Retry-After quando vier, senão backoff exponencial com jitter.

js
async function comRetry(fn, tentativas = 5) {
  for (let i = 0; i < tentativas; i++) {
    try { return await fn(); }
    catch (err) {
      if (err.status !== 429 && err.status < 500) throw err;
      const base = Math.min(1000 * 2 ** i, 30_000);
      const jitter = Math.random() * 0.3 * base;
      await new Promise(r => setTimeout(r, base + jitter));
    }
  }
  throw new Error('tentativas esgotadas');
}

#5xx — do lado do servidor

json
{
  "error": {
    "type": "api_error",
    "code": "service_unavailable",
    "message": "Upstream unavailable"
  },
  "request_id": "req_01J..."
}

Seguro repetir. Use a mesma Idempotency-Key, para que uma cobrança duplicada nunca seja criada caso o processamento tenha dado certo e só a resposta tenha se perdido.

#Erros de entrega de webhook (servidor → seu endpoint)

Quando a Moonip não consegue alcançar o seu endpoint, a falha aparece em GET /v1/webhooks/deliveries:

json
{
  "id": "b2c3d4e5-f6a7-4890-9abc-def012345678",
  "status": "FAILED",
  "attempts": 3,
  "maxAttempts": 15,
  "lastStatusCode": 502,
  "lastError": "HTTP 502",
  "lastResponseBody": "Bad Gateway",
  "nextRetryAt": "2026-07-23T16:08:00.000Z"
}
lastStatusCode / lastErrorO que significa
2xxEntregue. Não retenta.
4xxSeu endpoint recusou o payload. A Moonip ainda retenta até maxAttempts — corrija e reenvie se precisar.
5xxSeu endpoint está fora. A Moonip retenta com backoff exponencial.
429 / Retry-AfterA Moonip respeita o seu Retry-After e reagenda.
Connection timeout / ETIMEDOUTSeu endpoint levou mais de 30s. Responda 200 OK rápido e processe em fila.
getaddrinfo ENOTFOUNDSeu domínio não resolve. Atualize a URL registrada.
self signed certificateErro de TLS. A Moonip exige certificado público válido.

Depois de maxAttempts (padrão 15), a entrega vai para a DLQ e pode ser reenviada manualmente. Veja Webhooks para a semântica completa.

#Erros de gestão de webhook

Códigos nas rotas de administração de /v1/webhooks (permission_error / conflict_error / rate_limit_error conforme o código):

error.codeQuando
WEBHOOK_ENDPOINT_DUPLICATEA URL já está registrada nesta conta (409).
WEBHOOK_ENDPOINT_LIMITA conta atingiu o limite de endpoints ativos.
WEBHOOK_HTTPS_REQUIREDUma URL que não é https:// fora do sandbox/staging.
WEBHOOK_OWN_DOMAINA URL aponta para um domínio da Moonip.
WEBHOOK_TEST_RATE_LIMITChamadas demais a POST /endpoints/{id}/test em uma hora (429).
WEBHOOK_ROLE_REQUIREDO papel de quem chama não pode gerenciar endpoints (access.canManage: false).
IMPERSONATION_VIEW_ONLYA sessão está impersonando e pode só ler.
TENANT_NOT_ACTIVEA conta não está ativa.

#Lendo o error.details

O error.details é uma lista plana de { param, message } — renderizável direto:

jsx
{error.details.map(({ param, message }) => (
  <li key={param}>{param}: {message}</li>
))}

O apelido fieldErrors é a mesma informação reformatada como { field: reasons[] }:

json
{
  "fieldErrors": {
    "amount": ["must be a positive integer"]
  }
}

#Quando escrever para o suporte

Fale com suporte@moonip.io se:

  • Um 5xx persistir por mais de 5 minutos
  • Uma entrega de webhook ficar presa em FAILED depois do maxAttempts
  • Um 403 Forbidden aparecer sem explicação
  • A mesma Idempotency-Key devolver cobranças diferentes em chamadas diferentes (isso é bug — não pode acontecer)

Sempre cite o request_id — ele nos deixa rastrear todo log, span e mensagem Kafka numa consulta só.

#Limite de consultas por entidade

HTTP 429, error.type: rate_limit_error, error.code: entity_rate_limited. Aguarde os segundos de Retry-After antes de consultar a mesma entidade novamente. Veja Consultas periódicas e limites.