Jungle Pagamentos Docs · API do Seller Acessar painel
API v1 · em produção · retrocompatível

Receba com PIX na sua stack,
do checkout ao saque.

Cobranças PIX, checkout hospedado, webhooks assinados e saque programático — com a mesma API Key da sua dashboard. E tudo pronto para o seu agente de IA integrar por você.

Base URL https://app.junglepagamentos.com/api
POST /gateway/charges → 201
$ curl -X POST '…/api/gateway/charges' \ -H 'X-Api-Key: ••••••••••••' \ -d '{"product":{"name":"Plano Pro","value":149.90}, …}' { "success": true, "data": { "transactionId": "b3f1c7e2-…", "pixCode": "00020126580014br.gov.bcb.pix…", "amount": 149.90, "netAmount": 144.65 } } // webhook ← transaction.paid · X-Signature ✓
Começando

Primeira cobrança em 3 passos

Do zero ao PIX pago em poucos minutos. Tudo o que você precisa está no painel da Jungle.

Gere a API Key e copie o Webhook Secret

No painel, abra Conta → API com a dashboard certa selecionada. A API Key identifica a dashboard; o Webhook Secret assina os webhooks e os saques. Guarde os dois em variáveis de ambiente do servidor.

.env
JUNGLE_API_KEY=sua_api_key
JUNGLE_WEBHOOK_SECRET=seu_webhook_secret

Crie a cobrança e mostre o QR Code

Envie produto, comprador e a URL que vai receber o webhook. Renderize o QR a partir de pixCode (é o copia-e-cola EMV).

POST/gateway/charges
curl -X POST 'https://app.junglepagamentos.com/api/gateway/charges' \
  -H "X-Api-Key: $JUNGLE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "product":  { "name": "Plano Pro", "value": 149.90 },
    "customer": { "name": "João Silva", "email": "joao@exemplo.com", "doc": "11122233344" },
    "callbackUrl": "https://seusite.com/webhooks/jungle",
    "metadata": { "pedido_id": "A-1001" }
  }'
const res = await fetch('https://app.junglepagamentos.com/api/gateway/charges', {
  method: 'POST',
  headers: { 'X-Api-Key': process.env.JUNGLE_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    product: { name: 'Plano Pro', value: 149.90 },
    customer: { name: 'João Silva', email: 'joao@exemplo.com', doc: '11122233344' },
    callbackUrl: 'https://seusite.com/webhooks/jungle',
    metadata: { pedido_id: 'A-1001' },
  }),
})
const { success, data, error } = await res.json()
if (!success) throw new Error(error)
// data.pixCode → QR Code · data.transactionId → salve no pedido
import os, requests

r = requests.post(
    "https://app.junglepagamentos.com/api/gateway/charges",
    headers={"X-Api-Key": os.environ["JUNGLE_API_KEY"]},
    json={
        "product": {"name": "Plano Pro", "value": 149.90},
        "customer": {"name": "João Silva", "email": "joao@exemplo.com", "doc": "11122233344"},
        "callbackUrl": "https://seusite.com/webhooks/jungle",
        "metadata": {"pedido_id": "A-1001"},
    },
    timeout=15,
)
body = r.json()
if not body["success"]:
    raise RuntimeError(body["error"])
pix_code = body["data"]["pixCode"]
$ch = curl_init('https://app.junglepagamentos.com/api/gateway/charges');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['X-Api-Key: ' . getenv('JUNGLE_API_KEY'), 'Content-Type: application/json'],
  CURLOPT_POSTFIELDS => json_encode([
    'product'  => ['name' => 'Plano Pro', 'value' => 149.90],
    'customer' => ['name' => 'João Silva', 'email' => 'joao@exemplo.com', 'doc' => '11122233344'],
    'callbackUrl' => 'https://seusite.com/webhooks/jungle',
    'metadata' => ['pedido_id' => 'A-1001'],
  ]),
]);
$body = json_decode(curl_exec($ch), true);
if (!$body['success']) throw new Exception($body['error']);
$pixCode = $body['data']['pixCode'];

Receba o webhook e libere o pedido

Valide o X-Signature com o Webhook Secret, responda 200 e libere só em transaction.paid. Exemplos completos em Verificar assinatura.

PENDING→PAID→seu pedido liberado
Começando

Autenticação

Toda requisição à API leva a API Key da dashboard no header X-Api-Key. Cada dashboard (workspace) tem a sua; o que você cria e consulta fica restrito a ela.

CredencialUso
API KeyHeader X-Api-Key em toda chamada. Também assina requisições, se a dashboard exigir assinatura.
Webhook SecretNunca trafega. Verifica o X-Signature dos webhooks e assina os saques.
Token MCP jmcp_…Só para o servidor MCP de análise. Não funciona nas rotas REST — e a API Key não funciona no MCP.

Chave é segredo de servidor. Nunca coloque API Key ou Webhook Secret em front-end, app mobile, repositório ou log. Vazou? Gere outra no painel — a anterior deixa de funcionar.

Respostas de autenticação

HTTPerrorCausa
401X-Api-Key header requiredHeader ausente
401Invalid API keyChave inexistente ou substituída
403Workspace is not activeDashboard desativada
403Company account is not activeConta inativa
Headers de toda requisição
X-Api-Key: sua_api_key
Content-Type: application/json

# só se a dashboard exigir assinatura (ver Segurança)
X-Signature: 3f9a…
X-Timestamp: 1727532000000
X-Nonce: 8b1e6c52-2f0e-4c0f-9d4a-1c2b3a4d5e6f
401
{ "success": false, "error": "Invalid API key" }
Começando

Convenções

Valores em reais

Todo valor monetário — product.value, amount, feeAmount, netAmount — é número decimal em reais (149.90). A API do seller não usa centavos. Exceção: o servidor MCP usa centavos inteiros (*_cents).

Envelope

Toda resposta REST tem success. Sucesso traz data (e meta em listas); erro traz error com uma mensagem legível. Cheque success antes de ler data.

Datas

ISO-8601 em UTC (2026-07-23T12:40:00.000Z). Converta para o fuso do seu usuário na exibição.

Status

Strings exatas em MAIÚSCULO (PAID, ENVIADO…). Eventos de webhook em minúsculo com ponto (transaction.paid). Compare por igualdade exata. Tabela completa.

Compatibilidade

A API evolui só de forma aditiva: campos novos são opcionais e comportamentos novos são opt-in. Nada que você já envia hoje muda de resultado. Por isso, ignore campos desconhecidos na resposta e no webhook em vez de rejeitá-los.

sucesso
{
  "success": true,
  "data": { "transactionId": "b3f1…", "amount": 149.90 }
}
lista
{
  "success": true,
  "data": [ … ],
  "meta": { "total": 137, "page": 1, "limit": 20, "pages": 7 }
}
erro
{ "success": false, "error": "Saldo insuficiente" }
Cobranças PIX

Criar cobrança PIX

POST/gateway/chargesX-Api-Key

Gera um PIX com QR Code dinâmico e devolve o copia-e-cola. A cobrança nasce PENDING e muda de status por webhook.

