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.
⚡ 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 sandboxPagou? 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 recebedor2. Convenções da API
- Autenticação: header
Authorization: Bearer ezx_test_...(sandbox) ouezx_live_...(produção). As chaves são emitidas no painel, em Desenvolvedores (self-service), com scopes explícitos por chave —GET /platform/memostra 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_idpra citar no suporte.
3. Modelos: assinatura e recebimento
| Modelo | Quem paga | Para quem vai | Métodos |
|---|---|---|---|
| Assinatura (recorrente) | O assinante do plano | O dono do produto | Cartão (Pix Automático em breve) |
| Recebimento (avulso) | O cliente final | O recebedor, com split da comissão | Pix, Boleto, Cartão (até 12x) |
4. Catálogo: produtos e preços
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:
| Campo | Significado |
|---|---|
value | Valor em centavos. Em assinatura, o valor por ciclo (vitalício). |
recurrencyCycle | WEEKLY..YEARLY — presença define assinatura; ausência, avulso. |
billingTypes | Métodos aceitos (respeitando a matriz do modelo, seção 3). |
maxCycles | Assinatura que TERMINA sozinha após N ciclos (ex.: "12x sem comprometer o limite"). |
trialDays | Dias grátis antes da 1ª cobrança. |
maxInstallments | Avulso no cartão: parcelamento até Nx. |
gatewayFeeBearer / installmentFeeBearer | RECIPIENT (default) ou CUSTOMER — quem absorve a taxa do gateway/parcelamento. A taxa da Eazix Pay nunca é repassável. |
introductory | Preç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..cyclescobramintroductory.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);valueCents≠value; commaxCycles,cyclesprecisa 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 URLO 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 cicloCampos 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 };nullem 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.
- 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). - Defina a URL: no link de pagamento (
callbackUrl, painel ou API), na sessão B2B (callbackUrldoPOST /platform/checkout-sessions) ou por venda, abrindo o link com?callback=https://loja.exemplo.com.br/obrigado?pedido=123. - Tracking: além dos
utm_*e click ids, o link aceita os slots do mercadosrc,sck(Hotmart) es1,s2,s3(Kiwify) — voltam no retorno e no webhook comoorder.tracking. Qualquer outro parâmetro é descartado (LGPD). - Opcional:
?ref=pedido-123no link marca a venda com a SUA referência — volta comoezx_refno retorno eorder.referenceno 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 elePor 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=1dentro da assinatura (idscks_test_…/chg_test_…). Trateezx_test=1como teste: nunca libere produto por ela. Um retorno real nunca traz esse parâmetro. - O código do pedido (
ezx_order/orderCodena API e no webhook) é pra humano (comprovante, WhatsApp, suporte) — o idchg_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
| Evento | Quando |
|---|---|
charge.created | Cobrança criada |
charge.confirmed | Pagamento confirmado (cartão/Pix) |
charge.received | Valor compensado (boleto) |
charge.overdue | Cobrança vencida |
charge.refunded | Estorno (parcial OU total — distinga por charge.refundedCents × chargedAmountCents no enriquecimento) |
charge.refund_in_progress | Estorno em processamento no banco |
charge.refund_denied / charge.refund_cancelled | Estorno negado/cancelado pelo banco |
charge.chargeback | Chargeback aberto/em disputa |
account.activated / account.suspended | Mudanç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)
}
}| HTTP | Quando |
|---|---|
| 401 | Chave ausente/ inválida/ revogada |
| 403 | Chave sem o scope necessário |
| 404 | Recurso inexistente ou de outra plataforma (nunca vaza existência) |
| 409 | Conflito de estado (sessão já paga, Idempotency-Key reusada com corpo diferente) |
| 422 | Regra de negócio (método fora da matriz, preço inválido, valor abaixo do mínimo) |
| 429 | Rate 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.