RunaTrack Live

RunaTrack: guia de integração

Uma única API de pagamentos. Você integra uma vez; a gateway usada por trás (Beehive, Velani, Pingu, Pague-X ou FreePay) é escolhida no painel e pode ser trocada sem alterar o seu código.

Configuração

No projeto (variáveis de ambiente, somente servidor):

Variável Valor Obrigatória
HUB_URL URL do hub, sem barra final. Ex.: https://hub-de-gateways.vercel.app sim
HUB_API_KEY Sua chave hub_live_... sim
HUB_SLUG Nome do funil deste projeto (ex.: funil1), o mesmo cadastrado em Gateway por funil no painel. Define qual gateway cobra este projeto. não (sem ele usa a gateway ativa)
HUB_WEBHOOK_SECRET Segredo de assinatura (whs_...), visível no painel só se receber webhook

A chave é secreta. Nunca a exponha no navegador (não use NEXT_PUBLIC_). Remova do projeto as variáveis das gateways antigas: elas agora ficam só no painel do hub.

Painel: acesse HUB_URL/login com a sua chave. Lá você cola as API keys das gateways, escolhe a gateway ativa e informa a URL do seu webhook.

Todas as chamadas levam o header:

Authorization: Bearer hub_live_...
Content-Type: application/json

Criar cobrança

POST /api/v1/charges

Corpo