Body

productobjectobrigatório
namestringobrigatório

Nome do produto ou serviço.

valuenumberobrigatório

Valor em reais, maior que zero. Ex.: 149.90.

customerobjectobrigatório
namestringobrigatório
emailstringobrigatório

E-mail válido.

docstringobrigatório

CPF ou CNPJ, mínimo 11 caracteres.

docType"cpf" | "cnpj"opcional= "cpf"
phone · zip · street · number · district · city · statestringopcional

Contato e endereço do comprador.

ipstringopcional

IP do comprador — nunca o do seu servidor. Melhora a atribuição de anúncio.

expiracaoSegundosnumberopcional= 3600

Validade do QR Code em segundos.

callbackUrlstring (URL)opcional

Recebe os webhooks transaction.* desta cobrança.

metadataobjectopcional

Objeto livre, devolvido intacto nos webhooks e na listagem — coloque o id do seu pedido. Também carrega os identificadores de clique.

Resposta 201

transactionIdstring

Id da cobrança. Salve no seu pedido: é a chave de reconciliação e de deduplicação de webhooks.

pixCodestring

Copia-e-cola EMV. Gere o QR Code a partir dele.

expiresAtstring (ISO)

Quando o QR deixa de valer.

amount · feeAmount · netAmountnumber

Valor cobrado, taxa e líquido que entra no seu saldo, em reais.

POST/gateway/charges
curl -X POST 'https://app.junglepagamentos.com/api/gateway/charges' \
  -H "X-Api-Key: $JUNGLE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "product":  { "name": "Plano Pro", "value": 149.90 },
    "customer": {
      "name": "João Silva",
      "email": "joao@exemplo.com",
      "doc": "11122233344",
      "docType": "cpf",
      "phone": "11912345678",
      "ip": "177.10.20.30"
    },
    "expiracaoSegundos": 3600,
    "callbackUrl": "https://seusite.com/webhooks/jungle",
    "metadata": { "pedido_id": "A-1001" }
  }'
export async function criarPix(pedido) {
  const res = await fetch('https://app.junglepagamentos.com/api/gateway/charges', {
    method: 'POST',
    headers: { 'X-Api-Key': process.env.JUNGLE_API_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      product: { name: pedido.titulo, value: pedido.total },   // reais
      customer: {
        name: pedido.cliente.nome,
        email: pedido.cliente.email,
        doc: pedido.cliente.cpf,
        ip: pedido.cliente.ip,                                  // IP do comprador
      },
      callbackUrl: 'https://seusite.com/webhooks/jungle',
      metadata: { pedido_id: pedido.id },
    }),
  })
  const body = await res.json()
  if (!body.success) throw new Error(`${res.status} ${body.error}`)
  return body.data // { transactionId, pixCode, expiresAt, amount, feeAmount, netAmount }
}
def criar_pix(pedido):
    r = requests.post(
        f"{API}/gateway/charges",
        headers={"X-Api-Key": os.environ["JUNGLE_API_KEY"]},
        json={
            "product": {"name": pedido.titulo, "value": float(pedido.total)},
            "customer": {
                "name": pedido.cliente.nome,
                "email": pedido.cliente.email,
                "doc": pedido.cliente.cpf,
                "ip": pedido.cliente.ip,
            },
            "callbackUrl": "https://seusite.com/webhooks/jungle",
            "metadata": {"pedido_id": pedido.id},
        },
        timeout=15,
    )
    body = r.json()
    if not body["success"]:
        raise RuntimeError(f'{r.status_code} {body["error"]}')
    return body["data"]
function criarPix(array $pedido): array {
  $ch = curl_init('https://app.junglepagamentos.com/api/gateway/charges');
  curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 15,
    CURLOPT_HTTPHEADER => ['X-Api-Key: ' . getenv('JUNGLE_API_KEY'), 'Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode([
      'product'  => ['name' => $pedido['titulo'], 'value' => (float) $pedido['total']],
      'customer' => ['name' => $pedido['nome'], 'email' => $pedido['email'], 'doc' => $pedido['cpf']],
      'callbackUrl' => 'https://seusite.com/webhooks/jungle',
      'metadata' => ['pedido_id' => $pedido['id']],
    ]),
  ]);
  $body = json_decode(curl_exec($ch), true);
  if (empty($body['success'])) throw new RuntimeException($body['error'] ?? 'erro');
  return $body['data'];
}
201 Created
{
  "success": true,
  "data": {
    "transactionId": "b3f1c7e2-5a41-4b8e-9f0d-2c6a7e1d4b90",
    "pixCode": "00020126580014br.gov.bcb.pix0136…6304A1B2",
    "expiresAt": "2026-07-23T13:00:00.000Z",
    "amount": 149.90,
    "feeAmount": 5.25,
    "netAmount": 144.65
  }
}
Cobranças PIX

Rastreio de anúncio no metadata

Opcional. Quando a venda é paga, a Jungle envia a conversão pelo servidor aos destinos configurados no painel (Integrações → Destinos de conversão): GA4 (Measurement Protocol), Meta (Conversions API) e TikTok (Events API). Para isso ela lê, do seu metadata, os identificadores de clique — dados que só existem no momento do clique e não são recuperáveis depois.

ChaveOrigem
gclid · gbraid · wbraidParâmetros da URL do anúncio Google
fbclidParâmetro da URL (Meta)
fbpCookie _fbp, valor cru
fbcCookie _fbc, valor cru. Sem o cookie, mande só fbclid
ttclid · ttpURL e cookie _ttp (TikTok)
gaClientIdCookie _ga (ex.: GA1.1.111.222)
clickAtISO-8601 UTC da primeira captura do clique
userAgentUser-Agent do navegador do comprador (Meta: client_user_agent)
sourceUrlURL da página da compra (Meta: event_source_url)

Todas são string e opcionais; ausente e null são equivalentes. A forma plana e a aninhada em trackingParameters produzem o mesmo resultado. Também aceitamos user_agent, source_url, pageUrl e page_url.

Deduplicação: o evento server-side sai com o transactionId como transaction_id (GA4) e event_id (Meta Purchase, TikTok CompletePayment). Se você também dispara a compra pelo pixel do navegador, use o mesmo transactionId — senão cada venda conta duas vezes.

Não hasheie nem monte fbp/fbc. Valor cru do cookie ou nada. Se só houver fbclid, a Jungle monta o fbc no formato da Meta usando o clickAt. E não duplique valor, e-mail, telefone ou documento no metadata: isso já está em product/customer, e a Jungle faz o hash exigido pelas plataformas.

