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

Referência da API

Catálogo completo de todos os endpoints públicos da Moonip v1. Todos os caminhos são relativos à URL base de produção — https://api.moonip.io/v1. (Um host de sandbox dedicado ainda não está disponível.)

Autentique toda requisição com sua chave de API no cabeçalho Authorization: Bearer <chave> — Authorization: Bearer <MOONIP_API_KEY> (ak_test_*/ak_live_*). O cabeçalho legado apikey: <chave> continua aceito por compatibilidade. Todos os corpos são JSON. Todos os valores são inteiros na menor unidade da moeda.

A API é organizada em torno de alguns recursos:

  • Cobranças (/v1/charges) — criar e consultar cobranças Pix: object: "charge", ids ch_…, cabeçalho Idempotency-Key, bloco pix. É por aqui que toda integração começa.
  • Carteiras (/v1/wallets) — o saldo da sua conta.
  • Operações de pagamento (/v1/payments) — cancelamento, estatísticas, comprovantes e outras operações sobre uma cobrança, endereçadas pelo id cru (sem o prefixo ch_). Reembolsos são feitos no painel administrativo, não por esta API.
  • Webhooks (/v1/webhooks) — registrar endpoints e inspecionar entregas.

#Cobranças

/v1/charges.

Contrato público das convenções da API §3. Toda resposta é montada por um serializador com allowlist — nenhum id interno, nome de PSP, custo ou payload cru de provedor pode aparecer aqui.

#Criar cobrança

POST /v1/charges — atalho Pix-first: POST /v1/pix/charges fixa payment_method: "pix" no corpo e devolve exatamente a mesma Charge.

Cabeçalhos

CabeçalhoObrigatórioNotas
AuthorizationsimBearer + sua chave de API (ak_test_* / ak_live_*). O cabeçalho legado apikey continua aceito.
Idempotency-KeysimQualquer string não vazia, com escopo (lojista, chave). É uma escrita financeira — sem ele, é 400.

Corpo

CampoTipoObrigatórioDescrição
amountinteirosimMenor unidade da moeda, > 0.
currencystringnãoPadrão "BRL". Cobranças Pix devem usar BRL.
payment_methodstringsim"pix" — o único valor aceito hoje.
descriptionstringnãoAté 500 caracteres; dobrado em metadata.description na leitura.
expires_ininteironãoValidade do Pix em segundos, 60–604800 (7 dias). Fora dessa faixa a API responde 400 — o valor nunca é cortado.
customer.namestringnão
customer.documentstringnãoCPF ou CNPJ.
customer.emailstringnão
customer.phonestringnão
expected_payer_tax_idstringnãoPrende a cobrança a um pagador: CPF (11) ou CNPJ (14) dígitos com checksum válido. Se você também enviar customer.document, os dois precisam coincidir; se o definir, customer.name passa a ser obrigatório.
metadataobjetonãoLivre; chaves reservadas/prefixadas com underscore são removidas.

Exemplo

bash
curl -X POST https://api.moonip.io/v1/pix/charges \
  -H "Authorization: Bearer $MOONIP_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1000,
    "currency": "BRL",
    "description": "Pedido #12345",
    "customer": { "name": "Maria Silva", "document": "12345678901" }
  }'

Resposta 201 Created

json
{
  "id": "ch_a1b2c3d4-0000-0000-0000-000000000009",
  "object": "charge",
  "amount": 1000,
  "currency": "BRL",
  "status": "pending",
  "payment_method": "pix",
  "customer": { "name": "Maria Silva", "document": "12345678901" },
  "pix": {
    "br_code": "000201BRCODEPIX",
    "qr_code_url": "https://qr.example/img.png",
    "expires_at": "2026-07-23T15:00:00.000Z"
  },
  "checkout_url": "https://checkout.moonip.io/a1b2c3d4-0000-0000-0000-000000000009",
  "settlement": {},
  "metadata": { "order_id": "12345" },
  "created_at": "2026-07-23T14:30:00.000Z"
}

pix.txid (quando o provedor vinculou um) e settlement.end_to_end_id (após paid e a liquidação) são dados Pix contextuais — veja PIX. charge.id nunca é igual a charge.pix.txid.

Reenviar o mesmo Idempotency-Key com o mesmo corpo devolve o 201 original e define o cabeçalho de resposta Idempotency-Replayed: true.

checkout_url é o checkout hospedado da Moonip para a cobrança — redirecione o pagador para lá em vez de renderizar sua própria tela de Pix. Ele vem na criação e no GET /v1/charges/{id}, e não pode ser derivado de charge.id (o caminho do checkout remove o prefixo ch_).

Vocabulário público de status: pending, processing, paid, failed, expired, cancelled, refunded, disputed.


#Consultar cobrança

GET /v1/charges/{id}

Aceita o id com prefixo ch_… ou o id interno cru. Com escopo na conta de quem chama — uma cobrança de outra conta responde 404.

Resposta 200 OK — o mesmo formato Charge da criação.


#Listar cobranças

GET /v1/charges

GET /v1/charges lista as cobranças da conta com paginação por cursor ({ object: "list", data, has_more, next_cursor }) — a ordenação é createdAt desc, estável, sem page/offset nesta rota.