Campo Tipo Obrigatório Descrição
amount inteiro sim Valor em centavos (1990 = R$ 19,90).
method string sim pix, boleto ou card.
customer.name string sim Nome do pagador.
customer.email string sim E-mail do pagador.
customer.document string depende CPF/CNPJ (só números). Obrigatório em Beehive, Pingu, Pague-X e FreePay.
customer.phone string depende Telefone com DDD. Obrigatório na Pingu e na FreePay.
customer.address objeto só boleto na Pague-X { zipCode, street, number, city, state, neighborhood? }.
reference string recomendado Id do pedido no seu sistema. Volta na resposta e evita duplicidade em algumas gateways.
description string não Descrição da cobrança.
ip string depende IP do comprador. Obrigatório na Beehive. Se omitido, o hub usa o IP encaminhado na requisição (normalmente o do seu servidor, então prefira enviar).
cardToken string só card Token/hash gerado no front pelo SDK da gateway (Beehive: card.hash; Pague-X: card.token; FreePay: hash do SDK). O hub nunca recebe o número do cartão.
installments inteiro não Parcelas do cartão (padrão 1).
funnel string não Slug do funil (a-z, 0-9, ., _, -; até 60). Se você mapeou este funil a uma gateway no painel (aba Funis; no projeto, basta HUB_SLUG=<slug> no .env), a cobrança usa ela; sem mapeamento, usa a gateway padrão. Também aceito no header X-Hub-Funnel. Veja Gateway por funil.
shopUrl, checkoutUrl string não URLs da loja/checkout (a Beehive as exige no metadata; padrão: URL do hub).
items lista não Produtos da cobrança, para o relatório por produto e para a Meta: `[{ id, name, price (centavos, unitário), quantity? (padrão 1), kind?: "main"
tracking objeto não Origem do comprador para o rastreamento: { visitorId?, attribution?, ua? }. Não é enviado à gateway.

Exemplo

curl -X POST "$HUB_URL/api/v1/charges" \
  -H "Authorization: Bearer $HUB_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "amount": 1990,
    "method": "pix",
    "funnel": "'"$HUB_SLUG"'",
    "reference": "pedido-123",
    "description": "Plano mensal",
    "customer": { "name": "Maria Silva", "email": "maria@ex.com", "document": "12345678900", "phone": "11999999999" },
    "ip": "189.10.20.30"
  }'

Resposta 201

{
  "id": "velani_8f2a1c",
  "gateway": "velani",
  "status": "pending",
  "amount": 1990,
  "method": "pix",
  "reference": "pedido-123",
  "pix": { "copyPaste": "00020126...", "qrCode": "data:image/png;base64,..." },
  "expiresAt": "2026-11-05T15:30:00.000Z"
}
Campo Descrição
id Id da cobrança no hub (<gateway>_<idExterno>). Guarde-o: é com ele que você consulta.
gateway Gateway que processou.
status pending, paid, failed, canceled, refunded ou chargeback.
pix.copyPaste Código "copia e cola". Sempre presente em Pix.
pix.qrCode Imagem do QR quando a gateway fornece; vazio na Beehive e na FreePay (gere o QR a partir do copyPaste).
boleto.url / boleto.barcode Link e linha digitável (boleto).
expiresAt Expiração, quando informada pela gateway.
checkoutUrl Quando presente, redirecione o comprador para essa URL (ex.: cartão com autenticação).

Idempotência (evitar cobrar em dobro)

Mande o header Idempotency-Key em cada criação (recomendado: qualquer string única por requisição de checkout, ex. o id do pedido). Sem o header, o hub usa a reference do corpo como chave.

Gateway por funil

Use quando cada funil (projeto) deve cobrar por uma gateway diferente, e você quer trocar a gateway de um sem mexer nos outros.

  1. No painel: aba Funis. Digite o nome do funil (slug, ex.: funil1), clique em Adicionar funil, escolha a gateway e clique em Salvar alterações. Sem salvar, a regra não vale.
  2. No projeto do funil: coloque no .env HUB_SLUG=funil1.
  3. No código: mande esse valor ao criar a cobrança, no corpo ("funnel": process.env.HUB_SLUG) ou no header X-Hub-Funnel. Se vierem os dois, vale o do corpo.

Regras:

Consultar cobrança

GET /api/v1/charges/{id}

curl "$HUB_URL/api/v1/charges/velani_8f2a1c" -H "Authorization: Bearer $HUB_API_KEY"

Retorna o mesmo formato da criação, com o status atual lido direto na gateway. Funciona mesmo que a gateway ativa tenha sido trocada depois da criação.

Ids que começam com ext_ são vendas registradas por você (veja Vendas de outro checkout). Nesse caso não há gateway para consultar: o hub responde a partir do próprio rastreamento, com source: "external".

Adicione ?events=1 para receber junto a timeline da cobrança (últimos 100 eventos): criação, webhook recebido/confirmado/repassado, mudanças de status, envio ao Meta/TikTok/Google, reconciliações e, nas vendas externas, uma linha de auditoria por chamada de ingestão.

curl "$HUB_URL/api/v1/charges/velani_8f2a1c?events=1" -H "Authorization: Bearer $HUB_API_KEY"

Webhook (aviso de pagamento)

  1. No painel do hub, preencha Webhook do cliente com a URL do seu projeto (HTTPS).
  2. Quando uma cobrança mudar de status, o hub faz POST nessa URL.

O hub confirma o status na API da gateway antes de avisar, então o evento reflete o estado real.

Corpo

{ "event": "charge.updated", "charge": { "id": "velani_8f2a1c", "gateway": "velani", "status": "paid", "amount": 1990, "method": "pix", "reference": "pedido-123" } }

Headers

Header Conteúdo
x-hub-signature sha256= + HMAC-SHA256 (hex) do corpo bruto usando HUB_WEBHOOK_SECRET.
x-hub-gateway Gateway de origem.

Validar a assinatura (Node)

import { createHmac, timingSafeEqual } from "crypto";

export function validHubSignature(rawBody: string, header: string | null, secret: string) {
  const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected), b = Buffer.from(header ?? "");
  return a.length === b.length && timingSafeEqual(a, b);
}

Use o corpo bruto (await req.text()), antes de fazer JSON.parse. Rejeite com 401 se a assinatura não bater.

Resposta esperada

Responda HTTP 2xx rapidamente. Qualquer outro código é tratado como falha e a gateway tentará reenviar. O mesmo evento pode chegar mais de uma vez: processe de forma idempotente (ex.: ignore se o pedido já está pago).

Evento de teste ("Testar agora" no painel)

O painel tem um botão Testar agora que manda um webhook de verdade à URL salva — mesmo header de assinatura e corpo no mesmo formato:

{ "event": "webhook.test", "charge": { "id": "test_1a2b3c", "gateway": "test", "status": "pending", "amount": 100, "method": "pix" } }

Seu sistema pode simplesmente responder 2xx e ignorar (event !== "charge.updated"), mas processe-o idempotentemente, como qualquer outro. O teste prova: URL acessível, assinatura validável e resposta rápida — o resultado aparece no próprio painel (status + latência).

Se o seu sistema ficar fora do ar

O hub não desiste do aviso: o repasse entra numa fila de reenvio com espera crescente (30 s, 1 min, 2 min, ... até 30 min, até 12 tentativas). Antes de cada reenvio o hub relê o status na gateway, então quando o seu sistema volta, recebe o status mais recente — vale o mesmo processamento idempotente. Depois das 12 tentativas o evento é marcado como abandonado (fica na timeline da cobrança, visível em GET /api/v1/charges/{id}?events=1); se a gateway reenviar o webhook original, o repasse acontece na hora.

Requisito: sua URL precisa responder 2xx rápido (sem processar dentro da própria resposta).

Rastreamento (UTMs)

O hub registra de onde veio cada venda (substitui a Utmify). O painel mostra em Rastreamento a receita paga, as vendas e a conversão por campanha, origem, mídia, criativo e termo.

  1. Cole o script em todas as páginas da loja e do checkout, antes do </head>. O snippet pronto, com o seu data-tid e a sua data-pk, está no painel:

    <script src="https://SEU-HUB/t.js" data-tid="t…" data-pk="pk_…" async></script>
    

    Ele guarda no navegador um visitorId e a última origem com UTM (utm_*, fbclid, gclid, ttclid, _fbp/_fbc) e registra as visitas.

  2. Ao criar a cobrança, envie o visitante:

    { "amount": 4990, "method": "pix", "customer": { "…": "…" },
      "tracking": { "visitorId": "<window.HubTrack.visitorId>" } }
    

    Se o checkout fica em outro domínio, leve a origem no link com window.HubTrack.decorate(urlDoCheckout), que acrescenta hub_vid e as UTMs. Seu backend também pode mandar tracking.attribution direto ({ "utm_source": "…", "utm_campaign": "…" }).

  3. Quando a gateway confirma o pagamento, a venda vira paga no painel. Isso acontece mesmo sem URL de webhook configurada.

Eventos extras: HubTrack.track("checkout_start" | "lead" | "add_to_cart"), ou o atributo data-hub-event="checkout_start" em um botão.

Sem visitorId (plano B): se o seu backend mandar ip (IP do comprador) e tracking.ua (o user agent do navegador dele), o hub procura uma visita recente com o mesmo IP e navegador (até 30 min) ou só o mesmo IP (até 10 min). O IP que chega ao hub sem o campo ip é o do seu servidor, por isso ele não é usado nessa busca. Mandar o visitorId continua sendo o mais preciso.

Vendas de outro checkout (sem passar pelo hub)

Se o pagamento não é criado pelo hub (você usa outro checkout), mande a venda para POST /api/v1/tracking/sales. Ela entra na receita, no ROAS/lucro, na atribuição por UTM e no Meta (Purchase), igual a uma cobrança do hub.

curl -X POST https://SEU-HUB/api/v1/tracking/sales \
  -H "Authorization: Bearer $HUB_API_KEY" -H "Content-Type: application/json" \
  -d '{"reference":"pedido-123","amount":4990,"status":"paid","method":"pix",
       "gateway":"appmax","visitorId":"<window.HubTrack.visitorId>",
       "items":[{"name":"Curso","price":4990}],
       "email":"maria@ex.com","phone":"11933332222"}'
Campo Obrigatório O que é
reference sim Id do pedido no seu sistema. É a chave de deduplicação.
amount sim Centavos, inteiro (> 0).
status sim paid, pending, refunded ou chargeback.
method sim pix, boleto ou card.
paidAt não ISO de quando foi aprovada. Padrão: agora, quando status=paid.
gateway não Texto livre: quem processou a venda. Padrão: external.
funnel não Slug do funil (o mesmo de Gateway por funil), para filtrar o relatório.
items ou product não Produtos: { id?, name, price (centavos), quantity?, kind? }. Sem id, o name vira o id.
email, phone não Do comprador. O hub guarda só o hash (Meta) e não devolve na resposta.
ip, ua não Plano B de atribuição (mesmas janelas acima).
visitorId não window.HubTrack.visitorId: caminho mais preciso.
attribution / utm_* não Origem enviada por você; tem prioridade sobre o último toque. As UTMs também são aceitas no topo do corpo.

Idempotência e mudança de status. A mesma reference cai sempre na mesma venda: reenviar com o mesmo status devolve 200 com duplicate: true e não duplica nada. Enviar outro status atualiza o mesmo registro — o fluxo normal é pending na criação, paid na aprovação e refunded/chargeback depois. O createdAt da venda não muda num reenvio (o dia do relatório continua sendo o da primeira venda).

Meta (Conversions API). Quando a venda está paid, o hub envia um Purchase (uma única vez por venda, event_id = id da venda, deduplicado com o Pixel do navegador). Não há InitiateCheckout para venda externa: ela já aconteceu.

Resposta (201 na primeira vez, 200 depois):

{ "id": "ext_9f2c…", "reference": "pedido-123", "status": "paid", "amount": 4990, "method": "pix",
  "gateway": "appmax", "createdAt": "2026-11-06T17:00:00.000Z", "paidAt": "2026-11-06T17:00:00.000Z",
  "attribution": { "utm_source": "facebook" }, "matchedBy": "visitor", "duplicate": false }

Erros: 401 chave inválida ou cliente desativado; 422 campo faltando ou inválido (a mensagem diz o que corrigir); 429 limite de requisições (veja Limites); 500 falha ao gravar (repita a mesma requisição — é idempotente).

Consulta e auditoria. O id devolvido (ext_...) funciona em GET /api/v1/charges/{id}. Como a venda não existe em gateway nenhuma, o hub responde a partir do próprio rastreamento (com source: "external") e, com ?events=1, devolve a timeline: a criação, cada mudança de status e uma linha de auditoria external.received por chamada aceita — quando, qual chave (hub_live_… só o prefixo), a reference e o status. Nenhum dado do comprador entra nesse log.

Servir pelo domínio da loja (recomendado)

Safari e iPhone apagam em até 7 dias (ou 24 h, quando a pessoa chega com fbclid) os cookies criados por script, e bloqueadores de anúncio barram domínios de rastreamento. Para a origem durar, sirva o rastreador pelo domínio da loja, com um proxy de /c/* para o hub. Assim o cookie hub_vid é gravado pelo servidor, no domínio e no IP da loja.

Use /c/s.js no lugar de /t.js, e o script envia os eventos para /c/e na mesma origem:

<script src="/c/s.js" data-tid="t…" data-pk="pk_…" async></script>

Repasse sempre o IP do visitante (X-Forwarded-For): ele é usado no plano B e no envio ao Meta. Um CNAME (t.loja.com.br apontando para o hub) não resolve o Safari, que trata como de terceiro o cookie de um subdomínio com IP diferente do site.

Sem proxy, o /t.js continua funcionando como antes, só com a duração menor no Safari.

Meta (Conversions API)

Em Rastreamento → Meta, cole o Pixel ID e um token de acesso da API de Conversões. A partir daí o hub envia ao Meta, direto do servidor:

Quando Evento event_id
A cobrança é criada (Pix/boleto gerado) InitiateCheckout <id da cobrança>:ic
A gateway confirma o pagamento Purchase (valor em BRL) <id da cobrança>

Gasto, ROAS e lucro (Meta, Google e TikTok)

O hub importa o gasto das três redes e soma tudo no mesmo relatório. Cada uma é configurada no seu card do Rastreamento:

Rede O que configurar Como é lido
Meta Ads ID da conta (act_…) + token com ads_read (pode ser um token separado do de Conversões) Insights por anúncio/dia
Google Ads Customer ID (10 dígitos), developer token e o OAuth das conversões (client id, client secret, refresh token) GAQL (searchStream): cost_micros por anúncio/dia
TikTok Ads Advertiser ID (Advertising Manager) + o access token já usado nas conversões Reporting API integrada, por anúncio/dia

Em Custos, informe a taxa da gateway (%), a taxa fixa por venda (R$), os impostos (%) e o custo do produto (%). O painel mostra:

Métrica Conta
ROAS receita paga ÷ gasto
CPA gasto ÷ vendas pagas
Custos receita × (taxa % + impostos % + produto %) + taxa fixa × vendas pagas
Lucro receita − gasto − custos

O gasto entra em cada linha da tabela quando o agrupamento é Campanha, Conjunto ou Criativo e a UTM leva o id do Meta (nome|{{campaign.id}}, {{adset.id}}, {{ad.id}}, como no modelo de UTM acima). Sem o id, o hub tenta casar pelo nome. Campanhas que gastaram e não venderam aparecem com lucro negativo. Nos demais agrupamentos, o gasto entra só no total.

O payload do painel traz spendByNetwork ({ meta, google, tiktok }, em centavos) e a soma dos três é exatamente o totals.spend. O gasto de uma conta que não está em BRL não é somado (o hub não converte moeda) e o painel avisa qual rede ficou de fora. Os dias do painel seguem o fuso da conta de anúncios, lido na sincronização (padrão: America/Sao_Paulo), para o dia das vendas bater com o do gasto.

Sem conectar a API? Use a importação manual (POST /api/panel/tracking/spend-import, veja abaixo).

Importar gasto manualmente (CSV ou JSON)

Para quem não conecta a API da rede: POST /api/panel/tracking/spend-import com a sessão do painel.

curl -X POST "$HUB_URL/api/panel/tracking/spend-import" -H "Content-Type: text/csv" --data-binary @gasto.csv
date,network,campaignId,campaignName,spend,currency
2026-11-01,google,1234567890,Campanha de busca,4990,BRL
2026-11-01,tiktok,1789...,Criativo TikTok,2500,BRL
Coluna Obrigatória O que é
date sim Dia do gasto (YYYY-MM-DD).
network não meta, google ou tiktok. Ausente = meta.
campaignId sim Id da campanha na rede (é o que liga a campanha ao funil).
campaignName não Nome exibido. Ausente = o id.
spend sim Centavos inteiros (4990 = R$ 49,90).
currency não Moeda da conta. Ausente = BRL. Diferente de BRL é gravado, mas não somado.

ROAS por funil

Cada funil pode ter uma ou várias campanhas, de qualquer rede. Em Rastreamento → Funis das campanhas, escolha o funil de cada campanha — o hub já sugere o que consegue deduzir pela UTM.

O painel recebe byFunnel (uma linha por funil: receita, gasto, lucro, ROAS e vendas pagas) e unmappedSpend. ROAS é null quando o funil não tem gasto atribuído (não é "infinito"). Com ?funnel= no relatório, os totais usam a receita e o gasto daquele funil.

GET /api/panel/tracking/funnels devolve o mapa e os funis cadastrados; PUT com { "campaignFunnels": { "<id da campanha>": "<slug>" } } substitui o mapa inteiro ({} apaga). Para campanhas fora do Meta, use a chave com a rede: "google:1234567890" ou "tiktok:1789…" — ids iguais de redes diferentes não se misturam, e a chave só com número continua valendo para o Meta.

Funil por etapa

O painel mostra quantas pessoas passaram por cada etapa: visitas → checkout iniciado → lead → cobranças criadas → vendas pagas, com a taxa de cada etapa contra a anterior.

No payload, funnelSteps traz as cinco etapas (key, label, count, rate) e cada linha de UTM pode trazer steps com os mesmos contadores.

Modelo de atribuição

Por padrão a venda fica com o último toque (comportamento histórico). Dá para escolher em Rastreamento → Atribuição:

Modelo Como a venda é atribuída
last (padrão) Último toque com origem antes da cobrança.
first Primeiro toque com origem do visitante (o que o rastreador gravou na primeira visita).
linear Receita dividida igualmente entre os toques distintos dentro da janela. Hoje o hub guarda o primeiro e o último toque: se forem o mesmo, é um toque só.

Lucro real (líquido, lucro, ROAS, ROI e margem)

O relatório calcula tudo em centavos e devolve os componentes separados em totals, rows, daily e byFunnel:

líquido (net)   = vendas pagas − taxa da gateway − coprodutor/afiliado − imposto
                  − custo do produto − multa de chargeback
imposto do gasto = gasto com anúncios × adsTaxPct
lucro (profit)  = líquido − gasto com anúncios − imposto do gasto − despesas
costs           = taxas + afiliado + produto + imposto + chargeback + imposto do gasto
ROAS            = receita bruta ÷ gasto com anúncios        (N/A sem gasto)
ROI             = líquido ÷ (gasto + imposto do gasto + despesas)   (N/A sem investimento)
margem          = lucro ÷ líquido                            (N/A só quando o líquido é zero)

PUT /api/panel/tracking com { "costs": { ... } } salva os custos (aceita feePct, feeFixed, taxPct, productPct, productFixed, affiliatePct, adsTaxPct, otherFees, chargebackFee, byGateway e byProduct).

Despesas: GET /api/panel/tracking/expenses (lista + total dos últimos 30 dias), POST (cria), POST/PUT em /api/panel/tracking/expenses/<id> (edita) e DELETE (apaga). Corpo: { "name": "Editor", "amount": 50000, "period": "monthly" } (amount em centavos; once exige date).

Filtros do relatório (?account=, ?source=, ?platform=, ?product= em GET /api/panel/tracking):

Listagens: pedidos e eventos de conversão

Webhooks de plataformas de checkout

Estrutura pronta para receber a venda de plataformas de terceiros (Hubla, Kiwify, Hotmart, Ticto, Perfect Pay, Eduzz). Configure em Rastreamento → Plataformas e use a URL mostrada:

POST /api/webhooks/platform/<slug>?t=<id do cliente>

x-platform-signature: sha256=<HMAC-SHA256 do corpo bruto com o segredo>
x-platform-timestamp: <epoch em segundos>          (opcional, recomendado)

Webhooks das gateways: limites e assinatura

O webhook que a gateway chama (/api/webhooks/<cliente>/<gateway>) também é público:

Rastreamento no navegador: atribuição e privacidade

O /t.js (e o /c/s.js atrás de proxy) manda o evento para /c/e:

TikTok (Events API)

Em Rastreamento → TikTok, cole o Pixel code e um access token do Events API (Gerenciador de Eventos do TikTok → API). O Advertiser ID (Advertising Manager) é opcional: só é usado para importar o gasto. Quando a venda é paga, o hub envia CompletePayment com:

O hub não envia o evento quando a venda não tem nenhum identificador (ttclid, e-mail ou telefone): não casaria e só gastaria quota. O código de teste é opcional (aparece em Eventos de teste).

Em Rastreamento → Google Ads, informe o Customer ID (10 dígitos, com ou sem traços), a conversion action (o id numérico ou customers/<id>/conversionActions/<id>) e o acesso OAuth: client id, client secret e refresh token, além do developer token. Se a conta está dentro de um MCC, informe também o login customer id.

As mesmas credenciais servem para importar o gasto da conta (a conversion action não é necessária para isso).

Quando a venda é paga e tem gclid, o hub chama uploadClickConversions com valor em reais, moeda BRL, a data da aprovação no fuso configurado e orderId = id da venda (o Google não conta o mesmo pedido duas vezes). Sem gclid nada é enviado.

Retry e isolamento das conversões

Rede Estado na venda Reenvio
Meta capi painel (3 tentativas, 15 min × tentativas) e cron diário, até 7 dias
TikTok tiktok.sentAt / tiktok.error mesmo esquema
Google Ads google.sentAt / google.error mesmo esquema

Uma rede que falha não impede as outras: cada envio é independente e a timeline da cobrança registra tiktok.sent, tiktok.error, google.sent e google.error. Nenhum token aparece em log, resposta ou mensagem de erro.

Produtos, aprovação, reembolsos e período

Avisos de venda no celular (Telegram)

Em Rastreamento → Avisos de venda, conecte um bot do Telegram seu. Cada venda chega no celular na hora em que a gateway confirma, inclusive no iPhone.

  1. No Telegram, fale com o @BotFather, envie /newbot e cole o token no painel.
  2. Abra o bot (ou adicione-o a um grupo da equipe) e envie /start.
  3. Clique em Conectar e depois em Enviar teste.

Você escolhe o que avisar: venda aprovada, reembolso e chargeback (ligados por padrão) e Pix/boleto gerado (desligado). O aviso traz valor, método, produto, order bump e a origem (UTM). Nenhum dado pessoal do comprador vai na mensagem. Cada mudança de status avisa uma vez, mesmo que a gateway repita o webhook.

Alerta de gateway fora (operacional)

No mesmo chat, com o mesmo bot, o hub também avisa quando uma gateway de pagamento para de responder: 3 falhas de comunicação seguidas (criação, confirmação na API, reenvio ou reconciliação) geram um aviso _🚨 Gateway fora do ar_ com o último erro — um só aviso por período de queda, sem spam. Quando a gateway volta a responder, chega _✅ Gateway normalizada_. Ligue/desligue no painel de Avisos (card "Alertas operacionais"; padrão: ligado). Falha do webhook do SEU sistema não dispara esse alerta — o alerta é sobre a gateway, não sobre sua URL.

Painel no celular (instalar como app)

No celular, abra o painel no navegador e use Adicionar à tela inicial (Chrome: menu ⋮ → Instalar app; Safari: Compartilhar → Adicionar à Tela de Início). Ele abre direto no Rastreamento, em tela cheia, como um app.

Pausar, ativar e mudar orçamento

Agrupando por Campanha, Conjunto ou Criativo, a tabela mostra o status e o orçamento diário que estão no Meta, com os botões Pausar / Ativar e Orçamento. O token precisa da permissão ads_management (ler o gasto pede só ads_read).

Regras automáticas

Regras pausam, reativam ou mudam o orçamento sozinhas, a partir do gasto e das vendas reais do hub (não das conversões que o Meta reporta).

Quando rodam: no cron diário da Vercel (o mesmo do gasto) e pelo botão Rodar agora. Para rodar a cada 15–30 min, que é o que faz sentido para regras de "hoje", use um agendador externo chamando:

curl -X POST https://SEU-HUB/api/cron/rules -H "Authorization: Bearer $CRON_SECRET"

Pode ser o QStash da Upstash (o mesmo provedor do banco), o cron-job.org ou um cron do plano Pro da Vercel. Depois de configurar, defina RULES_SCHEDULER=external na Vercel para o painel parar de avisar que as regras rodam só uma vez por dia.

Cuidado com regras que se contradizem (uma pausa, outra reativa a mesma campanha): cada regra respeita a própria espera, então elas podem se alternar a cada ciclo.

UTMs sugeridas para o Meta Ads (copie no painel): utm_source=facebook&utm_medium={{placement}}&utm_campaign={{campaign.name}}|{{campaign.id}}&utm_content={{ad.name}}|{{ad.id}}&utm_term={{adset.name}}|{{adset.id}}. Os ids servem para cruzar com o gasto dos anúncios quando essa etapa entrar.

Erros

Todas as falhas retornam { "error": "mensagem" }.

HTTP Significado
400 Corpo inválido ou campo obrigatório ausente.
401 Chave ausente, inválida ou cliente desativado.
404 Cobrança não encontrada.
422 Pedido recusado: método não suportado pela gateway ativa, valor abaixo do mínimo, dado obrigatório ausente (documento, telefone, ip) ou recusa da gateway. A mensagem explica o motivo.
429 Limite de requisições por cliente (veja abaixo). Espere o Retry-After.
502 A gateway não respondeu ou deu erro. Pode tentar de novo.
503 A gateway ativa está sem API key configurada no painel.

Limites de requisições

Cada cliente tem 120 requisições por minuto em cada endpoint de escrita: POST /api/v1/charges e POST /api/v1/tracking/sales. Os dois têm orçamentos separados — um pico de cobranças não bloqueia a ingestão de vendas (e vice-versa).

Diferenças entre gateways

Beehive Velani Pingu Pague-X FreePay
Métodos pix, boleto, card pix pix pix, boleto, card¹ pix, boleto, card
Valor mínimo – R$ 5,00 R$ 1,00 boleto: R$ 5,00 R$ 1,00²
customer.document obrigatório opcional obrigatório obrigatório obrigatório
customer.phone opcional opcional obrigatório opcional obrigatório
customer.address – – – boleto –
ip do comprador obrigatório – – – recomendado
pix.qrCode (imagem) vazio sim sim sim vazio
Idempotência por reference – sim sim não –

¹ Cartão e boleto na Pague-X exigem adquirente habilitada na conta; senão a gateway recusa. ² Mínimo conservador do hub (a doc da FreePay não informa).

Se o seu sistema aceita vários métodos, trate o 422 e ofereça ao cliente os métodos que a gateway ativa suporta.

Boas práticas

Capturar IP e token de cartão

IP do comprador (ip). Envie o IP do visitante, não o do seu servidor. Atrás de proxy/CDN, ele vem no primeiro valor do header x-forwarded-for:

// Next.js / Node (rota do seu servidor)
const buyerIp = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "";

Cartão (cardToken). O hub nunca recebe número, validade nem CVV. O formulário de cartão fica no navegador do seu cliente, que usa o SDK da gateway ativa para gerar um token/hash de uso único (curta duração), e o seu servidor envia só esse token em cardToken:

Gateway Como gerar o token
Beehive BeehivePay.encrypt(...) (script https://api.conta.paybeehive.com.br/v1/js), usa o hash em cardToken.
Pague-X Hosted Fields / SDK paguex.js, usa card.token.
FreePay encrypt() do SDK da FreePay (ver "Cartão com 3DS" na doc dela).

Como o SDK depende da gateway ativa, só ofereça cartão se o seu sistema souber qual está ativa (ou fixe uma gateway para cartão). Quando a resposta trouxer checkoutUrl, redirecione o comprador.

Exemplo em Python

import os, requests

def criar_cobranca(valor_centavos, pedido, cliente, ip):
    r = requests.post(
        f"{os.environ['HUB_URL']}/api/v1/charges",
        headers={"Authorization": f"Bearer {os.environ['HUB_API_KEY']}"},
        json={
            "amount": valor_centavos, "method": "pix", "reference": pedido,
            "funnel": os.environ.get("HUB_SLUG"),  # funil deste projeto (opcional)
            "customer": cliente,  # {"name","email","document","phone"}
            "ip": ip,
        },
        timeout=25,
    )
    r.raise_for_status()  # 422 traz {"error": "..."} explicando o que faltou
    return r.json()       # guarde r.json()["id"]

Validar o webhook em Python:

import hmac, hashlib

def assinatura_valida(corpo_bruto: bytes, header: str, segredo: str) -> bool:
    esperado = "sha256=" + hmac.new(segredo.encode(), corpo_bruto, hashlib.sha256).hexdigest()
    return hmac.compare_digest(esperado, header or "")

Usar com uma IA

Para uma IA gerar o código de integração, entregue um destes (do mais simples ao mais completo):

  1. Este guia em texto puro: /docs.md (ou o botão ⬇ Baixar .md no topo da página) e cole no chat.
  2. A URL /llms.txt, para ferramentas que leem esse índice.
  3. O contrato OpenAPI: /openapi.json, para Cursor, ChatGPT/Actions e geradores de cliente.

Prompt sugerido:

Usando a API do documento anexo, crie o serviço de pagamentos no meu projeto (informe a linguagem/framework): função para criar cobrança Pix, função para consultar por id, e uma rota de webhook que valide x-hub-signature com o corpo bruto e marque o pedido como pago quando charge.status === "paid" (idempotente). Use as variáveis HUB_URL, HUB_API_KEY, HUB_SLUG (mande o valor em funnel ao criar a cobrança; ou no header X-Hub-Funnel) e HUB_WEBHOOK_SECRET; nunca exponha a chave no front.

Revise o que a IA gerar e teste com a gateway Simulado (teste) antes de usar uma gateway real.

Documentação · RunaTrack