metadata — forma plana
{
  "metadata": {
    "pedido_id": "A-1001",
    "gclid": "Cj0KCQjw…",
    "fbp": "fb.1.1690000000000.1234567890",
    "fbclid": "IwAR2x…",
    "clickAt": "2026-07-23T11:58:12.000Z",
    "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X)…",
    "sourceUrl": "https://sualoja.com/checkout"
  }
}
metadata — aninhada (mesmo resultado)
{
  "metadata": {
    "pedido_id": "A-1001",
    "trackingParameters": { "gclid": "Cj0KCQjw…", "ttclid": "E.C.P.…" }
  }
}
Capturar no navegador (front-end)
// Rode na landing, ANTES do checkout, e guarde junto do carrinho.
const q = new URLSearchParams(location.search)
const cookie = (n) => document.cookie.match('(^|;)\\s*' + n + '=([^;]*)')?.[2]
const tracking = {
  gclid: q.get('gclid'), fbclid: q.get('fbclid'), ttclid: q.get('ttclid'),
  fbp: cookie('_fbp'), fbc: cookie('_fbc'), ttp: cookie('_ttp'),
  clickAt: new Date().toISOString(),
  userAgent: navigator.userAgent, sourceUrl: location.href,
}
// envie `tracking` ao SEU backend, que repassa em metadata na cobrança
Cobranças PIX

Listar transações

GET/gateway/transactionsX-Api-Key

Lista as transações da dashboard da API Key, da mais recente para a mais antiga. Use para reconciliação diária ou como fallback quando o webhook não chegar.

Query

pageintegeropcional= 1
limitintegeropcional= 20 · máx. 100

Campos de cada transação

idstring

O mesmo transactionId da criação e do webhook.

statusenum
PENDINGPAIDFAILEDEXPIREDMED
endToEndIdstring | null

Identificador E2E do PIX no Banco Central (E + 31). Guarde para conciliar com o extrato.

amount · feeRate · feeAmount · netAmountnumber

Valores em reais; feeRate em %.

pixCode · pixExpiresAtstring | null

Em cobrança de boleto (emitida no checkout hospedado), pixCode vem null e pixExpiresAt traz o vencimento do título.

paidAt · createdAt · updatedAtstring (ISO)
customer · metadataobject

metadata é exatamente o que você enviou.

Cobrança que passou do vencimento aparece como EXPIRED mesmo antes do aviso do banco. MED é pagamento contestado e devolvido: trate como não pago.

GET/gateway/transactions
curl 'https://app.junglepagamentos.com/api/gateway/transactions?page=1&limit=100' \
  -H "X-Api-Key: $JUNGLE_API_KEY"
// Varre todas as páginas (reconciliação noturna)
async function* transacoes() {
  for (let page = 1; ; page++) {
    const res = await fetch(`${API}/gateway/transactions?page=${page}&limit=100`, {
      headers: { 'X-Api-Key': process.env.JUNGLE_API_KEY },
    })
    const { success, data, meta, error } = await res.json()
    if (!success) throw new Error(error)
    yield* data
    if (page >= meta.pages) return
  }
}
def transacoes():
    page = 1
    while True:
        r = requests.get(f"{API}/gateway/transactions",
                         params={"page": page, "limit": 100},
                         headers={"X-Api-Key": os.environ["JUNGLE_API_KEY"]}, timeout=15)
        body = r.json()
        if not body["success"]:
            raise RuntimeError(body["error"])
        yield from body["data"]
        if page >= body["meta"]["pages"]:
            return
        page += 1
$ch = curl_init('https://app.junglepagamentos.com/api/gateway/transactions?page=1&limit=100');
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['X-Api-Key: ' . getenv('JUNGLE_API_KEY')],
]);
$body = json_decode(curl_exec($ch), true);
foreach ($body['data'] as $tx) { /* $tx['status'] === 'PAID' */ }
200 OK
{
  "success": true,
  "data": [
    {
      "id": "b3f1c7e2-…",
      "acquirerTxId": "abc123",
      "endToEndId": "E1234567890202607231240abcdef123",
      "status": "PAID",
      "productName": "Plano Pro",
      "productValue": 149.90,
      "amount": 149.90,
      "feeRate": 3.5,
      "feeAmount": 5.25,
      "netAmount": 144.65,
      "pixCode": "00020126580014br.gov.bcb.pix…",
      "pixExpiresAt": "2026-07-23T13:00:00.000Z",
      "paidAt": "2026-07-23T12:40:00.000Z",
      "customer": { "name": "João Silva", "email": "joao@exemplo.com", "doc": "11122233344", "phone": "11912345678" },
      "metadata": { "pedido_id": "A-1001" },
      "createdAt": "2026-07-23T12:00:00.000Z",
      "updatedAt": "2026-07-23T12:40:00.000Z"
    }
  ],
  "meta": { "total": 137, "page": 1, "limit": 100, "pages": 2 }
}
Checkout hospedado

Criar sessão de checkout

POST/checkout/sessionsX-Api-Key

Transforma o carrinho do seu sistema numa página de pagamento pronta — a que você montou em Vendas → Checkouts, com seu visual, order bumps, cupons, frete, PIX e boleto. Você recebe o link e redireciona o comprador.

Body

checkoutIdstringobrigatório

Id do checkout, copiado do painel.

productsarrayobrigatório · mín. 1
namestringobrigatório

Exibido no resumo do pedido.

valuenumberobrigatório

Preço unitário em reais.

quantityintegeropcional= 1
imagestring (URL)opcional

URL http(s) da foto. O nome é image — imageUrl, photo e afins são descartados em silêncio.

variantstringopcional

Tamanho, cor etc., exibido junto ao nome.

customerobjectopcional

Pré-preenche o comprador: name, email, phone, docType, doc.

externalRefstringopcional

Id do seu pedido/carrinho. Ver Referência do pedido.

Resposta 201

sessionIdstring
amountnumber

Total dos produtos enviados, em reais. Frete, order bump e cupom escolhidos na página entram depois, na cobrança.

checkoutUrlstring

Link da página de pagamento, no formato <base>/c/<sessionId>.

Use o checkoutUrl exatamente como veio. O domínio pode ser o seu domínio próprio de checkout ou um domínio da plataforma, e pode mudar. Não monte o link nem valide o host contra uma lista.

Link não pago expira após 1 dia. Para vender de novo, crie outra sessão. A venda concluída vira uma transação da dashboard e aparece em GET /gateway/transactions, com checkoutId, sessionId, products, bumps, coupon e shipping no metadata.

POST/checkout/sessions
curl -X POST 'https://app.junglepagamentos.com/api/checkout/sessions' \
  -H "X-Api-Key: $JUNGLE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "checkoutId": "d78a3545-0920-41a9-a0e9-5d2a8d89946c",
    "products": [
      {
        "name": "Camiseta Oversized",
        "value": 89.90,
        "quantity": 2,
        "image": "https://cdn.sualoja.com/p/1.webp",
        "variant": "Preta · M"
      }
    ],
    "customer": { "email": "joao@exemplo.com" },
    "externalRef": "PEDIDO-A1001"
  }'
