eazixpayAmbiente seguro

Documentação para integradores

A Eazix Pay é a camada de pagamentos white-label do ecossistema Eazix. Sua plataforma (marketplace, SaaS ou app) usa a Eazix Pay para dar aos seus profissionais uma conta de recebimento e um checkout pronto — Pix, boleto e cartão — com split automático da sua comissão. Você cuida do seu produto; a Eazix cuida de conta digital, cobrança, repasse e conciliação.

Referência interativa: explore e dispare TODAS as chamadas com a sua chave de teste no playground da Platform API — sem instalar nada.

⚡ Primeira venda em 5 minutos

Quatro comandos com a sua chave de sandbox (ezx_test_…) e você termina com um link de pagamento aberto no navegador, pagável com Pix de teste — e o seu primeiro webhook chegando. Troque SUA_CHAVE e rode na ordem:

# 1. Valide a chave (e veja seus scopes)
curl https://api-hml.eazix.com.br/v1/platform/me \
  -H "Authorization: Bearer SUA_CHAVE"

# 2. Crie um produto
curl -X POST https://api-hml.eazix.com.br/v1/platform/products \
  -H "Authorization: Bearer SUA_CHAVE" -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-produto" \
  -d '{ "name": "Meu primeiro produto", "context": "receivable" }'
# → guarde o "id" (prd_...)

# 3. Crie um preço de R$ 5,00 (centavos!)
curl -X POST https://api-hml.eazix.com.br/v1/platform/products/PRD_ID/prices \
  -H "Authorization: Bearer SUA_CHAVE" -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-preco" \
  -d '{ "value": 500, "billingTypes": ["PIX", "CREDIT_CARD"] }'
# → guarde o "id" (prc_...)

# 4. Crie o link de pagamento permanente
curl -X POST https://api-hml.eazix.com.br/v1/platform/payment-links \
  -H "Authorization: Bearer SUA_CHAVE" -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-link" \
  -d '{ "priceId": "PRC_ID" }'
# → abra a "url" no navegador e pague com o Pix de teste da sandbox

Pagou? Consulte GET /platform/charges e veja a cobrança confirmada com a cascata de taxas — e, se você já cadastrou sua URL de webhook (seção 8), o charge.confirmed chegou assinado. É isso: o resultado da integração não é um JSON — é uma venda.

1. Como funciona

  sua plataforma                  Eazix Pay                    cliente final
  ───────────────                 ─────────                    ─────────────
  1. cria produto + preço  ───►   catálogo
  2. cria sessão/link      ───►   checkout pronto ───►  paga (Pix/boleto/cartão)
  3. recebe webhook        ◄───   charge.confirmed
                                  split: sua comissão + valor do recebedor

2. Convenções da API

  • Autenticação: header Authorization: Bearer ezx_test_... (sandbox) ou ezx_live_... (produção). As chaves são emitidas no painel, em Desenvolvedores (self-service), com scopes explícitos por chave — GET /platform/me mostra os seus.
  • Money em CENTAVOS, sempre, request e response: 14900 = R$ 149,00. Nenhum campo monetário viaja em reais.
  • Idempotency-Key é obrigatório nos POST de criação (produtos, preços, sessões, links, estornos) — sem o header, 400 IDEMPOTENCY_KEY_REQUIRED. Repita a mesma key num retry de rede e receba a MESMA resposta, sem duplicar o recurso.
  • Paginação por cursor: listagens devolvem { "data": [...], "nextCursor": "..." }; passe ?limit=&cursor=nextCursor: null = acabou.
  • Erros sempre no envelope da seção 10, com request_id pra citar no suporte.

3. Modelos: assinatura e recebimento

ModeloQuem pagaPara quem vaiMétodos
Assinatura (recorrente)O assinante do planoO dono do produtoCartão (Pix Automático em breve)
Recebimento (avulso)O cliente finalO recebedor, com split da comissãoPix, Boleto, Cartão (até 12x)
POST /platform/products            { "name": "Meu SaaS", "context": "subscription" }
POST /platform/products/:id/prices { ...campos do preço... }
GET  /platform/products            → { data: [ { ..., prices: [...] } ], nextCursor }