http
GET /v1/charges?limit=25&starting_after=ch_01J…&status=paid
ParamTipoPadrãoDescrição
limitinteiro251–100.
starting_afterstring—Um id de cobrança (ch_…) do mesmo recurso — cursor, não offset.
statusenum—Vocabulário público de status (pending, paid, …).
created_afterISO 8601—Limite inferior inclusivo de created_at.
created_beforeISO 8601—Limite superior inclusivo de created_at.
customer_idstring—Filtra por documento do pagador.
json
{
  "object": "list",
  "data": [],
  "has_more": false,
  "next_cursor": null
}

page/offset nunca são aceitos nesta lista com cursor — só starting_after. Quando has_more for true, passe next_cursor como starting_after para buscar a próxima página.

Para uma visão paginada e filtrável das mesmas transações, veja GET /v1/payments.


#Carteiras

/v1/wallets. Os saldos da sua conta. Somente leitura, com escopo na sua chave de API — as rotas de escrita em /v1/wallets/* são exclusivas do painel e respondem 403 para uma chave de API.

#Consultar saldo

GET /v1/wallets/balance

O parâmetro de consulta currency é opcional. Sem ele (a forma recomendada) a resposta traz todas as moedas da conta em balances[], mais os campos da moeda principal (BRL por padrão) no topo:

bash
curl "https://api.moonip.io/v1/wallets/balance" \
  -H "Authorization: Bearer $MOONIP_API_KEY"

Resposta 200 OK

json
{
  "available": 880,
  "pending": 0,
  "total": 880,
  "retained": 0,
  "currency": "BRL",
  "next_release_at": null,
  "next_release_amount": null,
  "balances": [
    { "currency": "BRL", "available": 880, "held": 0, "pending": 0, "total": 880 }
  ],
  "primary": { "currency": "BRL", "available": 880, "held": 0, "pending": 0, "total": 880 }
}
CampoDescrição
availableSaldo gasto/retirável da moeda principal, menor unidade (centavos para BRL). 880 = R$ 8,80. Com sinal — fica negativo quando a conta está em dívida.
heldValor em reserva (retenção em trânsito) nas entradas de balances[]/primary. Não existe no nível superior.
pendingLiquidado mas ainda retido — indisponível por enquanto (moeda principal). Inclui reservas de reembolso em trânsito, por isso pode ser maior que retained.
totalavailable + held + pending da moeda principal.
retainedTotal sob retenção (reservas PENDING) da moeda principal. Não inclui reservas de reembolso.
currencyMoeda principal (ISO 4217). Padrão BRL.
next_release_atString ISO 8601 de quando a próxima parcela de pending libera, ou null quando nada está agendado.
next_release_amountValor dessa próxima liberação (menor unidade), ou null.
balances[]Uma entrada por moeda: { currency, available, held, pending, total }.
primaryA moeda principal (mesmos campos de uma entrada de balances[]).

Os campos do nível superior (available/pending/total/currency) são da moeda principal — mantidos por compatibilidade. Para contas multimoeda, percorra balances[].

Como os números são calculados (eles não são intercambiáveis):

  • balances[] e primary contam apenas carteiras OPERATIONAL ativas, somadas por moeda: available = Σ(balance − heldBalance), held = Σ heldBalance, pending = Σ pendingBalance, total = available + held + pending.
  • Esta forma nunca retorna 404: sem carteira nenhuma você recebe zeros e balances: [].
  • Um saque individual sai de uma carteira, e o formato plano com ?currency= lê só a carteira padrão da moeda — então, para uma moeda com várias carteiras, /balance, ?currency= e a checagem do saque podem mostrar três números available diferentes.

#Moeda única (formato legado)

GET /v1/wallets/balance?currency=BRL

Passe currency para receber o formato plano de moeda única:

bash
curl "https://api.moonip.io/v1/wallets/balance?currency=BRL" \
  -H "Authorization: Bearer $MOONIP_API_KEY"
json
{
  "available": 880,
  "held": 0,
  "pending": 0,
  "currency": "BRL",
  "next_release_at": null,
  "next_release_amount": null
}

currency não diferencia maiúsculas de minúsculas. Este formato lê a carteira padrão da conta para aquela moeda apenas (não é uma soma), e uma moeda desconhecida devolve zeros em vez de erro. Aqui não existem total/retained.

Autenticação e erros. Sempre envie a chave de API. Chave ausente ou inválida → 401 (nunca 404). Sem currency esta rota nunca retorna 404; com ?currency=, um 404 not_found significa que a conta não tem carteira nenhuma (não é problema de roteamento). Um caminho errado (/v1/wallets/balance, no plural, com o prefixo /v1) ou um intermediário que não encaminha /v1/wallets/* nunca chega à Moonip e aparece como erro próprio.

#Listar carteiras

GET /v1/wallets

Todas as carteiras da conta — qualquer walletType, ativas e inativas, podendo haver várias por moeda — mesma autenticação. Um array JSON puro, sem paginação.

bash
curl https://api.moonip.io/v1/wallets \
  -H "Authorization: Bearer $MOONIP_API_KEY"

Resposta 200 OK

json
[
  {
    "id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
    "currency": "BRL",
    "label": "Operating wallet",
    "walletType": "OPERATIONAL",
    "isDefault": true,
    "isActive": true,
    "balance": 880,
    "pendingBalance": 0,
    "createdAt": "2026-04-25T15:42:11.000Z"
  }
]

balance/pendingBalance são na menor unidade da moeda. Eles não são os mesmos valores que GET /v1/wallets/balance apresenta como available/pending: balance já inclui o valor em reserva (available = balance − heldBalance numa carteira OPERATIONAL ativa), pendingBalance coincide com pending apenas no caso de carteira única, e esta lista não traz heldBalance — então available não é calculável a partir dela.

#Consultar carteira

GET /v1/wallets/{id}

Uma carteira pelo id (UUID). De qualquer tipo, ativa ou não, com escopo na conta de quem chama.

bash
curl https://api.moonip.io/v1/wallets/a1b2c3d4-e5f6-4789-9abc-def012345678 \
  -H "Authorization: Bearer $MOONIP_API_KEY"

Resposta 200 OK

json
{
  "id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
  "currency": "BRL",
  "walletType": "OPERATIONAL",
  "label": "Operating wallet",
  "isDefault": true,
  "balance": 880,
  "pendingBalance": 0,
  "heldBalance": 0,
  "isActive": true,
  "createdAt": "2026-04-25T15:42:11.000Z",
  "updatedAt": "2026-07-23T14:31:00.000Z"
}

Um id desconhecido, ou de outra conta, responde 404 not_found.


#Operações de pagamento

/v1/payments. Estas rotas operam sobre a mesma cobrança criada via /v1/charges, endereçada pelo id cru (sem o prefixo ch_). Elas cobrem operações que a superfície /v1/charges ainda não expõe — cancelamento, estatísticas, comprovantes, reconciliação — além de criar/ler/listar no formato de transação. Para criar uma cobrança, prefira Cobranças.

Reembolsos não fazem parte da API do lojista: POST /payments/{id}/refund responde 403 para toda requisição com chave de API (error.code "forbidden", mensagem com o prefixo do marcador REFUND_ADMIN_ONLY). Reembolsos são feitos no painel administrativo da Moonip — veja Reembolsos abaixo.

#Criar pagamento

POST /payments

Cabeçalhos

CabeçalhoObrigatórioNotas
AuthorizationsimBearer + sua chave de API.
Idempotency-KeysimCanal preferencial. O campo idempotencyKey no corpo é aceito como apelido — se ambos forem enviados, precisam coincidir, e o valor do cabeçalho é o que vale.

Corpo

CampoTipoObrigatórioDescrição
amountinteirosimMenor unidade da moeda, > 0.
currencystring (3–8)nãoPadrão "BRL". Exemplos: BRL, EUR, USD, USDT.
paymentMethodsstring[]exceto com checkoutUm ou mais dos 11 métodos: PIX, CRYPTO, CREDIT_CARD, BOLETO, MBWAY, MULTIBANCO, IDEAL, BANCONTACT, SEPA, BIZUM, SWAP_NOW. Com checkout é opcional e restringe os métodos do template.
checkoutstringnãoSlug do template de checkout hospedado (1–80 caracteres). Slug desconhecido → 404.
customerIdstringnãoO id interno do seu usuário. Só é encaminhado com a requisição — não é persistido, não aparece em respostas ou webhooks do lojista.
customerNamestringnão
customerDocumentstringnãoCPF, CNPJ, NIF ou outro id fiscal.
customerEmailstringnãoPrecisa ser um endereço de e-mail válido.
customerPhonestringnão
customerobjetonãoAninhado { name?, document?, email?, phone? }. Achata nos campos customer*; em conflito, os campos planos ganham.
expectedPayerDocumentstringnãoTrava de CPF/CNPJ do pagador, só dígitos.
itemsobjeto[]não{ name, quantity, unitPrice, totalPrice } — todos inteiros > 0 exceto name.
metadataobjetonãoLivre. As chaves returnUrl/cancelUrl dentro dele precisam ser URLs http(s), mas as URLs de retorno são os campos de primeira classe abaixo.
webhookUrlstringnãoSobrescrita da URL de webhook por transação.
countrystringnão2–3 caracteres.
riskTierenumnãoLOW, MEDIUM ou HIGH.
environmentenumnãoSANDBOX, STAGING ou PRODUCTION (padrão PRODUCTION). A chave, sozinha, decide para onde a cobrança vai de fato.
returnUrlstringnãoPara onde o pagador volta depois de pagar: URL http(s) sem fragmento.
cancelUrlstringnãoPara onde o pagador vai ao cancelar: mesma regra de URL. Exige returnUrl.
expiresIninteironãoValidade do Pix em segundos, 60–604800.
idempotencyKeystringsimApelido no corpo do cabeçalho Idempotency-Key. Único por requisição pretendida; retentativas devolvem a original.

Resposta 201 Created

json
{
  "id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
  "status": "WAITING_PAYMENT",
  "amount": 24900,
  "currency": "BRL",
  "paymentMethods": ["PIX"],
  "customerName": "Maria Silva",
  "customerDocument": "12345678909",
  "customerEmail": "maria@example.com",
  "expectedPayerDocument": "12345678909",
  "actualPayerDocument": null,
  "payerValidationStatus": "PENDING",
  "metadata": { "orderId": "ORD-7821" },
  "checkoutUrl": "https://checkout.moonip.io/c/a1b2c3d4-e5f6-4789-9abc-def012345678",
  "createdAt": "2026-04-25T15:42:11.000Z"
}

Os campos específicos de cada método (pixQrCode, mbEntity, boletoLine, cardRedirectUrl, …) já vêm na resposta de criação quando o método os produz de forma síncrona — BR Code/QR do Pix, entidade/referência do Multibanco, boleto e redirecionamento de cartão voltam todos no 201. Só os artefatos que chegam mesmo depois precisam de um GET /payments/{id} seguinte.

checkoutUrl está presente na criação e na consulta, e ausente nas linhas de listagem e na resposta do cancelamento. O grupo de validação do pagador é expectedPayerDocument (o que você travou), actualPayerDocument (o que o provedor informou) e payerValidationStatus (NOT_REQUIRED, PENDING, MATCHED, MISMATCH, UNVERIFIED).


#Consultar pagamento

GET /payments/{id}

Resposta 200 OK

json
{
  "id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
  "status": "PAID",
  "amount": 24900,
  "currency": "BRL",
  "paymentMethods": ["PIX"],
  "paidWith": "PIX",
  "pixQrCode": "data:image/png;base64,iVBOR...",
  "pixCopyPaste": "00020126580014br.gov.bcb.pix...",
  "pixExpiresAt": "2026-04-25T16:12:11.000Z",
  "cardRedirectUrl": null,
  "mbEntity": null,
  "mbReference": null,
  "mbExpiresAt": null,
  "boletoBarcode": null,
  "boletoLine": null,
  "boletoPdfUrl": null,
  "boletoExpiresAt": null,
  "cryptoAddress": null,
  "cryptoAmount": null,
  "cryptoNetwork": null,
  "cryptoCurrency": null,
  "platformFee": 200,
  "expectedPayerDocument": "12345678909",
  "actualPayerDocument": "12345678909",
  "payerValidationStatus": "MATCHED",
  "metadata": { "orderId": "ORD-7821" },
  "checkoutUrl": "https://checkout.moonip.io/c/a1b2c3d4-e5f6-4789-9abc-def012345678",
  "createdAt": "2026-04-25T15:42:11.000Z",
  "paidAt": "2026-04-25T15:43:08.000Z"
}

Os campos são null quando não se aplicam aos paymentMethods escolhidos.

Nenhuma resposta de /v1/payments (criação, consulta, listagem, cancelamento) diz qual adquirente atendeu a cobrança: provider, providerAccountId, providerTransactionId, providerFeeSource, cardClientSecret e tenantId não são retornados (removidos em 2026-10-12), e tampouco o custo do adquirente (providerFee, netAmount). Um redirecionamento de cartão ou banco vem sempre como o link /c/{id} do checkout.


#Listar pagamentos

GET /payments

Parâmetros de consulta

ParamTipoPadrãoDescrição
pageint1
limitint20Máximo 100.
statusenum—Veja Enum de status.
currencystring—Filtra por moeda.
startDateISO 8601—Limite inferior inclusivo de createdAt.
endDateISO 8601—Limite superior inclusivo de createdAt.
sourceenum—API, PANEL, MANUAL ou INDEFINIDO (linhas sem origem).
sortBystringcreatedAtNome de uma coluna da transação (createdAt, amount, status, paidAt, updatedAt, …). Nome desconhecido é 500, não 400 — fique com colunas reais.
sortOrderenumdescasc ou desc.

Resposta 200 OK

json
{
  "data": [ { "id": "a1b2c3d4-e5f6-4789-9abc-def012345678", "...": "..." } ],
  "total": 142,
  "page": 1,
  "limit": 20
}

As linhas de listagem não trazem checkoutUrl. Este é um envelope paginado (total/page/limit) — a paginação por cursor está em GET /v1/charges.


#Estatísticas

GET /payments/stats?days=7

ParamTipoPadrãoDescrição
daysint7Dias úteis no timeZone, com o dia de hoje incluído.
from/toISO 8601—Janela explícita; prevalece sobre days.
timeZonestringAmerica/Sao_PauloNome de fuso IANA. Nome inválido → 400.

days=7 = hoje mais os seis dias de calendário anteriores. Os totais (totalTransactions, paidTransactions, successRate, volumeByCurrency, methodBreakdown) cobrem a janela inteira, seja o tamanho que for. dailyVolume tem um ponto por dia de calendário e no máximo 366 pontos: para uma janela maior, guarda os 366 dias mais recentes. hourlyVolume só aparece quando a janela é de um único dia — um ponto por hora comercial, com a mesma chave do dailyVolume mais a hora ("YYYY-MM-DDTHH"). methodBreakdownByCurrency tem uma linha plana por par (método, moeda). Cobranças de sandbox/teste ficam de fora.

Resposta 200 OK

json
{
  "totalTransactions": 142,
  "paidTransactions": 119,
  "todayTransactions": 8,
  "successRate": 84,
  "volumeByCurrency": [
    { "currency": "BRL", "volume": 1245000, "count": 95 },
    { "currency": "EUR", "volume": 89400,   "count": 24 }
  ],
  "todayVolumeByCurrency": { "BRL": 24900, "EUR": 8990 },
  "dailyVolume": [
    { "date": "2026-04-19", "currencies": { "BRL": 89000 } },
    { "date": "2026-04-20", "currencies": { "BRL": 124500, "EUR": 4500 } }
  ],
  "hourlyVolume": [
    { "date": "2026-04-20T09", "currencies": { "BRL": 24900 } }
  ],
  "methodBreakdown": [
    { "method": "PIX",         "volume": 980000, "count": 78 },
    { "method": "CREDIT_CARD", "volume": 265000, "count": 34 },
    { "method": "MULTIBANCO",  "volume": 89400,  "count": 7 }
  ],
  "methodBreakdownByCurrency": [
    { "method": "PIX",        "currency": "BRL", "volume": 980000, "count": 78 },
    { "method": "CREDIT_CARD", "currency": "BRL", "volume": 265000, "count": 34 },
    { "method": "MULTIBANCO", "currency": "EUR", "volume": 89400,  "count": 7 }
  ],
  "statusCounts": { "PAID": 119, "REFUSED": 10 },
  "avgPaySeconds": 42
}

#Métricas

GET /payments/metrics?days=7&currency=BRL

Métricas de funil de conversão e volume da sua conta de lojista. days tem padrão 7 (mesma regra de janela das Estatísticas, sem o teto de 366 pontos); currency é opcional (filtra para uma moeda); timeZone funciona como nas Estatísticas. Com escopo na sua chave de API.

Resposta 200 OK

json
{
  "days": 7,
  "currency": "BRL",
  "overall": { "created": 142, "paid": 119, "conversionRate": 83.8 },
  "byMethod": [
    { "method": "PIX", "created": 90, "paid": 78, "conversionRate": 86.7 }
  ],
  "refunds": { "count": 2, "volumeByCurrency": { "BRL": 4500 } },
  "chargebacks": { "count": 0 }
}

#Volume líquido

GET /payments/net-volume

Volume líquido liquidado (amount − platformFee, só PAID) de uma janela.

ParamTipoPadrãoDescrição
from/toISO 8601—Limites da janela.
timeZonestringAmerica/Sao_PauloNome de fuso IANA.

Resposta 200 OK

json
{
  "from": "2026-04-19T00:00:00.000Z",
  "to": "2026-04-25T23:59:59.999Z",
  "byCurrency": {
    "BRL": { "netVolume": 1220500, "txCount": 95, "grossVolume": 1245000, "feeTotal": 24500 }
  }
}

#Conversão

GET /payments/conversion

Linhas de { currency, status, count, volume } para o funil por status.

ParamTipoPadrãoDescrição
fromISO 8601—Limite inferior inclusivo. Valor inválido → 400.

Resposta 200 OK

json
[
  { "currency": "BRL", "status": "PAID", "count": 95, "volume": 1245000 },
  { "currency": "BRL", "status": "REFUSED", "count": 4, "volume": 12000 }
]

#Reembolsos (apenas no painel)

POST /payments/{id}/refund

Não é chamável com chave de API. Toda requisição do lojista é recusada com 403, seja qual for o corpo: o error.code é "forbidden" e a mensagem começa com o marcador REFUND_ADMIN_ONLY (prefixo de mensagem, não um valor de code — veja Erros). Reembolsos (total ou parcial, Pix ou cartão) são solicitados no painel administrativo da Moonip, que primeiro reserva o valor na carteira e então executa o reembolso. O status de um reembolso pode ser acompanhado pelo lojista em GET /payments/{id}/refunds e no webhook charge.refunded.

O corpo documentado para o chamador interno — idempotencyKey, amount, reason, strategy (devolution/cashout), passFeeToTenant, destinationKey, refundId — não é contrato do lojista; não construa contra ele.


#Listar reembolsos

GET /payments/{id}/refunds

Devolve todos os reembolsos emitidos contra uma transação (do mais recente para o mais antigo). O id de reembolso do adquirente não é devolvido.

json
[
  {
    "id": "b2c3d4e5-f6a7-4890-9abc-def012345678",
    "transactionId": "a1b2c3d4-e5f6-4789-9abc-def012345678",
    "amount": 24900,
    "currency": "BRL",
    "reason": null,
    "status": "REFUNDED",
    "origin": "MERCHANT",
    "idempotencyKey": "order-7821",
    "refundedAt": "2026-04-25T15:50:00.000Z",
    "createdAt": "2026-04-25T15:50:00.000Z",
    "updatedAt": "2026-04-25T16:12:00.000Z"
  }
]

status é um de PENDING, IN_PROGRESS, PENDING_RECONCILIATION, REFUNDED, FAILED; origin é MERCHANT, AUTOMATIC_PAYER_RESTRICTION ou REVIEW_REJECTION. reason só é preenchido para reembolsos de origem do lojista.


#Cancelar pagamento

POST /payments/{id}/cancel

Cancela uma cobrança em andamento — válido apenas enquanto WAITING_PAYMENT ou PENDING. PROCESSING é recusado com 409 payment_not_cancellable, e qualquer outro status com 400. Cobranças liquidadas (PAID/APPROVED) devem ser reembolsadas, não canceladas. Devolve a transação atualizada (sem checkoutUrl) e publica um evento de mudança de status cujo nome público é charge.failed (o gêmeo canônico é charge.failed), com status: "CANCELLED" e previousStatus.


#Reenviar webhook

POST /payments/{id}/resend-webhook

Reemite o status atual da transação como uma entrega de webhook nova — útil quando seu endpoint esteve fora do ar.

json
{ "resent": true, "status": "PAID" }

#Reconciliar

POST /payments/{id}/reconcile

Pede à plataforma que confira a transação de novo com o provedor e liquide qualquer divergência.

Resposta 200 OK

json
{ "reconciled": true, "status": "PAID" }

Uma transação já em estado final responde { "reconciled": false, "status": "…" }. Motivos internos e status do provedor não fazem parte da resposta do lojista.


#Confirmar pagamento de sandbox

POST /payments/{id}/confirm-sandbox

A alavanca de auto-teste do lojista para o fim a fim de webhook: numa transação criada com chave de teste, esta rota torna a cobrança simulada PAID e dispara os webhooks normais. Veja Sandbox.

Resposta 200 OK

json
{ "confirmed": true, "status": "PAID" }

Uma cobrança já em estado final responde { "confirmed": false, "status": "…", "reason": "ALREADY_TERMINAL" }. Uma cobrança que não é de sandbox/teste é recusada com 400.


#Comprovante

GET /payments/{id}/receipt

O comprovante próprio da Moonip (PDF) de uma transação com desfecho final — nunca o documento do adquirente. Transações em andamento respondem 400.

json
{ "contentType": "application/pdf", "base64": "JVBERi0xLjcK..." }

#Webhooks

#Registrar endpoint

POST /webhooks/endpoints

json
{
  "url": "https://merchant.example.com/hooks/moonip",
  "events": ["charge.paid", "charge.failed", "charge.expired"]
}

Nomes de eventos (relacionados a cobrança): charge.created, charge.paid, charge.failed, charge.expired, charge.refunded, payout.created, payout.approved, payout.paid, payout.failed, mais med_case.created e med_case.updated. Eles são entregues no envelope versionado evt_ e assinados X-Moonip-Signature: t=<unix>,v1=<hex> — veja Webhooks. Assine estes. (med.resolved pertence à mesma família, mas viaja no envelope mais antigo { event, data } com a assinatura sha256=.)

Apenas nomes canônicos de cobranças e saques são aceitos em novas inscrições; nomes antigos retornam HTTP 400 LEGACY_WEBHOOK_EVENT com o replacement canônico.

Resposta 201 Created

json
{
  "id": "e5f6a7b8-c9d0-4123-9ef0-123456789012",
  "url": "https://merchant.example.com/hooks/moonip",
  "events": ["charge.paid", "charge.failed", "charge.expired"],
  "secret": "b8f3a9c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1",
  "status": "ACTIVE"
}

O secret é exibido uma única vez — guarde-o imediatamente. Fora do sandbox/staging a URL precisa ser https:// e não pode apontar para um domínio da Moonip; uma URL duplicada responde 409, e a conta tem um número limitado de endpoints ativos.


#Listar endpoints

GET /webhooks/endpoints

json
{
  "data": [
    {
      "id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
      "url": "https://...",
      "events": ["payment.status_changed"],
      "status": "ACTIVE",
      "createdAt": "...",
      "updatedAt": "..."
    }
  ],
  "access": { "canManage": true },
  "httpsOnly": true
}

Os ids são UUIDs. access.canManage diz se quem chama pode escrever endpoints; httpsOnly diz se a plataforma exige URLs https:// no momento.

O secret nunca é devolvido por este endpoint.


#Testar endpoint

POST /webhooks/endpoints/{id}/test

Envia uma amostra canônica charge.paid, com envelope versionado e assinatura com timestamp. O limite de testes por hora devolve 429 WEBHOOK_TEST_RATE_LIMIT quando excedido.

json
{
  "ok": true,
  "statusCode": 200,
  "durationMs": 143,
  "errorKind": null,
  "error": null,
  "responseSnippet": "{\"received\":true}",
  "eventType": "charge.paid",
  "url": "https://merchant.example.com/hooks/moonip",
  "sentHeaders": { "X-Moonip-Event-Type": "charge.paid" },
  "sentPayload": { "id": "evt_...", "object": "event", "...": "..." }
}

errorKind é timeout, connection ou http quando ok é false.


#Rotacionar segredo

POST /webhooks/endpoints/{id}/rotate-secret

json
{ "id": "e5f6a7b8-c9d0-4123-9ef0-123456789012" }

Resposta 200 OK

json
{ "id": "e5f6a7b8-c9d0-4123-9ef0-123456789012", "secret": "<64-char hex>" }

O novo segredo é exibido uma única vez. A rotação é uma troca instantânea no servidor — não há janela de sobreposição. Todo webhook assinado depois desta chamada usa o novo segredo, então faça seu verificador aceitar tanto o segredo antigo quanto o novo durante o seu deploy, e só então descarte o antigo (veja webhooks.md).


#Atualizar endpoint

PATCH /webhooks/endpoints/{id}

Atualiza URL, eventos assinados ou status (ACTIVE/INACTIVE). Envie só os campos a mudar.

json
{ "url": "https://merchant.example.com/hooks/v2", "status": "ACTIVE" }

Resposta 200 OK — { id, url, events, status }. O segredo nunca é devolvido.


#Remover endpoint

DELETE /webhooks/endpoints/{id}

Remove o endpoint permanentemente. Entregas pendentes para ele são redirecionadas para os outros endpoints ativos da conta; só quando não resta nenhum são canceladas. O histórico de entregas é mantido.

Resposta 200 OK — { "deleted": true }.


#Catálogo de eventos

GET /webhooks/event-catalog

Devolve a lista completa e atual dos nomes de eventos públicos assináveis, com descrições. Esta rota está sob a mesma autenticação por chave de API do resto de /v1/webhooks. Excerto relevante para cobranças/saques:

json
[
  { "event": "charge.created", "description": "Cobrança criada (Pix/cartão gerado, aguardando pagamento)." },
  { "event": "charge.paid",    "description": "Cobrança paga e confirmada (Pix/cartão liquidado)." },
  { "event": "charge.failed",  "description": "Cobrança falhou ou foi cancelada." },
  { "event": "charge.expired", "description": "Cobrança expirou sem pagamento." },
  { "event": "charge.refunded","description": "Cobrança reembolsada." },
  { "event": "payout.created", "description": "Saque solicitado." },
  { "event": "payout.approved", "description": "Saque aprovado." },
  { "event": "payout.paid",    "description": "Saque liquidado com sucesso." },
  { "event": "payout.failed",  "description": "Saque falhou ou foi rejeitado." }
]

#Listar entregas

GET /webhooks/deliveries

ParamDescrição
statusPENDING, PROCESSING, DELIVERED, FAILED, CANCELLED
eventTypeFiltra por nome de evento (charge.paid, charge.refunded, charge.paid, …).
endpointIdFiltra por endpoint registrado.
limitPadrão 25, máximo 100.
offsetPadrão 0.
json
{
  "data": [
    {
      "id": "b2c3d4e5-f6a7-4890-9abc-def012345678",
      "endpointId": "a1b2c3d4-e5f6-4789-9abc-def012345678",
      "eventType": "charge.paid",
      "status": "DELIVERED",
      "attempts": 1,
      "maxAttempts": 15,
      "lastStatusCode": 200,
      "lastError": null,
      "nextRetryAt": null,
      "deliveredAt": "2026-04-25T15:43:09.000Z",
      "createdAt": "2026-04-25T15:43:08.000Z",
      "updatedAt": "2026-04-25T15:43:09.000Z",
      "payload": { "type": "charge.paid", "object": "event", "data": { "object": { "id": "ch_example", "object": "charge", "status": "paid" } } }
    }
  ],
  "total": 142,
  "limit": 25,
  "offset": 0
}

As linhas trazem payload e lastError, não o corpo da resposta — esse fica no detalhe da entrega abaixo.


#Consultar entrega

GET /webhooks/deliveries/{id}

A entrega completa, incluindo o corpo da resposta e o log de tentativas.

json
{
  "id": "b2c3d4e5-f6a7-4890-9abc-def012345678",
  "endpointId": "a1b2c3d4-e5f6-4789-9abc-def012345678",
  "eventType": "charge.paid",
  "status": "FAILED",
  "attempts": 3,
  "maxAttempts": 15,
  "lastStatusCode": 502,
  "lastError": "HTTP 502",
  "lastResponseBody": "Bad Gateway",
  "logs": [
    {
      "id": "c3d4e5f6-a7b8-4901-9abc-def012345678",
      "deliveryId": "b2c3d4e5-f6a7-4890-9abc-def012345678",
      "attempt": 1,
      "statusCode": 502,
      "error": "HTTP 502",
      "responseBody": "Bad Gateway",
      "duration": 312,
      "url": "https://merchant.example.com/hooks/moonip",
      "createdAt": "2026-04-25T15:43:09.000Z"
    }
  ],
  "endpoint": { "url": "https://merchant.example.com/hooks/moonip", "status": "ACTIVE" }
}

#Repetir entrega

POST /webhooks/deliveries/{id}/replay

Reenfileira uma entrega (zera tentativas e agenda). Uma entrega DELIVERED é recusada com 400, e uma PROCESSING também com 400 — espere e tente de novo.

json
{ "id": "b2c3d4e5-f6a7-4890-9abc-def012345678", "status": "PENDING", "attempts": 0, "nextRetryAt": "2026-04-25T16:08:00.000Z" }

#Repetir entregas em lote

POST /webhooks/deliveries/replay-bulk

json
{ "status": "FAILED", "endpointId": "a1b2c3d4-e5f6-4789-9abc-def012345678", "limit": 50 }
ParamDescrição
statusStatus alvo. Padrão FAILED + CANCELLED. Valor desconhecido → 400.
endpointIdRestringe a um endpoint.
limitPadrão 50, máximo 500.

Resposta 200 OK — { "requeued": 12 }.


#Puxar eventos

GET /webhooks/events

Visão de reconciliação dos eventos, com paginação por keyset: { data, nextCursor } (note o nextCursor em camelCase). As linhas são { eventType, payload, status, lastStatusCode, deliveredAt, createdAt } — sem id por linha.

ParamDescrição
since/untilLimites ISO 8601 de createdAt.
statusFiltro de status de entrega.
eventTypeFiltro de nome de evento.
limitPadrão 50, máximo 100.
cursorCursor opaco de nextCursor.

#Estatísticas de entrega

GET /webhooks/stats

json
{
  "total": 1402,
  "PENDING": 3,
  "PROCESSING": 1,
  "DELIVERED": 1380,
  "FAILED": 12,
  "CANCELLED": 6
}

#Enum de status

A tabela abaixo é o enum de status detalhado devolvido pelas rotas de transação de /v1/payments (e em eventType/payload.data.status das entregas de webhook delas). POST/GET /v1/charges nunca emitem estes valores; emitem o vocabulário público menor (pending, processing, paid, failed, expired, cancelled, refunded, disputed — veja Primeiros passos §5), sobre o qual o enum abaixo faz a correspondência.

Status da transaçãoDescriçãocharge.status público
PENDINGInterno — em criação. Raramente aparece.pending
WAITING_PAYMENTAguardando ação do cliente.pending
PROCESSINGCartão / 3DS em trânsito.processing
PAIDLiquidado — métodos que não são cartão.paid
APPROVEDLiquidado — métodos de cartão.paid
REFUSEDRecusado pelo adquirente ou emissor.failed
CANCELLEDCancelado antes de concluir.cancelled
EXPIREDA janela de tempo passou.expired
REFUNDEDReembolsado por completo.refunded
CHARGEBACKO emissor abriu um chargeback (cartões).disputed
DISPUTEO portador do cartão abriu uma disputa (cartões).disputed

charge.paid represents a paid charge. charge.failed and charge.expired distinguish failure and expiry.


#Schema do payload de webhook

#Envelope canônico (charge.* / payout.*)

json
{
  "id": "evt_5f3a9b2c1d4e5f6a7b8c9d0e1f2a3b4c",
  "object": "event",
  "api_version": "2026-07-23",
  "type": "charge.paid",
  "created_at": "2026-07-23T14:31:00.000Z",
  "data": {
    "object": {
      "id": "ch_a1b2c3d4-0000-0000-0000-000000000009",
      "object": "charge",
      "amount": 24900,
      "currency": "BRL",
      "status": "paid",
      "payment_method": "pix",
      "settlement": {}
    }
  }
}
CampoSempre presenteDescrição
idsimevt_… — estável por evento de negócio, idêntico entre retentativas. Dedupe por ele.
objectsimSempre "event".
api_versionsimVersão do contrato, ex. 2026-07-23.
typesimcharge.created | charge.paid | charge.failed | charge.expired | charge.refunded | payout.created | payout.paid | payout.failed | med_case.created | med_case.updated.
payout.approvedSaque aprovado, aguardando liquidação (processing).
data.objectsimO objeto público do evento — mesmo serializador e nomes de campo da superfície REST para charge/payout, com as reduções específicas do evento descritas em Webhooks.

#Cabeçalhos de webhook

CabeçalhoDescrição
X-Moonip-SignatureEventos charge.*/payout.*: t=<unix>,v1=<hex> HMAC de "<t>.<rawBody>". KYC, account lifecycle and med.resolved: sha256=<hex> HMAC do corpo cru.
X-Moonip-Delivery-IdÚnico por entrega — idêntico entre todas as retentativas dessa entrega; use para dedupe no nível de transporte.
X-Moonip-Event-TypeEspelha type / event.

Veja webhooks.md para exemplos de verificação.

#Códigos de status HTTP

CódigoUsado para
200Leitura bem-sucedida
201Recurso criado
204Sem conteúdo (ex.: logout)
400Erro de validação, ou uma escrita financeira sem Idempotency-Key
401Chave de API ausente / inválida
403Bloqueado pela guarda de fraude / velocidade (fraud.*), por uma tentativa de reembolso com chave de API (marcador de mensagem REFUND_ADMIN_ONLY, não um valor de code), account_suspended, ou pela allowlist de IP (apenas rotas da conta — carteiras, saques, transações, MED, api-keys; não pagamentos/cobranças/webhooks)
404Recurso não encontrado
409Conflito — idempotency_key_reused, idempotency_key_in_flight, payment_not_cancellable
422Reprovação por regra de negócio repassada da configuração do provedor (ex.: nenhum PSP configurado)
429Limite de taxa atingido
500Erro do servidor — seguro repetir (a idempotência protege)
503Adquirente indisponível — repita com backoff

Veja Erros para o envelope canônico completo de erro (error.type/error.code/error.message, request_id, X-Request-Id).