app.post('/finalizar', async (req, res) => {
  const carrinho = await carrinhos.get(req.session.cartId)
  const r = await fetch('https://app.junglepagamentos.com/api/checkout/sessions', {
    method: 'POST',
    headers: { 'X-Api-Key': process.env.JUNGLE_API_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      checkoutId: process.env.JUNGLE_CHECKOUT_ID,
      products: carrinho.itens.map(i => ({
        name: i.nome, value: i.preco, quantity: i.qtd, image: i.foto, variant: i.variacao,
      })),
      externalRef: `PEDIDO-${carrinho.id}`,
    }),
  })
  const { success, data, error } = await r.json()
  if (!success) return res.status(502).send(error)
  res.redirect(303, data.checkoutUrl)   // exatamente como veio
})
r = requests.post(
    f"{API}/checkout/sessions",
    headers={"X-Api-Key": os.environ["JUNGLE_API_KEY"]},
    json={
        "checkoutId": os.environ["JUNGLE_CHECKOUT_ID"],
        "products": [
            {"name": i.nome, "value": float(i.preco), "quantity": i.qtd, "image": i.foto}
            for i in carrinho.itens
        ],
        "externalRef": f"PEDIDO-{carrinho.id}",
    },
    timeout=15,
)
body = r.json()
return redirect(body["data"]["checkoutUrl"])
$payload = [
  'checkoutId' => getenv('JUNGLE_CHECKOUT_ID'),
  'products'   => array_map(fn($i) => [
    'name' => $i['nome'], 'value' => (float) $i['preco'], 'quantity' => $i['qtd'], 'image' => $i['foto'],
  ], $carrinho['itens']),
  'externalRef' => 'PEDIDO-' . $carrinho['id'],
];
$ch = curl_init('https://app.junglepagamentos.com/api/checkout/sessions');
curl_setopt_array($ch, [
  CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['X-Api-Key: ' . getenv('JUNGLE_API_KEY'), 'Content-Type: application/json'],
  CURLOPT_POSTFIELDS => json_encode($payload),
]);
$body = json_decode(curl_exec($ch), true);
header('Location: ' . $body['data']['checkoutUrl'], true, 303);
201 Created
{
  "success": true,
  "data": {
    "sessionId": "5f0c2a9e-7b1d-4c3e-8a6f-9d2e1b0c4a7f",
    "amount": 179.80,
    "checkoutUrl": "https://pagar.sualoja.com/c/5f0c2a9e-7b1d-4c3e-8a6f-9d2e1b0c4a7f"
  }
}
Checkout hospedado

Consultar sessão

GET/checkout/sessions/{sessionId}público

Devolve a sessão como a página do comprador a vê. Serve para conferir se os produtos voltaram com o que você mandou (lembre: campo com nome errado é descartado sem erro).

expiredboolean

true quando o link passou de 1 dia sem pagamento e sem cobrança pendente. A página passa a exibir "Checkout expirado".

checkout.themeobject

Só apresentação (layoutKey pode ser "default", "vega" ou "zed"). Não baseie lógica de integração nele.

Para saber se a venda foi paga, use o webhook ou GET /gateway/transactions — são a fonte da verdade do pagamento.

GET/checkout/sessions/{sessionId}
curl 'https://app.junglepagamentos.com/api/checkout/sessions/5f0c2a9e-7b1d-4c3e-8a6f-9d2e1b0c4a7f'
Checkout hospedado

Referência do pedido — externalRef

Cruza a sessão do checkout com o pedido do seu sistema na análise de conversão (painel e MCP). Duas formas, independentes:

  1. No body de POST /checkout/sessions: "externalRef": "PEDIDO-A1001".
  2. Na URL: anexe ?external_ref=PEDIDO-A1001 ao checkoutUrl devolvido.
Regra
Formato1–128 caracteres, só A-Z a-z 0-9 . _ : - (sem espaço)
Valor inválidoIgnorado — a sessão é criada normalmente, sem referência
Primeira gravação venceUma vez preenchida, não troca
Onde apareceAnálise de conversão. Não vai para webhooks nem substitui o metadata da cobrança

Não coloque e-mail, CPF ou telefone no externalRef. Use o id do pedido.

Pela URL
const url = new URL(data.checkoutUrl)
url.searchParams.set('external_ref', 'PEDIDO-A1001')
location.href = url.toString()
Webhooks

Eventos de transação

Com callbackUrl na cobrança, a Jungle faz POST nessa URL a cada mudança de status.

transaction.created→ transaction.paidou transaction.failedou transaction.expired

Headers enviados

Content-Typeapplication/json
User-AgentJunglePagamentos-Webhook-Dispatcher/1.0
X-SignatureHMAC-SHA256 hex do corpo bruto, com o Webhook Secret

Payload

eventstring

Minúsculo com ponto: transaction.created, .paid, .failed, .expired.

data.statusenum

MAIÚSCULO: PENDING PAID FAILED EXPIRED MED.

data.endToEndIdstring | null

Pode vir null antes do pagamento.

data.metadataobject

Ecoa o que você mandou. Campos internos da plataforma nunca aparecem aqui.

Seu endpoint deve:

  • ler o corpo bruto e validar o X-Signature antes de tudo;
  • responder 200 rápido e processar numa fila;
  • ser idempotente por transactionId + event — o mesmo evento pode chegar mais de uma vez;
  • liberar o pedido só em transaction.paid com data.status === "PAID";
  • casar pelo seu metadata e, em dúvida, confirmar com GET /gateway/transactions.
POST→ sua callbackUrl
{
  "event": "transaction.paid",
  "data": {
    "transactionId": "b3f1c7e2-…",
    "acquirerTxId": "abc123",
    "endToEndId": "E1234567890202607231240abcdef123",
    "status": "PAID",
    "amount": 149.90,
    "feeAmount": 5.25,
    "netAmount": 144.65,
    "productName": "Plano Pro",
    "customer": {
      "name": "João Silva",
      "email": "joao@exemplo.com",
      "doc": "11122233344",
      "phone": "11912345678"
    },
    "metadata": { "pedido_id": "A-1001" },
    "paidAt": "2026-07-23T12:40:00.000Z",
    "createdAt": "2026-07-23T12:00:00.000Z"
  }
}
Webhooks

Verificar a assinatura

O header X-Signature é:

fórmula
X-Signature = hex( HMAC-SHA256( chave = Webhook Secret, mensagem = corpo bruto ) )

Recalcule e compare em tempo constante. O erro mais comum é assinar o JSON re-serializado pelo seu framework — espaços e ordem de chaves mudam e a assinatura não bate. Leia o corpo cru antes de qualquer parser.

FrameworkComo obter o corpo bruto
Expressexpress.raw({ type: 'application/json' }) na rota
Next.js (App Router)await req.text()
FastifyaddContentTypeParser com parseAs: 'buffer'
Flask / Djangorequest.get_data() / request.body
Laravel / PHP$request->getContent() / php://input

Quer conferir um webhook real? Cole o corpo e o segredo no laboratório HMAC — o cálculo roda no seu navegador.

Verificação
import express from 'express'
import { createHmac, timingSafeEqual } from 'node:crypto'

const app = express()
const SECRET = process.env.JUNGLE_WEBHOOK_SECRET