O preço é imutável depois de criado (quem já comprou mantém as condições — grandfathering): pra mudar, crie um preço novo e arquive o antigo (PATCH { "active": false }). Campos principais do preço:

CampoSignificado
valueValor em centavos. Em assinatura, o valor por ciclo (vitalício).
recurrencyCycleWEEKLY..YEARLY — presença define assinatura; ausência, avulso.
billingTypesMétodos aceitos (respeitando a matriz do modelo, seção 3).
maxCyclesAssinatura que TERMINA sozinha após N ciclos (ex.: "12x sem comprometer o limite").
trialDaysDias grátis antes da 1ª cobrança.
maxInstallmentsAvulso no cartão: parcelamento até Nx.
gatewayFeeBearer / installmentFeeBearerRECIPIENT (default) ou CUSTOMER — quem absorve a taxa do gateway/parcelamento. A taxa da Eazix Pay nunca é repassável.
introductoryPreço escalonado — abaixo.

Preço escalonado (valor diferente nos primeiros ciclos)

POST /platform/products/:id/prices
{
  "recurrencyCycle": "MONTHLY",
  "value": 10900,                            // fase vitalícia: R$ 109,00/mês
  "billingTypes": ["CREDIT_CARD"],
  "introductory": { "cycles": 12, "valueCents": 14900 }  // 12 primeiras: R$ 149,00
}
  • Ciclos 1..cycles cobram introductory.valueCents; do ciclo seguinte em diante, value. Vale nas duas direções (implantação que barateia depois, ou promo de entrada que encarece).
  • A virada é automática — nenhuma ação sua: no dia seguinte ao vencimento da última cobrança da fase 1, o valor da assinatura muda no gateway. O assinante recebe e-mail 7 dias antes da 1ª cobrança do novo valor (nas duas direções).
  • As condições das DUAS fases são congeladas na venda: mudar o preço depois não afeta quem já assinou.
  • Só em assinatura; o objeto é completo (cycles + valueCents); valueCentsvalue; com maxCycles, cycles precisa ser menor.

5. Cobrança: sessão de checkout e link fixo

Sessão de checkout (um comprador, uma URL)

POST /platform/checkout-sessions
Authorization: Bearer ezx_test_...
Idempotency-Key: pedido-8123
{ "priceId": "..." }                          // ou "adHoc": cobrança sem produto
→ 201 { "id": "cks_...", "url": "https://pay.eazix.com.br/pagar/cks_...", "status": "OPEN", "expiresAt": "..." }

Redirecione o comprador pra url. O checkout cuida de métodos, parcelas, máscara e tokenização de cartão — dados de cartão nunca passam pelo seu servidor. Com callbackUrl (domínio previamente aprovado), o comprador volta pro seu site após pagar; ainda assim, confie no webhook, não no redirect.

Link fixo de pagamento (URL permanente pra LP, bio ou QR)

POST  /platform/payment-links   { "priceId": "..." }     → { "url": "https://pay.eazix.com.br/l/plk_..." }
GET   /platform/payment-links
PATCH /platform/payment-links/:id  { "priceId" | "active" | ... }   // troca o preço SEM trocar a URL

O link nunca quebra: dá pra trocar o preço apontado (dentro do mesmo produto) e pausar/reativar mantendo a mesma URL — o QR impresso continua valendo.

6. Assinaturas: consulta, fases e cancelamento

GET  /platform/subscriptions                  → lista (cursor)
GET  /platform/subscriptions/:id              → detalhe
POST /platform/subscriptions/:id/cancel       { "atPeriodEnd": true|false }
GET  /platform/charges?subscriptionId=...     → as cobranças de cada ciclo

