Última atualização: 27 de Setembro de 2026
API e webhooks
Leia os pedidos, o catálogo, os clientes e as assinaturas da sua loja, gere uma cobrança avulsa e receba um aviso no seu servidor a cada mudança. Tudo em JSON, autenticado por chave.
Começar
A chave nasce no painel, em Configurações → Integrações. Ela aparece UMA vez, na hora de criar: o Junne guarda só um hash, então não há como mostrá-la de novo — perdeu, cria outra e revoga a antiga.
Toda chamada leva a chave no cabeçalho Authorization. A chave já diz de qual loja são os dados: não existe parâmetro de loja em nenhuma rota, e um id de outra loja responde 404, nunca o dado.
export JUNNE_API_KEY="jn_live_..." # a sua, criada no painel
curl https://www.junne.app/api/public/v1/pedidos?limite=5 \
-H "Authorization: Bearer $JUNNE_API_KEY"A chave numa variável de ambiente, e não colada no comando: o terminal guarda o que você digita, e um curl com a chave dentro fica no histórico do shell — é de lá que ela costuma vazar depois.
A API não movimenta dinheiro. Quase todos os endpoints são de leitura; POST /cobrancas cria uma cobrança avulsa, mas quem paga é sempre o link de pagamento hospedado que a resposta devolve — é por ele que cobrança e assinatura nascem, e o Junne o hospeda.
Escopos
Cada chave carrega os escopos que você marcar, e cada rota exige um. Marque só o que a integração usa: uma chave de conciliação não precisa ler cliente.
pedidos.lerLista e detalhe dos pedidos da loja.catalogo.lerProdutos, categorias e adicionais.clientes.lerQuem já comprou nesta loja, sem os endereços salvos.assinaturas.lerPlanos, assinantes e cobranças do clube.catalogo.escreverPreço e disponibilidade dos produtos.ainda sem endpointpedidos.criarAbrir pedido de entrega ou retirada.ainda sem endpointcobrancas.criarGerar link de pagamento avulso.assinaturas.criarCriar plano de assinatura. O link de adesão é o id do plano.saldo.linkGerar link temporário que mostra o saldo.ainda sem endpointconta.lerSe a conta já pode cobrar e por qual meio.
Respostas e erros
Lista devolve { dados, proximaPagina }. Erro devolve { erro, codigo } — e o que sua integração deve tratar é o codigo. A frase em erro é para pessoa ler e pode melhorar a qualquer momento sem aviso; o código, não.
| Código | HTTP | Quando |
|---|---|---|
NAO_AUTENTICADO | 401 | Chave ausente, malformada ou revogada. |
SEM_ESCOPO | 403 | A chave existe, mas não tem o escopo daquela rota. |
NAO_ENCONTRADO | 404 | O id não existe — ou é de outra loja. |
PARAMETRO_INVALIDO | 400 | Filtro fora do formato (data, estado, limite). |
TETO_DE_USO | 429 | Limite por minuto ou por dia. Vem com Retry-After. |
PEDIDO_INVALIDO | 422 | O corpo esta correto, mas a loja recusa este pedido: valor abaixo do minimo dela, documento do pagador faltando, forma de pagamento indisponivel. A mensagem diz qual. |
CONTA_INDISPONIVEL | 422 | A conta do lojista nao recebe pagamento: suspensa, encerrada, ou a conta de pagamento ainda nao foi aberta (ele precisa concluir a verificacao). A mensagem traz o motivo, pra mostrar a ele. Repetir nao resolve. |
PIX_INDISPONIVEL | 422 | O Pix so libera depois da aprovacao do cadastro. Cobrar no cartao funciona antes disso: repita com metodo CARD. |
ERRO_INTERNO | 500 | Falha do nosso lado. Num POST, repetir pode criar uma segunda cobranca — leia a armadilha sobre retry antes de tentar de novo. |
Todo valor em dinheiro é inteiro, em centavos, em campos terminados em Cents. Datas são ISO 8601 em UTC.
Paginação
Por cursor, não por página numerada: o cursor é o id da última linha devolvida, então uma linha nova no topo não embaralha o que você já leu.
limite vai de 1 a 200 e o padrão é 50; fora da faixa, o valor é ajustado para a borda mais próxima. Quando proximaPagina vier null, acabou.
curl "https://www.junne.app/api/public/v1/pedidos?limite=100" -H "Authorization: Bearer $JUNNE_API_KEY"
# { "dados": [...], "proximaPagina": "ord_abc123" }
curl "https://www.junne.app/api/public/v1/pedidos?limite=100&cursor=ord_abc123" -H "Authorization: Bearer $JUNNE_API_KEY"Limites de uso
120 por minuto e 10.000 por dia, contados por chave. Estourar devolve 429 com Retry-After em segundos — respeite o cabeçalho em vez de tentar de novo em laço.
Sincronização inicial de catálogo grande: use limite=200 e siga o cursor, em vez de muitas chamadas pequenas.
Endpoints
- GET
/pedidospedidos.lerOs pedidos da loja, do mais recente para o mais antigo.
deData inicial, AAAA-MM-DD. Inclusiva.ateData final, AAAA-MM-DD. EXCLUSIVA — o dia informado fica de fora.estadoUm estado de pedido. Valor desconhecido devolve 400, não lista vazia.unidadeIdRestringe a um ponto de retirada ou entrega.
- GET
/pedidos/{id}pedidos.lerUm pedido pelo id.
- GET
/produtoscatalogo.lerProdutos com categoria e complementos.
categoriaIdSó os de uma categoria.disponiveltrue ou false.
- GET
/produtos/{id}catalogo.lerUm produto pelo id.
- GET
/categoriascatalogo.lerAs categorias do catálogo.
- GET
/clientesclientes.lerQuem já comprou na loja, com total gasto e data do último pedido. Sem endereços salvos.
telefoneBusca exata pelo número.
- GET
/assinaturasassinaturas.lerAssinantes do clube, com as cobranças mais recentes.
estadoFiltra pelo estado da assinatura.
- GET
/assinaturas/{id}assinaturas.lerUma assinatura pelo id.
- GET
/planosassinaturas.lerOs planos do clube.
ativotrue ou false.
- POST
/cobrancascobrancas.criarGera uma cobrança avulsa e devolve o link de pagamento.
- POST
/planosassinaturas.criarCria um plano de assinatura. O link de adesão é /assinar/{id}.
- GET
/contaconta.lerSe a conta já pode cobrar e por qual meio, antes de gerar uma cobrança que morre.
Todas as listas aceitam limite e cursor.
Gerar uma cobrança avulsa
Só valorCents, descricao e o telefone de quem paga são obrigatórios. Sem pagador.cpf: quem paga se identifica na própria tela de pagamento, então a chave não precisa conhecer o documento de quem o parceiro não tem cadastrado.
curl -X POST https://www.junne.app/api/public/v1/cobrancas \
-H "Authorization: Bearer $JUNNE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"valorCents": 15000,
"descricao": "Consulta de retorno",
"pagador": { "nome": "Maria Silva", "phone": "82991234567" },
"referenciaExterna": "paciente_482"
}'
# { "id": "ord_...", "shortCode": "A1B2C3", "url": "https://.../p/A1B2C3", "estado": "AGUARDANDO_PAGAMENTO" }referenciaExterna é opaca: o Junne só guarda e devolve, nunca interpreta. Ela volta no pedido e no webhook, pra você conciliar com o registro da sua base. metodo (PIX ou CARD) é opcional — sem ele, quem paga escolhe na tela.
Criar um plano de assinatura
nome, valorCents e intervalo (WEEKLY, MONTHLY ou YEARLY) são obrigatórios. A resposta traz url, o link de adesão do plano — e é ali, na tela hospedada, que quem vai assinar se identifica. Não existe endpoint para assinar em nome de alguém: criar a assinatura por API seria assinar um cliente sem o consentimento dele.
curl -X POST https://www.junne.app/api/public/v1/planos \
-H "Authorization: Bearer $JUNNE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"nome": "Acompanhamento mensal",
"valorCents": 19900,
"intervalo": "MONTHLY"
}'
# { "id": "pl_...", "nome": "Acompanhamento mensal", "valorCents": 19900, "intervalo": "MONTHLY",
# "url": "https://.../assinar/pl_...",
# "referenciaExterna": { "parametro": "ref", "comoUsar": "https://.../assinar/pl_...?ref=SEU_ID_DO_ASSINANTE" } }O link é por plano, não por pessoa. Para saber quem assinou, acrescente ?ref= ao link de cada assinante com o id dele na sua base: /assinar/pl_...?ref=paciente_482. O valor volta em referenciaExterna na assinatura e em todo webhook dela. Sem ele, o webhook de cobrança paga chega sem referência e só resta casar por telefone — que erra em número trocado, compartilhado e recadastro. Um ref fora do formato não derruba a adesão: ele é ignorado e a assinatura nasce sem referência.
Um pedido, por dentro
{
"id": "ord_...",
"codigo": "A1B2C3",
"criadoEm": "2026-09-27T14:02:11.000Z",
"atualizadoEm": "2026-09-27T14:31:44.000Z",
"estado": "DELIVERED",
"canal": "vitrine",
"unidadeId": "unt_...",
"cliente": { "nome": "Maria Silva", "telefone": "82991234567", "email": null },
"itens": [
{
"nome": "Pizza grande",
"quantidade": 1,
"precoUnitarioCents": 5900,
"adicionais": [{ "nome": "Borda recheada", "precoCents": 800 }]
}
],
"subtotalCents": 6700,
"entregaCents": 700,
"descontoCents": 0,
"totalCents": 7400,
"pagamento": { "tipo": "online", "metodo": "PIX", "pagoEm": "2026-09-27T14:03:50.000Z" },
"entrega": {
"endereco": "Rua das Flores, 100 — Centro",
"lat": -9.66, "lng": -35.73,
"distanciaMeters": 2300,
"taxaCotadaCents": 700
}
}Identificadores internos da cobrança não saem na API. entrega é null em retirada e consumo no local.
Webhooks
Cadastre o endereço do seu servidor em Configurações → Integrações e escolha os eventos. O Junne faz POST em JSON a cada um.
pedido.criadopedido.pagopedido.aceitopedido.recusadopedido.saiu_para_entregapedido.entreguepedido.canceladopedido.expiradoassinatura.ativadaassinatura.cobranca_pagaassinatura.cobranca_falhouassinatura.canceladaO corpo é sempre o mesmo envelope. Em evento de pedido, dados é o pedido no formato acima; em evento de assinatura, é a assinatura — os mesmos contratos da API, não um formato paralelo.
POST https://seu-servidor.com/junne
content-type: application/json
x-junne-signature: t=1774627331,v1=5a2f...
{
"id": "ev_7c1f...",
"tipo": "pedido.pago",
"criadoEm": "2026-09-27T14:03:50.000Z",
"dados": { ... }
}Conferir a assinatura
O cabeçalho x-junne-signature traz o instante do envio e um HMAC-SHA256 do texto {t}.{corpo}, com o segredo do endpoint. Compare sobre o corpo cru, antes de qualquer parse: reserializar o JSON muda bytes e quebra a conferência.
const [t, v1] = cabecalho.split(',').map((p) => p.split('=')[1])
// Rejeite o que for velho: sem isso, quem capturar um corpo válido
// reenvia quando quiser. A janela do Junne é de 5 minutos.
if (Math.abs(Date.now() - Number(t) * 1000) > 5 * 60_000) return rejeitar()
const esperado = createHmac('sha256', SEGREDO).update(`${t}.${corpoCru}`).digest('hex')
if (!timingSafeEqual(Buffer.from(esperado, 'hex'), Buffer.from(v1, 'hex'))) return rejeitar()Entrega e retentativa
Responda 2xx rápido e processe depois. Qualquer outra coisa — ou silêncio — vira retentativa, com esperas de 1, 5, 30, 120, 360 e 1440 minutos. Esgotadas as seis, a entrega é dada como falha definitiva e aparece no histórico do painel.
Redirecionamento não é seguido: o endereço que você cadastrar é o endereço que recebe.
Armadilhas
Cada uma destas já mordeu alguém — inclusive a gente. Elas não aparecem lendo a lista de campos.
pedido.pagonão quer dizer “recebi online”. Ele também sai no pagamento na entrega, junto depedido.entregue. Quem tratar “pago” como dinheiro que caiu na conta vai contar o mesmo pedido duas vezes. Olhepagamento.tipo.- A ordem de chegada não é garantida. Dois eventos do mesmo instante podem chegar trocados. Ordene por
criadoEm, nunca pela ordem em que seu servidor recebeu. - A entrega é “pelo menos uma vez”. O mesmo evento pode chegar repetido — uma resposta sua que demorou demais conta como falha e gera retentativa. Guarde o
iddo evento e ignore o que já viu. assinatura.canceladatem três nascedouros. Cancelamento pelo cliente, pelo lojista e por inadimplência chegam com o mesmo tipo. Se o seu fluxo trata os três diferente, olhe o estado da assinatura.- As cobranças da assinatura não são o histórico inteiro. O campo
cobrancastraz só as mais recentes e não é paginado. Para o histórico completo, use/pedidos. POST /cobrancasnão tem chave de idempotência. Se a sua chamada estourar o tempo, a cobrança pode ter nascido mesmo assim — e repetir cria uma segunda, com dois links pagáveis para a mesma pessoa. Em timeout ouERRO_INTERNO, não repita direto: peçaGET /pedidos(vem do mais recente para o mais antigo) e procure a suareferenciaExternanos últimos; só crie de novo se ela não estiver lá. É mais um motivo para mandar umareferenciaExternaem toda cobrança.ateé exclusiva.?de=2026-09-01&ate=2026-09-30não traz o dia 30. Para fechar o mês, peçaate=2026-10-01.
Quando algo não bater
Escreva para suporte@junne.app com o codigo do erro, o horário e o endpoint. Se for sobre webhook, o id do evento encontra a entrega no nosso lado.