app.post('/webhooks/jungle', express.raw({ type: 'application/json' }), async (req, res) => {
  const expected = createHmac('sha256', SECRET).update(req.body).digest('hex')
  const got = String(req.header('x-signature') ?? '')
  const ok = got.length === expected.length &&
    timingSafeEqual(Buffer.from(got), Buffer.from(expected))
  if (!ok) return res.status(401).end()

  const evt = JSON.parse(req.body.toString('utf8'))
  res.status(200).end()

  if (await jaProcessado(evt.data.transactionId, evt.event)) return
  if (evt.event === 'transaction.paid' && evt.data.status === 'PAID') {
    await liberarPedido(evt.data.metadata.pedido_id)
  }
})
// Cloudflare Workers, Next.js edge, Deno, Bun
export async function POST(req) {
  const raw = await req.text()
  const sig = req.headers.get('x-signature') ?? ''
  const key = await crypto.subtle.importKey(
    'raw', new TextEncoder().encode(process.env.JUNGLE_WEBHOOK_SECRET),
    { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])
  const mac = new Uint8Array(await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(raw)))
  const hex = [...mac].map(b => b.toString(16).padStart(2, '0')).join('')
  let diff = hex.length ^ sig.length
  for (let i = 0; i < hex.length; i++) diff |= hex.charCodeAt(i) ^ sig.charCodeAt(i)
  if (diff !== 0) return new Response(null, { status: 401 })

  const evt = JSON.parse(raw)
  // enfileire e processe de forma idempotente
  return new Response(null, { status: 200 })
}
import hmac, hashlib, os
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["JUNGLE_WEBHOOK_SECRET"].encode()

@app.post("/webhooks/jungle")
def jungle_webhook():
    raw = request.get_data()                      # corpo BRUTO
    expected = hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, request.headers.get("X-Signature", "")):
        abort(401)
    evt = request.get_json()
    if evt["event"] == "transaction.paid" and evt["data"]["status"] == "PAID":
        fila.enqueue(liberar_pedido, evt["data"]["metadata"]["pedido_id"])
    return "", 200
<?php
$raw = file_get_contents('php://input');
$expected = hash_hmac('sha256', $raw, getenv('JUNGLE_WEBHOOK_SECRET'));
$got = $_SERVER['HTTP_X_SIGNATURE'] ?? '';

if (!hash_equals($expected, $got)) {
  http_response_code(401);
  exit;
}

$evt = json_decode($raw, true);
http_response_code(200);

if ($evt['event'] === 'transaction.paid' && $evt['data']['status'] === 'PAID') {
  liberarPedido($evt['data']['metadata']['pedido_id']);
}
func jungleWebhook(w http.ResponseWriter, r *http.Request) {
	raw, _ := io.ReadAll(r.Body)
	mac := hmac.New(sha256.New, []byte(os.Getenv("JUNGLE_WEBHOOK_SECRET")))
	mac.Write(raw)
	expected := hex.EncodeToString(mac.Sum(nil))
	if !hmac.Equal([]byte(expected), []byte(r.Header.Get("X-Signature"))) {
		w.WriteHeader(http.StatusUnauthorized)
		return
	}
	var evt struct {
		Event string `json:"event"`
		Data  struct {
			TransactionID string         `json:"transactionId"`
			Status        string         `json:"status"`
			Metadata      map[string]any `json:"metadata"`
		} `json:"data"`
	}
	json.Unmarshal(raw, &evt)
	w.WriteHeader(http.StatusOK)
	// idempotência por evt.Data.TransactionID + evt.Event
}
Ferramenta

Laboratório de assinatura

Calcule o X-Signature de um webhook, de uma requisição assinada ou de um saque e compare com o que o seu código gera. Tudo roda localmente com Web Crypto — nada sai do seu navegador.

Mensagem assinada (↵ = \n)
X-Signature esperado
—

Use um segredo de teste sempre que possível. Esta página não faz nenhuma requisição com o que você digita.

Saques

Criar saque via PIX

POST/gateway/withdrawalsX-Api-Key + assinatura + Idempotency-Key

Transfere do seu saldo disponível para uma chave PIX. Este endpoint tem proteção própria, sempre ligada — a API Key sozinha nunca saca:

  1. X-Api-Key identifica a dashboard.
  2. Allowlist de IP (opt-in): cadastrando IPs no painel, chamadas de fora recebem 403.
  3. Assinatura HMAC com o Webhook Secret (não a API Key), incluindo a Idempotency-Key.
  4. Idempotency-Key obrigatória: repetir a mesma key devolve o mesmo saque (200) sem novo débito.
canônico do saque
canonical   = METHOD \n path \n body \n timestamp \n nonce \n idempotencyKey
X-Signature = hex( HMAC-SHA256( chave = Webhook Secret, mensagem = canonical ) )

# path = /api/gateway/withdrawals · timestamp em ms (±5 min) · nonce único

Body

amountnumberobrigatório

Valor em reais que cai na chave PIX. Pode haver um mínimo configurado para a sua conta.

pixKeystringopcional

Chave de destino. Envie junto com pixKeyType — os dois ou nenhum. Sem eles, usa a chave PIX cadastrada no painel.

pixKeyTypeenumopcional
cpfcnpjemailphonerandom

Sem distinção de maiúsculas. Aliases: evp, aleatoria, chave_aleatoria → random; telefone, celular → phone; e-mail → email.

callbackUrlstring (URL)opcional

Recebe os webhooks withdrawal.*.

Resposta e taxa

CampoSignificado
statusSempre PENDING na criação. A confirmação chega por webhook ou consulta.
amountO que você pediu — e o que cai no PIX
feeAmountTaxa do saque (0 se a conta não tem taxa)
netAmountO que cai na chave PIX (= amount)
totalAmountO que sai do saldo: amount + feeAmount

Retry seguro: guarde a Idempotency-Key junto do pedido de saque e reuse-a em timeout ou 5xx. Gere uma nova só para um saque novo. Nonce e timestamp, esses sim, são novos a cada tentativa.

POST/gateway/withdrawals
import { createHmac, randomUUID } from 'node:crypto'

const API = 'https://app.junglepagamentos.com/api'

export async function sacar({ amount, pixKey, pixKeyType, idempotencyKey }) {
  const path = '/api/gateway/withdrawals'
  const raw = JSON.stringify({ amount, pixKey, pixKeyType })
  const ts = Date.now().toString()
  const nonce = randomUUID()
  const canonical = `POST\n${path}\n${raw}\n${ts}\n${nonce}\n${idempotencyKey}`
  const signature = createHmac('sha256', process.env.JUNGLE_WEBHOOK_SECRET)
    .update(canonical).digest('hex')

  const res = await fetch(`${API}/gateway/withdrawals`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': process.env.JUNGLE_API_KEY,
      'X-Signature': signature,
      'X-Timestamp': ts,
      'X-Nonce': nonce,
      'Idempotency-Key': idempotencyKey,   // a MESMA em todo retry
    },
    body: raw,                              // exatamente a string assinada
  })
  const body = await res.json()
  if (!body.success) throw new Error(`${res.status} ${body.error}`)
  return body.data  // { withdrawalId, status: 'PENDING', amount, feeAmount, netAmount, totalAmount }
}
import hmac, hashlib, json, os, time, uuid, requests