Campos que importam no DTO (assinatura escalonada incluída):

  • amountCents — o valor da PRÓXIMA cobrança (já considera a fase corrente).
  • introductory{ cycles, introAmountCents, steadyAmountCents, currentPhase: 1|2 }; null em preço único.
  • cycleCount — ciclos já cobrados; nextDueDate — próximo vencimento.

7. Renderizando seu próprio checkout

O snapshot público da sessão é aberto (sem autenticação) e tem tudo pra você montar a tela do seu jeito:

GET /checkout/sessions/:id      (público, rate-limited)
→ {
  "status": "OPEN",
  "item": {
    "kind": "price",
    "value": 14900,
    "introductory": {              // escalonado (null em preço único):
      "cycles": 12,
      "valueCents": 14900,         // o que cobra HOJE (fase 1)
      "recurringValueCents": 10900,
      "recurringAmountCents": 10900 // fase 2 JÁ com taxa repassada, se houver
    },
    ...
  },
  "payment": { "methods": [ { "billingType": "PIX", "amountCents": ..., "installments": [...] } ] }
}

payment.methods[].amountCents é sempre o total do comprador na cobrança ATUAL. Ao concluir uma assinatura, a resposta do subscribe também traz introductory: { cycles, recurringAmountCents } pra você montar a confirmação ("12× R$ 149, depois R$ 109") sem re-buscar o snapshot.

8. Webhooks

Cadastre sua URL HTTPS no painel (Desenvolvedores → Webhook) ou via PATCH /platform/webhook-settings. A cada evento, a Eazix Pay envia um POST assinado:

POST {sua URL}
Content-Type: application/json
X-Eazix-Signature: {HMAC-SHA256 hex do corpo cru, com o seu signing secret}
X-Eazix-Signature-Previous: {presente só por 48h após você rotacionar o secret}

{
  "eventId": "evt_...",          // DEDUPLICAR por aqui — entrega é at-least-once
  "event": "charge.confirmed",
  "tenantSlug": "sua-plataforma",
  "recursoId": "cks_...",
  "externalReference": "{tenantSlug}:{recursoId}",
  "occurredAt": "2026-08-28T23:59:00.000Z",   // quando OCORREU (ordene/descarte por aqui)
  "payload": { ...evento cru... },
  "subscription": {              // SÓ em cobrança de ciclo de assinatura:
    "subscriptionId": "...",
    "cycleCount": 13,
    "currentPhase": 2            // 1|2 no preço escalonado; null em preço único
  },
  "charge": { ... }, "refunds": [ ... ],  // SÓ em charge.refunded (parcial × total sem GET)
  "order": { "code": "EZX-7K3MQ9PD", "reference": "pedido-123", "tracking": { "src": "insta" }, "chargeId": "chg_..." },
  "customer": { "name": "...", "email": "...", "phone": "+5511999991234" }  // comprador (você é o controlador)
}

Contrato de entrega: at-least-once e sem garantia de ordem — deduplique por eventId e use occurredAt pra ignorar reentrega mais antiga que o estado que você já tem (replay entrega sempre o occurredAt original). Responda 2xx em até 10s (enfileire trabalho pesado); falha re-tenta com espaçamento crescente de 1min até 24h, por ~45h, e então vai pra dead-letter — que você lista e reenvia por GET/POST /platform/webhook-events (ou no painel, em Desenvolvedores → Eventos).

Validação da assinatura (Node):

import { createHmac, timingSafeEqual } from 'node:crypto'

function isValid(rawBody, signatureHeader, secret) {
  const expected = createHmac('sha256', secret).update(rawBody).digest('hex')
  const a = Buffer.from(expected)
  const b = Buffer.from(signatureHeader)
  return a.length === b.length && timingSafeEqual(a, b)
}
  • Compute o HMAC sobre o corpo cru, antes de qualquer parse.
  • Ao rotacionar o secret (painel ou API), aceite qualquer UMA das duas assinaturas durante a janela de 48h — depois só a principal.
  • Estorno e chargeback chegam pelo mesmo canal — é o fluxo reverso.

