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", idsch_…, cabeçalhoIdempotency-Key, blocopix. É 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 prefixoch_). 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çalho | Obrigatório | Notas |
|---|---|---|
Authorization | sim | Bearer + sua chave de API (ak_test_* / ak_live_*). O cabeçalho legado apikey continua aceito. |
Idempotency-Key | sim | Qualquer string não vazia, com escopo (lojista, chave). É uma escrita financeira — sem ele, é 400. |
Corpo
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
amount | inteiro | sim | Menor unidade da moeda, > 0. |
currency | string | não | Padrão "BRL". Cobranças Pix devem usar BRL. |
payment_method | string | sim | "pix" — o único valor aceito hoje. |
description | string | não | Até 500 caracteres; dobrado em metadata.description na leitura. |
expires_in | inteiro | não | Validade do Pix em segundos, 60–604800 (7 dias). Fora dessa faixa a API responde 400 — o valor nunca é cortado. |
customer.name | string | não | |
customer.document | string | não | CPF ou CNPJ. |
customer.email | string | não | |
customer.phone | string | não | |
expected_payer_tax_id | string | não | Prende 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. |
metadata | objeto | não | Livre; chaves reservadas/prefixadas com underscore são removidas. |
Exemplo
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
{
"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.
GET /v1/charges?limit=25&starting_after=ch_01J…&status=paid| Param | Tipo | Padrão | Descrição |
|---|---|---|---|
limit | inteiro | 25 | 1–100. |
starting_after | string | — | Um id de cobrança (ch_…) do mesmo recurso — cursor, não offset. |
status | enum | — | Vocabulário público de status (pending, paid, …). |
created_after | ISO 8601 | — | Limite inferior inclusivo de created_at. |
created_before | ISO 8601 | — | Limite superior inclusivo de created_at. |
customer_id | string | — | Filtra por documento do pagador. |
{
"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:
curl "https://api.moonip.io/v1/wallets/balance" \
-H "Authorization: Bearer $MOONIP_API_KEY"Resposta 200 OK
{
"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 }
}| Campo | Descrição |
|---|---|
available | Saldo 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. |
held | Valor em reserva (retenção em trânsito) nas entradas de balances[]/primary. Não existe no nível superior. |
pending | Liquidado mas ainda retido — indisponível por enquanto (moeda principal). Inclui reservas de reembolso em trânsito, por isso pode ser maior que retained. |
total | available + held + pending da moeda principal. |
retained | Total sob retenção (reservas PENDING) da moeda principal. Não inclui reservas de reembolso. |
currency | Moeda principal (ISO 4217). Padrão BRL. |
next_release_at | String ISO 8601 de quando a próxima parcela de pending libera, ou null quando nada está agendado. |
next_release_amount | Valor dessa próxima liberação (menor unidade), ou null. |
balances[] | Uma entrada por moeda: { currency, available, held, pending, total }. |
primary | A 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[]eprimarycontam apenas carteirasOPERATIONALativas, 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 ebalances: []. - 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úmerosavailablediferentes.
#Moeda única (formato legado)
GET /v1/wallets/balance?currency=BRL
Passe currency para receber o formato plano de moeda única:
curl "https://api.moonip.io/v1/wallets/balance?currency=BRL" \
-H "Authorization: Bearer $MOONIP_API_KEY"{
"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(nunca404). Semcurrencyesta rota nunca retorna404; com?currency=, um404not_foundsignifica 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.
curl https://api.moonip.io/v1/wallets \
-H "Authorization: Bearer $MOONIP_API_KEY"Resposta 200 OK
[
{
"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.
curl https://api.moonip.io/v1/wallets/a1b2c3d4-e5f6-4789-9abc-def012345678 \
-H "Authorization: Bearer $MOONIP_API_KEY"Resposta 200 OK
{
"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çalho | Obrigatório | Notas |
|---|---|---|
Authorization | sim | Bearer + sua chave de API. |
Idempotency-Key | sim | Canal 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
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
amount | inteiro | sim | Menor unidade da moeda, > 0. |
currency | string (3–8) | não | Padrão "BRL". Exemplos: BRL, EUR, USD, USDT. |
paymentMethods | string[] | exceto com checkout | Um 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. |
checkout | string | não | Slug do template de checkout hospedado (1–80 caracteres). Slug desconhecido → 404. |
customerId | string | não | O id interno do seu usuário. Só é encaminhado com a requisição — não é persistido, não aparece em respostas ou webhooks do lojista. |
customerName | string | não | |
customerDocument | string | não | CPF, CNPJ, NIF ou outro id fiscal. |
customerEmail | string | não | Precisa ser um endereço de e-mail válido. |
customerPhone | string | não | |
customer | objeto | não | Aninhado { name?, document?, email?, phone? }. Achata nos campos customer*; em conflito, os campos planos ganham. |
expectedPayerDocument | string | não | Trava de CPF/CNPJ do pagador, só dígitos. |
items | objeto[] | não | { name, quantity, unitPrice, totalPrice } — todos inteiros > 0 exceto name. |
metadata | objeto | não | Livre. As chaves returnUrl/cancelUrl dentro dele precisam ser URLs http(s), mas as URLs de retorno são os campos de primeira classe abaixo. |
webhookUrl | string | não | Sobrescrita da URL de webhook por transação. |
country | string | não | 2–3 caracteres. |
riskTier | enum | não | LOW, MEDIUM ou HIGH. |
environment | enum | não | SANDBOX, STAGING ou PRODUCTION (padrão PRODUCTION). A chave, sozinha, decide para onde a cobrança vai de fato. |
returnUrl | string | não | Para onde o pagador volta depois de pagar: URL http(s) sem fragmento. |
cancelUrl | string | não | Para onde o pagador vai ao cancelar: mesma regra de URL. Exige returnUrl. |
expiresIn | inteiro | não | Validade do Pix em segundos, 60–604800. |
idempotencyKey | string | sim | Apelido no corpo do cabeçalho Idempotency-Key. Único por requisição pretendida; retentativas devolvem a original. |
Resposta 201 Created
{
"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
{
"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
| Param | Tipo | Padrão | Descrição |
|---|---|---|---|
page | int | 1 | |
limit | int | 20 | Máximo 100. |
status | enum | — | Veja Enum de status. |
currency | string | — | Filtra por moeda. |
startDate | ISO 8601 | — | Limite inferior inclusivo de createdAt. |
endDate | ISO 8601 | — | Limite superior inclusivo de createdAt. |
source | enum | — | API, PANEL, MANUAL ou INDEFINIDO (linhas sem origem). |
sortBy | string | createdAt | Nome de uma coluna da transação (createdAt, amount, status, paidAt, updatedAt, …). Nome desconhecido é 500, não 400 — fique com colunas reais. |
sortOrder | enum | desc | asc ou desc. |
Resposta 200 OK
{
"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
| Param | Tipo | Padrão | Descrição |
|---|---|---|---|
days | int | 7 | Dias úteis no timeZone, com o dia de hoje incluído. |
from/to | ISO 8601 | — | Janela explícita; prevalece sobre days. |
timeZone | string | America/Sao_Paulo | Nome 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
{
"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¤cy=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
{
"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.
| Param | Tipo | Padrão | Descrição |
|---|---|---|---|
from/to | ISO 8601 | — | Limites da janela. |
timeZone | string | America/Sao_Paulo | Nome de fuso IANA. |
Resposta 200 OK
{
"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.
| Param | Tipo | Padrão | Descrição |
|---|---|---|---|
from | ISO 8601 | — | Limite inferior inclusivo. Valor inválido → 400. |
Resposta 200 OK
[
{ "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.
[
{
"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.
{ "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
{ "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
{ "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.
{ "contentType": "application/pdf", "base64": "JVBERi0xLjcK..." }#Webhooks
#Registrar endpoint
POST /webhooks/endpoints
{
"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
{
"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
{
"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.
{
"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
{ "id": "e5f6a7b8-c9d0-4123-9ef0-123456789012" }Resposta 200 OK
{ "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.
{ "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:
[
{ "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
| Param | Descrição |
|---|---|
status | PENDING, PROCESSING, DELIVERED, FAILED, CANCELLED |
eventType | Filtra por nome de evento (charge.paid, charge.refunded, charge.paid, …). |
endpointId | Filtra por endpoint registrado. |
limit | Padrão 25, máximo 100. |
offset | Padrão 0. |
{
"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.
{
"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.
{ "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
{ "status": "FAILED", "endpointId": "a1b2c3d4-e5f6-4789-9abc-def012345678", "limit": 50 }| Param | Descrição |
|---|---|
status | Status alvo. Padrão FAILED + CANCELLED. Valor desconhecido → 400. |
endpointId | Restringe a um endpoint. |
limit | Padrã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.
| Param | Descrição |
|---|---|
since/until | Limites ISO 8601 de createdAt. |
status | Filtro de status de entrega. |
eventType | Filtro de nome de evento. |
limit | Padrão 50, máximo 100. |
cursor | Cursor opaco de nextCursor. |
#Estatísticas de entrega
GET /webhooks/stats
{
"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ção | Descrição | charge.status público |
|---|---|---|
PENDING | Interno — em criação. Raramente aparece. | pending |
WAITING_PAYMENT | Aguardando ação do cliente. | pending |
PROCESSING | Cartão / 3DS em trânsito. | processing |
PAID | Liquidado — métodos que não são cartão. | paid |
APPROVED | Liquidado — métodos de cartão. | paid |
REFUSED | Recusado pelo adquirente ou emissor. | failed |
CANCELLED | Cancelado antes de concluir. | cancelled |
EXPIRED | A janela de tempo passou. | expired |
REFUNDED | Reembolsado por completo. | refunded |
CHARGEBACK | O emissor abriu um chargeback (cartões). | disputed |
DISPUTE | O 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.*)
{
"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": {}
}
}
}| Campo | Sempre presente | Descrição |
|---|---|---|
id | sim | evt_… — estável por evento de negócio, idêntico entre retentativas. Dedupe por ele. |
object | sim | Sempre "event". |
api_version | sim | Versão do contrato, ex. 2026-07-23. |
type | sim | charge.created | charge.paid | charge.failed | charge.expired | charge.refunded | payout.created | payout.paid | payout.failed | med_case.created | med_case.updated. |
payout.approved | Saque aprovado, aguardando liquidação (processing). | |
data.object | sim | O 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çalho | Descrição |
|---|---|
X-Moonip-Signature | Eventos 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-Type | Espelha type / event. |
Veja webhooks.md para exemplos de verificação.
#Códigos de status HTTP
| Código | Usado para |
|---|---|
200 | Leitura bem-sucedida |
201 | Recurso criado |
204 | Sem conteúdo (ex.: logout) |
400 | Erro de validação, ou uma escrita financeira sem Idempotency-Key |
401 | Chave de API ausente / inválida |
403 | Bloqueado 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) |
404 | Recurso não encontrado |
409 | Conflito — idempotency_key_reused, idempotency_key_in_flight, payment_not_cancellable |
422 | Reprovação por regra de negócio repassada da configuração do provedor (ex.: nenhum PSP configurado) |
429 | Limite de taxa atingido |
500 | Erro do servidor — seguro repetir (a idempotência protege) |
503 | Adquirente 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).