def sacar(amount, idempotency_key, pix_key=None, pix_key_type=None):
    path = "/api/gateway/withdrawals"
    payload = {"amount": amount}
    if pix_key:
        payload |= {"pixKey": pix_key, "pixKeyType": pix_key_type}
    raw = json.dumps(payload, separators=(",", ":"))
    ts = str(int(time.time() * 1000))
    nonce = str(uuid.uuid4())
    canonical = f"POST\n{path}\n{raw}\n{ts}\n{nonce}\n{idempotency_key}"
    sig = hmac.new(os.environ["JUNGLE_WEBHOOK_SECRET"].encode(),
                   canonical.encode(), hashlib.sha256).hexdigest()
    r = requests.post(
        "https://app.junglepagamentos.com" + path,
        data=raw,                                  # a MESMA string assinada
        headers={
            "Content-Type": "application/json",
            "X-Api-Key": os.environ["JUNGLE_API_KEY"],
            "X-Signature": sig, "X-Timestamp": ts, "X-Nonce": nonce,
            "Idempotency-Key": idempotency_key,
        },
        timeout=20,
    )
    body = r.json()
    if not body["success"]:
        raise RuntimeError(f'{r.status_code} {body["error"]}')
    return body["data"]
function sacar(float $amount, string $idempotencyKey): array {
  $path  = '/api/gateway/withdrawals';
  $raw   = json_encode(['amount' => $amount]);
  $ts    = (string) round(microtime(true) * 1000);
  $nonce = bin2hex(random_bytes(16));
  $canonical = "POST\n{$path}\n{$raw}\n{$ts}\n{$nonce}\n{$idempotencyKey}";
  $sig = hash_hmac('sha256', $canonical, getenv('JUNGLE_WEBHOOK_SECRET'));

  $ch = curl_init('https://app.junglepagamentos.com' . $path);
  curl_setopt_array($ch, [
    CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => $raw,
    CURLOPT_HTTPHEADER => [
      'Content-Type: application/json',
      'X-Api-Key: ' . getenv('JUNGLE_API_KEY'),
      "X-Signature: {$sig}", "X-Timestamp: {$ts}", "X-Nonce: {$nonce}",
      "Idempotency-Key: {$idempotencyKey}",
    ],
  ]);
  $body = json_decode(curl_exec($ch), true);
  if (empty($body['success'])) throw new RuntimeException($body['error'] ?? 'erro');
  return $body['data'];
}
201 Created
{
  "success": true,
  "data": {
    "withdrawalId": "c9a24f1e-…",
    "status": "PENDING",
    "amount": 150.00,
    "feeAmount": 10.00,
    "netAmount": 150.00,
    "totalAmount": 160.00
  }
}
Saques

Consultar saque

GET/gateway/withdrawals/{id}X-Api-Key

Só a API Key basta. Retorna 404 Withdrawal not found para id de outra dashboard ou inexistente.

PENDING→ ENVIADOou RECUSADO CANCELADO BLOQUEADO

ENVIADO = PIX concluído. RECUSADO e CANCELADO devolvem o valor ao saldo disponível.

GET/gateway/withdrawals/{id}
curl 'https://app.junglepagamentos.com/api/gateway/withdrawals/c9a24f1e-…' \
  -H "X-Api-Key: $JUNGLE_API_KEY"
200 OK
{
  "success": true,
  "data": {
    "withdrawalId": "c9a24f1e-…",
    "status": "ENVIADO",
    "amount": 150.00,
    "feeAmount": 10.00,
    "netAmount": 150.00,
    "totalAmount": 160.00,
    "pixKeyType": "email",
    "createdAt": "2026-07-23T12:00:00.000Z"
  }
}
Saques

Webhooks de saque

Com callbackUrl no saque, você recebe withdrawal.created, withdrawal.paid e withdrawal.failed, assinados no X-Signature com o mesmo Webhook Secret — verifique exatamente como nos webhooks de transação.

  • pixKeyMasked é sempre mascarada; a chave completa nunca trafega em webhook.
  • failureReason só vem preenchido em withdrawal.failed.
  • Idempotência por withdrawalId + event.
POST→ callbackUrl do saque
{
  "event": "withdrawal.paid",
  "data": {
    "withdrawalId": "c9a24f1e-…",
    "amount": 100.00,
    "feeAmount": 0,
    "netAmount": 100.00,
    "totalAmount": 100.00,
    "status": "ENVIADO",
    "pixKeyType": "email",
    "pixKeyMasked": "lo***@exemplo.com",
    "failureReason": null,
    "createdAt": "2026-07-23T12:00:00.000Z",
    "updatedAt": "2026-07-23T12:05:00.000Z"
  }
}
Segurança

Assinatura de requisições (anti-replay)