8b. Retorno pós-pagamento (callback)

Depois de pagar, o comprador pode voltar pro seu site. O retorno é experiência; a verdade é o webhook (ou GET /platform/charges/{id}) — nunca libere o produto só pela URL de retorno.

  1. Cadastre o domínio em Desenvolvedores → Domínios de retorno (ou POST /platform/callback-domains { "host": "loja.exemplo.com.br" }). Só domínios cadastrados recebem redirect (anti open-redirect).
  2. Defina a URL: no link de pagamento (callbackUrl, painel ou API), na sessão B2B (callbackUrl do POST /platform/checkout-sessions) ou por venda, abrindo o link com ?callback=https://loja.exemplo.com.br/obrigado?pedido=123.
  3. Tracking: além dos utm_* e click ids, o link aceita os slots do mercado src, sck (Hotmart) e s1, s2, s3 (Kiwify) — voltam no retorno e no webhook como order.tracking. Qualquer outro parâmetro é descartado (LGPD).
  4. Opcional: ?ref=pedido-123 no link marca a venda com a SUA referência — volta como ezx_ref no retorno e order.reference no webhook. Nunca coloque dado do comprador aqui (a URL fica em logs de terceiros).

Quando a sessão fica PAGA, o checkout redireciona pra sua URL com parâmetros ezx_* — sem dados pessoais:

https://loja.exemplo.com.br/obrigado?pedido=123
  &ezx_v=1                 // versão da assinatura
  &ezx_status=PAID
  &ezx_session=cks_...     // sessão de checkout
  &ezx_charge=chg_...      // cobrança (use no GET /platform/charges/{id})
  &ezx_order=EZX-7K3MQ9PD  // código do pedido — curto, opaco, único na sua conta
  &ezx_ref=pedido-123      // SUA referência (?ref= no link ou externalReference da sessão) — assinada junto
  &ezx_amount=14990        // total pago, em CENTAVOS
  &ezx_method=PIX          // PIX | BOLETO | CREDIT_CARD
  &ezx_ts=1760000000       // unix seconds — rejeite se tiver mais de 5 min
  &ezx_sig=...             // HMAC-SHA256 hex (abaixo)
  &ezx_test=1              // SÓ na simulação do painel (assinado junto); nunca no retorno real
  &utm_source=meta         // utm_*, gclid/fbclid/ttclid e src/sck/s1/s2/s3 que o comprador trouxe voltam com ele

Por que e como validar o ezx_sig

A URL de retorno chega pelo navegador do comprador — qualquer pessoa pode digitar ?ezx_status=PAID&ezx_amount=14990 na barra sem ter pago. Se a sua página de obrigado libera acesso, mostra "pagamento confirmado" ou marca o pedido como pago só porque viu esses parâmetros, ela foi enganada. O ezx_sig é um carimbo: prova que a URL foi montada pela Eazix Pay e que nenhum valor foi alterado no caminho.

O carimbo é calculado a partir de todos os ezx_* juntos mais o seu secret de webhook, que só você e a Eazix Pay conhecem. Seu site refaz a mesma conta com o seu secret e compara: bateu, a URL é legítima; não bateu, alguém forjou ou mexeu. Como o secret nunca vai na URL, ninguém sem ele fabrica um carimbo válido. O ezx_ts serve pra recusar URL antiga reaproveitada.

Quando validar: só se a página de obrigado decide algo com base nos parâmetros. Se ela apenas agradece, não precisa. Se libera acesso, mostra o valor ou marca o pedido, valide o ezx_sig — e, para liberar de verdade, espere o webhook.

Detalhe técnico: a chave é derivada do secret (HKDF-SHA256, info eazix-pay/callback/v1) — o secret em si nunca assina URL. A string assinada são os ezx_* (menos ezx_sig) em ordem alfabética, chave=valor url-encoded, unidos por &. Verificador em Node:

import { createHmac, hkdfSync, timingSafeEqual } from 'node:crypto'

// Devolve { valid, test }. O caminho feliz é valid && !test — a simulação do painel
// (ezx_test=1, assinado) NUNCA deve liberar produto.
function verifyReturn(url, webhookSecret) {
  const params = new URL(url).searchParams
  const sig = params.get('ezx_sig')
  const ts = Number(params.get('ezx_ts'))
  if (!sig || Math.abs(Date.now() / 1000 - ts) > 300) return { valid: false, test: false }
  const canonical = [...params.keys()]
    .filter((k) => k.startsWith('ezx_') && k !== 'ezx_sig')
    .sort()
    .map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(params.get(k))}`)
    .join('&')
  const key = Buffer.from(hkdfSync('sha256', webhookSecret, '', 'eazix-pay/callback/v1', 32))
  const expected = createHmac('sha256', key).update(canonical).digest('hex')
  const valid = expected.length === sig.length && timingSafeEqual(Buffer.from(expected), Buffer.from(sig))
  return { valid, test: valid && params.get('ezx_test') === '1' }
}

// const r = verifyReturn(location.href, SECRET)
// if (!r.valid || r.test) mostrarTelaNeutra()   // simulação ou assinatura inválida
// else mostrarObrigado(params)                  // e libere o produto SÓ pelo webhook
  • Sem signing secret configurado, a URL vem sem ezx_sig — gere o secret em Desenvolvedores → Webhook.
  • Simulação: em Desenvolvedores → Domínios de retorno, "Simular retorno" gera uma URL assinada com o seu secret real e ezx_test=1 dentro da assinatura (ids cks_test_…/chg_test_…). Trate ezx_test=1 como teste: nunca libere produto por ela. Um retorno real nunca traz esse parâmetro.
  • O código do pedido (ezx_order / orderCode na API e no webhook) é pra humano (comprovante, WhatsApp, suporte) — o id chg_ continua sendo a referência técnica.
  • ?callback= com domínio não cadastrado não bloqueia a venda: o checkout ignora o parâmetro e usa o retorno do link (ou nenhum).
  • Boleto e Pix pendentes NÃO redirecionam: o comprador fica na tela do checkout até pagar (o redirect acontece ao confirmar).

9. Referência de eventos

EventoQuando
charge.createdCobrança criada
charge.confirmedPagamento confirmado (cartão/Pix)
charge.receivedValor compensado (boleto)
charge.overdueCobrança vencida
charge.refundedEstorno (parcial OU total — distinga por charge.refundedCents × chargedAmountCents no enriquecimento)
charge.refund_in_progressEstorno em processamento no banco
charge.refund_denied / charge.refund_cancelledEstorno negado/cancelado pelo banco
charge.chargebackChargeback aberto/em disputa
account.activated / account.suspendedMudança de status do recebedor

Assinatura escalonada: a virada de fase não gera evento próprio — a 1ª cobrança do novo valor chega como charge.confirmed normal, com subscription.currentPhase: 2 no envelope.

10. Erros

Todo erro responde no envelope:

{
  "error": {
    "code": "PRICE_NOT_FOUND",
    "message": "Preço não encontrado.",
    "request_id": "req_...",        // cite no suporte
    "details": { ... }              // opcional (ex.: campos inválidos, sem dados sensíveis)
  }
}
HTTPQuando
401Chave ausente/ inválida/ revogada
403Chave sem o scope necessário
404Recurso inexistente ou de outra plataforma (nunca vaza existência)
409Conflito de estado (sessão já paga, Idempotency-Key reusada com corpo diferente)
422Regra de negócio (método fora da matriz, preço inválido, valor abaixo do mínimo)
429Rate limit — respeite Retry-After

Em caso de divergência entre esta página e o comportamento real, o comportamento real vence — fale com a equipe Eazix. Conheça a Eazix em eazix.com.br.