Opcional por dashboard. Com “exigir assinatura” ligado, toda chamada a /gateway/* precisa, além da API Key, de uma assinatura HMAC com janela de tempo e nonce — uma API Key capturada deixa de bastar para repetir requisições. Desligado, só a X-Api-Key é necessária (e, se você enviar assinatura, ela é validada).

canônico
canonical   = METHOD \n path \n body \n timestamp \n nonce
X-Signature = hex( HMAC-SHA256( chave = API Key, mensagem = canonical ) )

# path  = pathname sem query, com /api   → /api/gateway/charges
# body  = "" em GET
# timestamp = epoch em ms, até ±5 min do relógio do servidor
# nonce = único por requisição (UUID); reusar = replay
HTTP 401 — errorCorreção
Signature required for this workspace (…)Envie os três headers
Missing X-Timestamp or X-NonceEnvie os dois junto com o X-Signature
Timestamp outside allowed window (±5min)Sincronize o relógio (NTP); use milissegundos
Replay detected (nonce reused)Gere nonce novo a cada chamada, inclusive em retry
Invalid signatureAssine a mesma string que vai no body; confira o path com /api
Cliente assinado
import { createHmac, randomUUID } from 'node:crypto'

const API = 'https://app.junglepagamentos.com/api'
const KEY = process.env.JUNGLE_API_KEY

export async function jungle(method, route, body) {
  const path = new URL(API + route).pathname          // /api/gateway/…
  const raw = body === undefined ? '' : JSON.stringify(body)
  const ts = Date.now().toString()
  const nonce = randomUUID()
  const sig = createHmac('sha256', KEY)
    .update(`${method}\n${path}\n${raw}\n${ts}\n${nonce}`).digest('hex')

  const res = await fetch(API + route, {
    method,
    headers: {
      'Content-Type': 'application/json', 'X-Api-Key': KEY,
      'X-Signature': sig, 'X-Timestamp': ts, 'X-Nonce': nonce,
    },
    body: method === 'GET' ? undefined : raw,
  })
  const json = await res.json()
  if (!json.success) throw new Error(`${res.status} ${json.error}`)
  return json
}

// jungle('POST', '/gateway/charges', { product: …, customer: … })
// jungle('GET', '/gateway/transactions?page=1')  → assina só o pathname
import hmac, hashlib, json, os, time, uuid, requests
from urllib.parse import urlparse

API = "https://app.junglepagamentos.com/api"
KEY = os.environ["JUNGLE_API_KEY"]

def jungle(method, route, body=None):
    path = urlparse(API + route).path
    raw = "" if body is None else json.dumps(body, separators=(",", ":"))
    ts, nonce = str(int(time.time() * 1000)), str(uuid.uuid4())
    sig = hmac.new(KEY.encode(), f"{method}\n{path}\n{raw}\n{ts}\n{nonce}".encode(),
                   hashlib.sha256).hexdigest()
    r = requests.request(method, API + route, data=raw or None, timeout=15, headers={
        "Content-Type": "application/json", "X-Api-Key": KEY,
        "X-Signature": sig, "X-Timestamp": ts, "X-Nonce": nonce,
    })
    out = r.json()
    if not out["success"]:
        raise RuntimeError(f'{r.status_code} {out["error"]}')
    return out
Segurança

Checklist de produção

O que separa uma integração que funciona no teste de uma que aguenta Black Friday.

Segredos no servidor

API Key e Webhook Secret em variável de ambiente. Nunca no navegador, no app ou no Git.

Assinatura sempre validada

Webhook sem X-Signature válido é descartado com 401, sem processar nada.

Idempotência

Deduplique webhooks por id + evento. Reuse a Idempotency-Key no retry de saque.

Responda rápido

200 em milissegundos, trabalho pesado numa fila. Webhook lento é webhook perdido.

Reconciliação diária

Varra GET /gateway/transactions e compare com seus pedidos. Guarde endToEndId.

Tolerância a mudanças

Ignore campos novos e trate null (pixCode em boleto, endToEndId antes do pagamento).

Inteligência artificial

Skills para agentes de IA

Uma skill é um manual que o agente carrega sozinho quando o assunto aparece. Instale a da Jungle e peça “integre a Jungle no meu checkout” — o Claude segue o contrato desta documentação, sem inventar campo nem status.

skills/jungle-api-integracao

Integração com a API

Para quem vai escrever código: backend, loja, ERP, bot.

  • Cobrança PIX, checkout hospedado e reconciliação
  • Endpoint de webhook com verificação HMAC (Node, Edge, Python, PHP)
  • Saque assinado com Idempotency-Key
  • Checklist final que o agente confere antes de entregar
Baixar .zip Ver SKILL.md
skills/jungle-mcp-analise

Análise de conversão (MCP)

Para quem quer respostas: gestor, tráfego, operação.

  • Conecta o MCP no Claude Code, Cursor, VS Code ou Desktop
  • Roteiro de análise: frescor → funil → maior queda → causa
  • Lê taxa com denominador e IC95 — não conclui com amostra pequena
  • Trata UTM e texto de terceiros como dado, nunca como instrução
Baixar .zip Ver SKILL.md

Instalar

Onde você usa IA?
# skill global (vale para todos os projetos)
for s in jungle-api-integracao jungle-mcp-analise; do
  mkdir -p ~/.claude/skills/$s
  curl -fsSL {{ORIGIN}}/skills/$s/SKILL.md -o ~/.claude/skills/$s/SKILL.md
done

# ou só neste repositório: troque ~/.claude por ./.claude
foreach ($s in 'jungle-api-integracao','jungle-mcp-analise') {
  $dir = "$HOME\.claude\skills\$s"
  New-Item -ItemType Directory -Force $dir | Out-Null
  Invoke-WebRequest "{{ORIGIN}}/skills/$s/SKILL.md" -OutFile "$dir\SKILL.md"
}
1. Baixe o .zip da skill (botões acima).
2. No Claude: Configurações → Capacidades → Skills → Enviar skill.
3. Selecione o .zip. Pronto: a skill aparece na lista e é ativada
   sozinha quando você falar de integrar a Jungle ou analisar vendas.
Agentes sem suporte a skills leem a documentação inteira em texto:

  {{ORIGIN}}/llms-full.txt   → tudo em Markdown, um arquivo
  {{ORIGIN}}/llms.txt        → índice curto
  {{ORIGIN}}/openapi.json    → OpenAPI 3.1 das rotas do seller

Cursor: Settings → Indexing & Docs → Add Doc → cole a URL do llms-full.txt
e cite com @Docs. Ou salve o SKILL.md como regra em .cursor/rules/.

Prompt pronto

Cole no seu agente depois de instalar a skill (ou junto do link do llms-full.txt). Clique para copiar:

Inteligência artificial

MCP de conversão do checkout

Servidor Model Context Protocol somente leitura que dá ao seu assistente de IA os mesmos dados de vendas e de conversão do checkout que você vê na dashboard. Pergunte em português: “em que etapa eu perco mais comprador no celular?”, “minhas vendas estão chegando no Meta com fbp?”.

EndpointPOST https://app.junglepagamentos.com/api/mcp
TransporteStreamable HTTP, stateless (sem SSE, sem sessão)
AutenticaçãoAuthorization: Bearer jmcp_…
Servidorjungle-checkout-analytics · 1.0.0
Protocolos2025-06-18 (padrão), 2025-03-26, 2024-11-05

1. Gere o token

No painel, Conta → Claude / MCP, escolha a dashboard e gere um token. Ele aparece uma única vez (jmcp_ + 64 hex). Um token = uma dashboard: tudo o que o MCP devolve é filtrado por ela, e nenhum parâmetro troca de conta. Revogou, parou na hora.

2. Conecte o seu cliente

Escolha ao lado. Troque jmcp_SEU_TOKEN pelo token gerado.

O conector web do claude.ai exige OAuth, que ainda não está disponível — use Claude Code, Claude Desktop, Cursor, VS Code ou qualquer cliente MCP com transporte HTTP e header de autorização.

3. Pergunte

Cliente
claude mcp add --transport http jungle \
  https://app.junglepagamentos.com/api/mcp \
  --header "Authorization: Bearer jmcp_SEU_TOKEN"

# confira
claude mcp list
// ~/.cursor/mcp.json
{
  "mcpServers": {
    "jungle": {
      "type": "http",
      "url": "https://app.junglepagamentos.com/api/mcp",
      "headers": { "Authorization": "Bearer jmcp_SEU_TOKEN" }
    }
  }
}
// .vscode/mcp.json
{
  "servers": {
    "jungle": {
      "type": "http",
      "url": "https://app.junglepagamentos.com/api/mcp",
      "headers": { "Authorization": "Bearer jmcp_SEU_TOKEN" }
    }
  }
}
// claude_desktop_config.json — ponte mcp-remote (requer Node 18+)
{
  "mcpServers": {
    "jungle": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote",
        "https://app.junglepagamentos.com/api/mcp",
        "--header", "Authorization:${JUNGLE_MCP_AUTH}"
      ],
      "env": { "JUNGLE_MCP_AUTH": "Bearer jmcp_SEU_TOKEN" }
    }
  }
}
curl -s https://app.junglepagamentos.com/api/mcp \
  -H 'Authorization: Bearer jmcp_SEU_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"data_freshness","arguments":{}}}'

O token dá leitura das suas vendas. Não cole em chat, print ou repositório. Arquivos de configuração com token ficam fora do Git.

Inteligência artificial

As 16 ferramentas

Todas somente leitura (readOnlyHint: true). Onde há período: from, to e exclude_test (padrão true). Parâmetro desconhecido é recusado com uma mensagem que o modelo lê e corrige.

Etapas do funil

-1 sem etapa→ 0 aberto→ 1 identificação→ 2 entrega→ 3 pagamento escolhido→ 4 tentativa→ 5 pago

Roteiro que funciona: describe_schema → data_freshness → get_checkout_funnel → aprofundar onde está o biggest_drop (get_field_errors, get_page_performance, get_exit_behavior) → casos concretos com get_checkout_sessions e get_checkout_events.

Inteligência artificial

Protocolo, dados e limites

Convenções dos dados

  • Dinheiro em centavos inteiros (*_cents): 12990 = R$ 129,90. Diferente da API REST, que usa reais.
  • Datas em ISO UTC, sempre acompanhadas de <campo>_brt em horário de Brasília. from/to aceitam YYYY-MM-DD (Brasília); to é exclusivo, mas data pura inclui o dia inteiro.
  • Taxas vêm como {numerator, denominator, rate, ci95} — intervalo de confiança de 95% (Wilson). Olhe o denominador antes de concluir.
  • Período máximo de 90 dias por consulta; padrão, últimos 7.
  • exclude_test tira sessões de teste não pagas das métricas de funil; vendas nunca são excluídas.

Privacidade

  • Nome, e-mail, documento, telefone e endereço do comprador nunca saem.
  • customer_hash: HMAC com chave por empresa — conta recompra, não é reversível e não cruza contas.
  • CEP só com 3 dígitos (zip3). Eventos de página guardam o nome do campo e o código do erro, nunca o valor digitado.

Texto de terceiros é dado, não instrução. utm_*, nome de produto, cupom, external_ref e mensagens de erro de JS vêm de compradores, anúncios e scripts. O servidor avisa o modelo disso — e você não deve dar ferramentas de escrita ao mesmo agente sem revisão humana.

Limites

Requisições120/min por token · 300/min por empresa → 429 + Retry-After
Corpo256 KB → 413
Lote JSON-RPCnão suportado (-32600)
Páginalimit ≤ 100 (listas), ≤ 300 (eventos de uma sessão)
Resultado~50 KB; acima disso vem truncated: true
RPCJSON-RPC 2.0
// → POST /api/mcp
{ "jsonrpc": "2.0", "id": 1, "method": "initialize",
  "params": { "protocolVersion": "2025-06-18", "capabilities": {},
              "clientInfo": { "name": "meu-cliente", "version": "1.0" } } }

// ←
{ "jsonrpc": "2.0", "id": 1, "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "tools": { "listChanged": false } },
    "serverInfo": { "name": "jungle-checkout-analytics", "version": "1.0.0" },
    "instructions": "…" } }
// → POST /api/mcp
{ "jsonrpc": "2.0", "id": 7, "method": "tools/call",
  "params": { "name": "get_checkout_funnel",
              "arguments": { "from": "2026-09-01", "to": "2026-09-14", "group_by": "device" } } }

// ← resultado vem em texto (JSON) e estruturado
{ "jsonrpc": "2.0", "id": 7, "result": {
    "content": [ { "type": "text", "text": "{\"period\":{…},\"rows\":[…]}" } ],
    "structuredContent": { "period": { … }, "rows": [ … ] },
    "isError": false } }
// Falha de auth → HTTP 401 + WWW-Authenticate: Bearer realm="jungle-mcp"
{ "jsonrpc": "2.0", "id": null,
  "error": { "code": -32001, "message": "Unauthorized: …" } }

// Rate limit → HTTP 429 + Retry-After
{ "jsonrpc": "2.0", "id": 3, "error": { "code": -32002, "message": "…" } }

// Erro da FERRAMENTA não é erro JSON-RPC: volta como resultado
{ "jsonrpc": "2.0", "id": 9, "result": {
    "content": [ { "type": "text", "text": "Período maior que 90 dias." } ],
    "isError": true } }

// -32700 JSON inválido · -32600 request inválido · -32601 método
// -32602 ferramenta inexistente ou arguments inválido
Referência

Status e eventos

Transação — status

PENDINGAguardando pagamento
PAIDPago — libere o pedido
FAILEDFalhou
EXPIREDVenceu sem pagamento
MEDPago e depois contestado/devolvido — trate como não pago

Saque — status

PENDINGAceito, saldo reservado
ENVIADOPIX concluído
RECUSADORecusado — valor volta ao saldo
CANCELADOCancelado — valor volta ao saldo
BLOQUEADOBloqueado pela plataforma — não foi enviado

Eventos de webhook

EventoQuandoDestino
transaction.createdCobrança criadacallbackUrl da cobrança
transaction.paidPagamento confirmado
transaction.failedFalha no pagamento
transaction.expiredVenceu sem pagamento
withdrawal.createdSaque aceitocallbackUrl do saque
withdrawal.paidPIX enviado
withdrawal.failedRecusado/cancelado (failureReason)
Referência

Erros

Todo erro volta como { "success": false, "error": "…" }. Use o HTTP status para decidir; a mensagem é para log e diagnóstico.

HTTPSignificadoExemplos de errorO que fazer
400Requisição inválidavalidação de campo · Idempotency-Key header is required · Saldo insuficiente · Valor abaixo do mínimo permitido para saque (R$ …) · Informe pixKey/pixKeyType no body ou cadastre uma chave PIX no painel antes de sacar via APICorrija o body. Não repita igual.
401Não autenticadoX-Api-Key header required · Invalid API key · Invalid signature · Replay detected (nonce reused) · Timestamp outside allowed window (±5min)Confira chave, relógio e assinatura.
403Sem permissãoWorkspace is not active · Company account is not active · IP não autorizado · Saques bloqueados por determinação administrativa.Verifique a conta e a allowlist no painel.
404Não encontradoWithdrawal not found · checkout ou sessão inexistenteConfira o id e a dashboard da API Key.
429Muitas requisições—Espere e tente de novo com backoff exponencial.
5xxErro do nosso ladomensagem genéricaRetry com backoff. Em saque, reuse a mesma Idempotency-Key.
Referência

Novidades

Toda mudança é aditiva: nada do que você já envia muda de resultado.

Checkout não pago expira em 1 dia aditivo

GET /checkout/sessions/{id} ganha expired. Para vender de novo, crie outra sessão.

Taxa de saque por cima do valor aditivo

feeAmount, netAmount e totalAmount no saque. Sem taxa, a resposta é idêntica à de antes.

Valor mínimo de saque por conta aditivo

Padrão: sem mínimo. Quando configurado, abaixo do piso retorna 400.

MCP de conversão e externalRef novo

16 ferramentas somente leitura para o seu assistente de IA; referência do seu pedido na sessão de checkout.

Boleto e customer.ip aditivo

Boleto no checkout hospedado aparece na listagem com pixCode: null. customer.ip melhora a atribuição de anúncio.

Conversões server-side novo

GA4, Meta CAPI e TikTok Events API a partir dos identificadores de clique no metadata.

Referência

Downloads e formatos para máquina

Copiado