/ Referência da API v1
API v1

Documentação da API EADPay

A API do EADPay permite consultar e criar pedidos, gerenciar produtos, cupons e webhooks, e acompanhar carrinhos abandonados a partir de qualquer sistema. Ela segue o padrão REST, recebe e devolve JSON em camelCase e usa códigos HTTP convencionais.

Todos os valores monetários são inteiros em centavos (49700 = R$ 497,00) e todas as datas seguem o padrão ISO 8601 em UTC (2026-10-31T14:02:13.530Z).

BASEhttps://checkout.sualoja.com.br/api/v1

Bibliotecas oficiais: JavaScript/Node (sdk/js), PHP (sdk/php) e Python (sdk/python), além dos apps do Zapier e do Make — instruções e downloads no painel em Conta › Desenvolvedores e app.

Autenticação

Autentique cada requisição enviando a sua chave secreta no header Authorization com o esquema Bearer. As chaves começam com vt_live_ seguido de 32 caracteres.

Para gerar uma chave, acesse Painel → API → Nova chave. O token completo é exibido uma única vez; guardamos apenas um hash SHA-256 dele. Se perder a chave, revogue-a e gere outra.

A chave dá acesso total à sua conta. Use-a somente no servidor — nunca em código que roda no navegador ou em aplicativos móveis.

Requisições sem chave, com chave inválida ou revogada recebem 401 unauthorized. O campo Último uso no painel mostra quando cada chave foi utilizada pela última vez.

curl https://checkout.sualoja.com.br/api/v1/ping \
  -H "Authorization: Bearer vt_live_9fK2mQx7Lr4Tz8Wb1Nc6Hp3Vd5Ys0Ae2"
const res = await fetch('https://checkout.sualoja.com.br/api/v1/ping', {
  headers: { Authorization: `Bearer ${process.env.VOLTRA_API_KEY}` }
});
console.log(await res.json()); // { ok: true, account: 'Minha Empresa' }
import os, requests

res = requests.get(
    "https://checkout.sualoja.com.br/api/v1/ping",
    headers={"Authorization": f"Bearer {os.environ['VOLTRA_API_KEY']}"},
)
print(res.json())  # {'ok': True, 'account': 'Minha Empresa'}

Respostas e erros

Respostas de sucesso trazem o recurso dentro de data. Listagens incluem também meta com informações de paginação.

Erros sempre têm o mesmo formato: um objeto error com code (estável, para o seu código tratar) e message (legível, em português). Erros de validação incluem fields, com a mensagem de cada campo inválido.

Sucesso · item e lista
{ "data": { "id": "prod_T7m2Kq9XrL4pW0aZ", "name": "Método Tráfego Pro" } }

{ "data": [ … ], "meta": { "page": 1, "limit": 20, "total": 148, "pages": 8 } }
Erro · 422
{
  "error": {
    "code": "validation_error",
    "message": "Verifique os campos destacados.",
    "fields": { "email": "Informe um e-mail válido", "document": "CPF inválido" }
  }
}
HTTPcodeQuando acontece
200 / 201—Requisição processada. 201 quando um recurso é criado.
400invalid_jsonO corpo da requisição não é um JSON válido.
401invalid_api_key · unauthorizedChave de API ausente, inválida ou revogada.
404not_foundO recurso não existe (ou não pertence à sua conta).
409invalid_status · slug_taken · in_useA operação conflita com o estado atual — ex.: reembolsar um pedido que não está pago, slug já em uso ou excluir um produto usado em checkouts.
413payload_too_largeCorpo da requisição acima do limite permitido.
422validation_error · invalid_coupon · invalid_amount · product_unavailableCampos inválidos (veja fields), cupom inexistente/expirado, valor abaixo de R$ 1,00 ou produto inativo.
429rate_limitedLimite de requisições por minuto excedido.
502refund_failedO gateway recusou ou não respondeu ao pedido de reembolso.
503no_gatewayNenhum gateway ativo para o método de pagamento escolhido.
500internal_errorErro inesperado. Pode tentar novamente com a mesma Idempotency-Key.

Paginação e filtros

Todas as listagens são paginadas por número de página. Os resultados vêm do mais recente para o mais antigo.

ParâmetroTipoDescrição
pageintegerPágina desejada, começando em 1. Padrão: 1.
limitintegerItens por página. Padrão 20, máximo 100.
from / todateIntervalo de datas de criação (YYYY-MM-DD), quando o recurso suporta.

O objeto meta informa page, limit, total (itens que atendem ao filtro) e pages. Para percorrer tudo, incremente page até ela ser maior que pages.

Rate limit

Cada chave pode fazer até 300 requisições por minuto. Todas as respostas trazem os headers abaixo; ao exceder o limite você recebe 429 rate_limited e deve aguardar até o horário indicado em X-RateLimit-Reset.

HeaderDescrição
X-RateLimit-LimitTotal de requisições permitidas na janela de 1 minuto.
X-RateLimit-RemainingRequisições restantes na janela atual.
X-RateLimit-ResetMomento (Unix, em segundos) em que a janela reinicia.

Idempotência

Falhas de rede acontecem. Para reenviar com segurança uma criação de pedido sem correr o risco de cobrar o cliente duas vezes, envie o header Idempotency-Key com um valor único por operação (um UUID, ou o id do pedido no seu sistema).

Durante 24 horas, qualquer nova chamada a POST /orders com a mesma chave devolve exatamente a resposta original, sem processar um novo pagamento.

Header
-H "Idempotency-Key: 2f6c0b1e-9a4d-4c55-b0f3-7d1e2a9c8b40"

Ambiente de testes

O Simulador EADPay é um gateway de testes que já vem instalado. Com ele você exercita todo o fluxo — aprovação, recusa, análise, fallback, PIX e boleto — sem movimentar dinheiro. Basta colocá-lo na fila de roteamento do método desejado em Painel → Gateways.

Cartões de teste

NúmeroResultadoUso
4111 1111 1111 1111AprovadoQualquer número válido (Luhn) aprova. Use qualquer validade futura e CVV.
4111 1111 1115 0002RecusadoSimula recusa do emissor. Com a cascata em recusas ativa, o próximo gateway é tentado.
4111 1111 1113 0004Erro de gatewaySimula indisponibilidade técnica — ideal para testar o fallback automático.
4111 1111 1112 0005Em análiseO pedido fica waiting_payment, como numa revisão antifraude.
4111 1111 0003 0009Emissor indisponívelRecusa com código 91 só no 1º gateway: a retentativa inteligente tenta o próximo gateway na mesma hora.

Vale qualquer número terminado em 0002, 0004 ou 0005 que seja válido pelo algoritmo de Luhn — os da tabela já estão prontos para copiar. O erro do final 0004 acontece só na primeira tentativa da cascata, então o gateway seguinte aprova. Com cartão salvo (renovações, 1 clique, upsell): final 0003 é recusa definitiva, 0006 saldo insuficiente e 0007 saldo insuficiente que aprova na retentativa.

PIX e boleto simulados

O PIX gera um código copia e cola no padrão EMV (com CRC16 válido) e expira no prazo configurado no checkout. O boleto gera uma linha digitável fictícia de 47 dígitos e uma página imprimível em /boleto/:orderId. Para confirmar o pagamento, use o botão Simular pagamento na página do pedido, a ação Marcar como pago no painel, ou envie um postback:

POST /postbacks/:gatewayId
curl -X POST https://checkout.sualoja.com.br/postbacks/gw_S4ndb0xVoltra01 \
  -H "Content-Type: application/json" \
  -d '{"transactionId":"sbx_tx_7Qm2Lr9Xc1","status":"paid"}'

O objeto Pedido

Um pedido representa uma tentativa de compra: os itens, o cliente, os valores calculados, o método de pagamento e o resultado em cada gateway. É o objeto que chega em data nos eventos order.*.

CampoTipoDescrição
idstringIdentificador único, prefixo ord_.
codestringCódigo legível e sequencial, ex.: VT-100318. Pode ser usado no lugar do id.
checkoutId · checkoutNamestring | nullCheckout de origem. null para pedidos criados com items pela API.
customerId · customerstring · objectCliente e uma cópia dos dados no momento da compra: name, email, document, phone, address.
itemsarrayItens com productId, name, price, quantity e type (main ou bump).
subtotalintegerSoma dos itens, em centavos.
discountintegerDesconto total (cupom + desconto do método).
interestintegerJuros do parcelamento no cartão.
totalintegerValor efetivamente cobrado.
couponobject | null{ code, discount } quando um cupom foi aplicado.
method · installmentsstring · integercard, pix ou boleto, e o número de parcelas.
statusstringVeja status do pedido.
gatewayId · gateway · gatewayNamestringGateway que processou com sucesso (ou o último tentado) e o provider (pagarme, appmax, mercadopago, asaas, sandbox).
transactionIdstringIdentificador da transação no gateway.
attemptsarrayCada tentativa de cobrança: gatewayId, provider, status (approved, refused, pending, error), message, latencyMs e at.
pixobject | null{ qrCode, expiresAt } — código copia e cola e vencimento.
boletoobject | null{ line, barcode, url, dueDate }.
cardobject | null{ brand, last4, holder }. Número completo e CVV nunca são armazenados.
utmobjectsource, medium, campaign, content, term capturados da URL do checkout.
source · metadatastring · objectOrigem (checkout ou api) e dados livres enviados por você.
historyarrayLinha do tempo de status: { status, at, note }.
createdAt · updatedAt · paidAt · refundedAtdatetimeDatas do ciclo de vida.
Exemplo · pedido aprovado após fallback
{
  "id": "ord_Zk81QmXr2LpV0c7N",
  "code": "VT-100318",
  "checkoutId": "chk_8Hq2LxP0aZ71mNcV",
  "checkoutName": "Tráfego Pro — Principal",
  "customerId": "cus_4Rt9WqLm2Xc8Vb1N",
  "customer": {
    "name": "Marina Costa",
    "email": "marina@email.com",
    "document": "39053344705",
    "phone": "11987654321",
    "address": null
  },
  "items": [
    { "productId": "prod_T7m2Kq9XrL4pW0aZ", "name": "Método Tráfego Pro", "price": 49700, "quantity": 1, "type": "main" },
    { "productId": "prod_Hc3Vn8Lq1Mz6Rt0Y", "name": "Kit Templates de Anúncios", "price": 3700, "quantity": 1, "type": "bump" }
  ],
  "subtotal": 53400,
  "discount": 0,
  "interest": 0,
  "total": 53400,
  "coupon": null,
  "method": "card",
  "installments": 1,
  "status": "paid",
  "gatewayId": "gw_P4g9Rm2Lx7Qa1Zc0",
  "gateway": "pagarme",
  "gatewayName": "Pagar.me",
  "transactionId": "or_56GXnk6T0eU88qMm",
  "attempts": [
    { "gatewayId": "gw_Ap8Lm3Qx9Rz2Vc1N", "provider": "appmax", "gatewayName": "Appmax", "status": "error",
      "message": "Tempo de resposta excedido", "latencyMs": 3004, "at": "2026-10-31T14:02:10.112Z" },
    { "gatewayId": "gw_P4g9Rm2Lx7Qa1Zc0", "provider": "pagarme", "gatewayName": "Pagar.me", "status": "approved",
      "message": "Transação autorizada", "latencyMs": 412, "at": "2026-10-31T14:02:13.528Z" }
  ],
  "pix": null,
  "boleto": null,
  "card": { "brand": "mastercard", "last4": "4018", "holder": "MARINA COSTA" },
  "utm": { "source": "facebook", "medium": "cpc", "campaign": "black-friday", "content": null, "term": null },
  "source": "checkout",
  "metadata": {},
  "history": [
    { "status": "pending", "at": "2026-10-31T14:02:09.870Z", "note": "Pedido criado" },
    { "status": "paid", "at": "2026-10-31T14:02:13.530Z", "note": "Aprovado via Pagar.me" }
  ],
  "createdAt": "2026-10-31T14:02:09.870Z",
  "updatedAt": "2026-10-31T14:02:13.530Z",
  "paidAt": "2026-10-31T14:02:13.530Z",
  "refundedAt": null
}

Status do pedido

statusSignificadoEvento
pendingPedido criado, cobrança em processamento.order.created
waiting_paymentPIX ou boleto gerado aguardando pagamento, ou cartão em análise.order.waiting_payment
paidPagamento aprovado. Libere o acesso ou envie o produto.order.paid
refusedPagamento recusado em todos os gateways tentados.order.refused
refundedValor devolvido ao cliente.order.refunded
canceledPedido cancelado antes do pagamento.order.canceled
chargebackO cliente contestou a compra junto ao banco.order.chargeback
expiredPIX ou boleto venceu sem pagamento.order.expired

Listar pedidos

GET/api/v1/orders

Retorna os pedidos da conta, do mais recente para o mais antigo, com filtros combináveis.

QueryTipoDescrição
statusstringUm dos status.
methodstringcard, pix ou boleto.
emailstringE-mail exato do cliente.
from · todatePeríodo de criação, YYYY-MM-DD.
page · limitintegerPaginação (máx. 100 por página).
curl "https://checkout.sualoja.com.br/api/v1/orders?status=paid&method=pix&from=2026-10-01&limit=50" \
  -H "Authorization: Bearer $VOLTRA_API_KEY"
const params = new URLSearchParams({ status: 'paid', method: 'pix', from: '2026-10-01', limit: '50' });
const res = await fetch(`https://checkout.sualoja.com.br/api/v1/orders?${params}`, {
  headers: { Authorization: `Bearer ${process.env.VOLTRA_API_KEY}` }
});
const { data, meta } = await res.json();
res = requests.get(
    "https://checkout.sualoja.com.br/api/v1/orders",
    params={"status": "paid", "method": "pix", "from": "2026-10-01", "limit": 50},
    headers={"Authorization": f"Bearer {API_KEY}"},
)
pedidos = res.json()["data"]
Resposta · 200
{
  "data": [ { "id": "ord_Zk81QmXr2LpV0c7N", "code": "VT-100318", "status": "paid", "total": 47215, … } ],
  "meta": { "page": 1, "limit": 50, "total": 61, "pages": 2 }
}

Obter pedido

GET/api/v1/orders/:id

Busca um pedido pelo id (ord_…) ou pelo code (VT-100318). Útil para confirmar o status antes de liberar um acesso, mesmo que você já tenha recebido o webhook.

curl https://checkout.sualoja.com.br/api/v1/orders/VT-100318 \
  -H "Authorization: Bearer $VOLTRA_API_KEY"
const res = await fetch('https://checkout.sualoja.com.br/api/v1/orders/VT-100318', {
  headers: { Authorization: `Bearer ${process.env.VOLTRA_API_KEY}` }
});
const { data: pedido } = await res.json();
if (pedido.status === 'paid') liberarAcesso(pedido.customer.email);
pedido = requests.get(
    "https://checkout.sualoja.com.br/api/v1/orders/VT-100318",
    headers={"Authorization": f"Bearer {API_KEY}"},
).json()["data"]

Criar cobrança

POST/api/v1/orders

Cria um pedido e já processa o pagamento usando o roteamento de gateways configurado. Informe um checkoutId (usa o preço, os métodos e as regras daquele checkout) ou uma lista de items com produtos do catálogo.

CampoTipoDescrição
checkoutIdstringCheckout de referência. Alternativo a items.
itemsarray[{ productId, quantity }]. Alternativo a checkoutId.
customerobrigatórioobjectname, email, document (CPF/CNPJ, só dígitos), phone e address opcional.
methodobrigatóriostringcard, pix ou boleto.
cardobjectPara cartão: number, holder, expMonth, expYear, cvv.
installmentsintegerNúmero de parcelas (padrão 1).
couponstringCódigo de cupom a aplicar.
metadataobjectDados livres devolvidos no pedido e nos webhooks.

Pagamentos recusados não geram erro HTTP: a resposta é 201 com status: "refused" e as tentativas em attempts. Sempre verifique o campo status.

curl -X POST https://checkout.sualoja.com.br/api/v1/orders \
  -H "Authorization: Bearer $VOLTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-pedido-8812" \
  -d '{
    "checkoutId": "chk_8Hq2LxP0aZ71mNcV",
    "method": "card",
    "installments": 3,
    "coupon": "BEMVINDO10",
    "customer": {
      "name": "Marina Costa",
      "email": "marina@email.com",
      "document": "39053344705",
      "phone": "11987654321"
    },
    "card": {
      "number": "4111111111111111",
      "holder": "MARINA COSTA",
      "expMonth": "09",
      "expYear": "2029",
      "cvv": "123"
    },
    "metadata": { "crmId": "8812" }
  }'
const res = await fetch('https://checkout.sualoja.com.br/api/v1/orders', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.VOLTRA_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': `crm-pedido-${crmId}`
  },
  body: JSON.stringify({
    items: [{ productId: 'prod_T7m2Kq9XrL4pW0aZ', quantity: 1 }],
    method: 'pix',
    customer: { name: 'Marina Costa', email: 'marina@email.com', document: '39053344705' },
    metadata: { crmId }
  })
});

const { data } = await res.json();
enviarWhatsApp(data.customer.phone, data.pix.qrCode);
res = requests.post(
    "https://checkout.sualoja.com.br/api/v1/orders",
    headers={"Authorization": f"Bearer {API_KEY}", "Idempotency-Key": f"crm-pedido-{crm_id}"},
    json={
        "checkoutId": "chk_8Hq2LxP0aZ71mNcV",
        "method": "boleto",
        "customer": {"name": "Marina Costa", "email": "marina@email.com", "document": "39053344705"},
    },
)
boleto = res.json()["data"]["boleto"]
print(boleto["line"], boleto["url"])
Resposta · 201 (PIX)
{
  "data": {
    "id": "ord_Qm7Xr2Lp9Vc1Nz8K",
    "code": "VT-100319",
    "status": "waiting_payment",
    "method": "pix",
    "subtotal": 49700,
    "discount": 2485,
    "total": 47215,
    "pix": {
      "qrCode": "00020101021226870014br.gov.bcb.pix…6304A1F3",
      "expiresAt": "2026-10-31T14:32:09.870Z"
    },
    …
  }
}

Reembolsar pedido

POST/api/v1/orders/:id/refund

Solicita o estorno integral ao gateway que aprovou o pagamento. Disponível apenas para pedidos com status paid; em outros casos a resposta é 409. Em caso de sucesso, o pedido passa para refunded e o evento order.refunded é disparado.

cURL
curl -X POST https://checkout.sualoja.com.br/api/v1/orders/VT-100318/refund \
  -H "Authorization: Bearer $VOLTRA_API_KEY"

Cancelar pedido

POST/api/v1/orders/:id/cancel

Cancela um pedido que ainda não foi pago — por exemplo um PIX ou boleto que o cliente desistiu de pagar. O pedido passa para canceled e o evento order.canceled é enviado. Pedidos pagos devem ser reembolsados.

cURL
curl -X POST https://checkout.sualoja.com.br/api/v1/orders/VT-100319/cancel \
  -H "Authorization: Bearer $VOLTRA_API_KEY"

Produtos

GET/api/v1/productsPOST/api/v1/products
GETPATCHDELETE/api/v1/products/:id

Produtos formam o catálogo usado pelos checkouts e pelas cobranças via API. Ao atualizar com PATCH, envie apenas os campos que quer alterar. Pedidos antigos guardam uma cópia do nome e preço, então mudanças não afetam o histórico.

CampoTipoDescrição
nameobrigatóriostringNome exibido no checkout.
priceobrigatóriointegerPreço em centavos.
descriptionstringDescrição curta.
typestringdigital, physical ou service.
imagestringURL da imagem.
skustringCódigo interno.
deliveryUrlstringLink de acesso entregue após a compra.
activebooleanProdutos inativos não podem ser vendidos.
Criar produto
curl -X POST https://checkout.sualoja.com.br/api/v1/products \
  -H "Authorization: Bearer $VOLTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Planilha Financeira Inteligente","price":4700,"type":"digital",
       "deliveryUrl":"https://membros.sualoja.com.br/planilha"}'
Resposta · 201
{
  "data": {
    "id": "prod_Lq2Xm9Rt4Vc7Nz1K",
    "name": "Planilha Financeira Inteligente",
    "description": "",
    "price": 4700,
    "type": "digital",
    "image": "",
    "sku": "",
    "deliveryUrl": "https://membros.sualoja.com.br/planilha",
    "active": true,
    "createdAt": "2026-10-31T15:10:00.000Z",
    "updatedAt": "2026-10-31T15:10:00.000Z"
  }
}

Clientes

GET/api/v1/customersGET/api/v1/customers/:id

Clientes são criados automaticamente na primeira compra e identificados pelo e-mail. Cada novo cliente dispara o evento customer.created. A listagem aceita page e limit.

Objeto Cliente
{
  "id": "cus_4Rt9WqLm2Xc8Vb1N",
  "name": "Marina Costa",
  "email": "marina@email.com",
  "document": "39053344705",
  "phone": "11987654321",
  "address": null,
  "createdAt": "2026-09-12T18:20:41.002Z",
  "updatedAt": "2026-10-31T14:02:09.870Z"
}

Checkouts

GET/api/v1/checkoutsGET/api/v1/checkouts/:id

Retorna a configuração completa de cada checkout: produto, preço, order bumps, métodos e regras de pagamento, campos, aparência, elementos de conversão, pixels e redirecionamentos. O link público é /c/:slug. A edição é feita pelo painel, com prévia ao vivo.

CampoDescrição
slugIdentificador usado na URL.
pricePreço em centavos, ou null para usar o do produto.
bumpsOfertas adicionais: { id, productId, title, description, price }.
methodsRegras de card (parcelas, juros), pix e boleto (desconto, validade).
fieldsCampos exigidos: phone, document, address, confirmEmail.
appearance · elementsVisual e elementos de conversão.
tracking · redirectsPixels e URLs de redirecionamento após o pagamento.
visitsVisitas registradas no link público.
Trecho da resposta
{
  "data": {
    "id": "chk_8Hq2LxP0aZ71mNcV",
    "slug": "trafego-pro",
    "name": "Tráfego Pro — Principal",
    "productId": "prod_T7m2Kq9XrL4pW0aZ",
    "price": null,
    "bumps": [
      { "id": "bump_K2m9Xq", "productId": "prod_Hc3Vn8Lq1Mz6Rt0Y",
        "title": "Leve também o Kit de Templates", "price": 3700 }
    ],
    "methods": {
      "card": { "enabled": true, "maxInstallments": 12, "freeInstallments": 1, "interestRate": 2.99 },
      "pix": { "enabled": true, "discountPercent": 5, "expiresInMinutes": 30 },
      "boleto": { "enabled": true, "discountPercent": 0, "dueDays": 3 }
    },
    "defaultMethod": "card",
    "visits": 4821
  }
}

Cupons

GETPOST/api/v1/couponsDELETE/api/v1/coupons/:id

Cupons dão desconto percentual ou fixo sobre o subtotal (produto + order bumps). O código é salvo sempre em maiúsculas.

CampoTipoDescrição
codeobrigatóriostringCódigo digitado pelo cliente.
typeobrigatóriostringpercent ou fixed.
valueobrigatórionumberPercentual (ex.: 10) ou valor em centavos.
maxUsesinteger | nullLimite total de usos.
expiresAtdatetime | nullData de expiração.
checkoutIdsarrayRestringe a checkouts específicos (vazio = todos).
activebooleanAtiva ou pausa o cupom.
Criar cupom
curl -X POST https://checkout.sualoja.com.br/api/v1/coupons \
  -H "Authorization: Bearer $VOLTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"code":"BLACK50","type":"fixed","value":5000,"maxUses":200,
       "expiresAt":"2026-11-30T23:59:59.000Z"}'

Carrinhos abandonados

GET/api/v1/leads?status=abandoned

Quando o cliente preenche o e-mail no checkout, um lead é registrado. O ciclo de status é:

  • open — o cliente está preenchendo o checkout.
  • abandoned — sem compra aprovada após o prazo configurado (30 min por padrão). Dispara cart.abandoned.
  • recovered — o cliente voltou e pagou depois de abandonar. Dispara cart.recovered.
  • converted — comprou sem chegar a abandonar.
Objeto Lead
{
  "id": "lead_Vx2Lm8Qr4Tz1Nc9K",
  "checkoutId": "chk_8Hq2LxP0aZ71mNcV",
  "checkoutName": "Tráfego Pro — Principal",
  "name": "Lucas Ferreira",
  "email": "lucas.ferreira@email.com",
  "phone": "21998765432",
  "status": "abandoned",
  "orderId": null,
  "value": 49700,
  "createdAt": "2026-10-31T13:10:22.000Z",
  "abandonedAt": "2026-10-31T13:40:30.000Z"
}

Eventos

GET/api/v1/events?type=order.paid

Todo evento enviado por webhook também fica registrado e pode ser consultado pela API — ótimo para reconciliar algo que o seu sistema perdeu. Filtre por type e pagine com page/limit. Cada item tem id, type, data e createdAt, no mesmo formato do payload do webhook.

Webhooks — visão geral

Webhooks avisam o seu sistema, em tempo real, sobre o que acontece na conta: um pagamento aprovado, um PIX gerado, um reembolso, um carrinho abandonado. Cadastre um endpoint em Painel → Webhooks (ou pela API), escolha os eventos e, opcionalmente, restrinja a checkouts específicos.

  • As entregas são feitas com POST e corpo JSON, com timeout de 10 segundos.
  • Qualquer resposta 2xx é considerada sucesso. Responda rápido e processe em segundo plano.
  • Cada endpoint tem um secret próprio (whsec_…) para validar a assinatura. Ele pode ser rotacionado no painel.
  • O mesmo evento pode chegar mais de uma vez em casos raros: use id do evento para deduplicar.

Tipos de eventos

typeQuando é disparadodata
order.createdPedido criado, antes da resposta do gateway.Pedido
order.waiting_paymentPIX ou boleto gerado, ou cartão em análise.Pedido
order.paidPagamento aprovado.Pedido
order.refusedPagamento recusado em todos os gateways tentados.Pedido
order.refundedPedido reembolsado.Pedido
order.canceledPedido cancelado.Pedido
order.chargebackContestação recebida do gateway.Pedido
order.expiredPIX ou boleto vencido sem pagamento.Pedido
customer.createdPrimeira compra de um novo e-mail.Cliente
cart.abandonedLead sem compra após o prazo de abandono.Lead
cart.recoveredCliente que abandonou voltou e pagou.Lead
webhook.testBotão “Enviar teste” no painel.Exemplo

Assine * para receber todos os eventos, incluindo os que forem criados no futuro.

Payload e headers

HeaderValor
Content-Typeapplication/json
User-AgentVoltra-Webhooks/1.0
X-Voltra-EventTipo do evento, ex.: order.paid.
X-Voltra-DeliveryId da entrega (dlv_…), igual em todas as retentativas.
X-Voltra-Signaturet=<unix>,v1=<hmac>
Corpo da requisição
{
  "id": "evt_Np4Lx9Qm2Rt7Vc1Z",
  "type": "order.paid",
  "createdAt": "2026-10-31T14:02:13.531Z",
  "data": {
    "id": "ord_Zk81QmXr2LpV0c7N",
    "code": "VT-100318",
    "status": "paid",
    "total": 53400,
    "method": "card",
    "customer": { "name": "Marina Costa", "email": "marina@email.com", … },
    "items": [ … ],
    …
  }
}

Verificar a assinatura

Para garantir que a requisição veio do EADPay e não foi alterada, calcule um HMAC-SHA256 usando o secret do endpoint sobre a string t + "." + corpoBruto e compare com v1. Use sempre o corpo bruto (antes de qualquer parse de JSON) e uma comparação em tempo constante. Rejeite timestamps com mais de 5 minutos para evitar ataques de repetição.

import crypto from 'node:crypto';
import express from 'express';

const app = express();

app.post('/webhooks/voltra', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('X-Voltra-Signature') || '';
  const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));

  const expected = crypto
    .createHmac('sha256', process.env.VOLTRA_WEBHOOK_SECRET)
    .update(`${t}.${req.body}`)
    .digest('hex');

  const valid = v1?.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
  const recent = Math.abs(Date.now() / 1000 - Number(t)) <= 300;
  if (!valid || !recent) return res.status(400).send('assinatura inválida');

  const event = JSON.parse(req.body);
  switch (event.type) {
    case 'order.paid': liberarAcesso(event.data); break;
    case 'order.refunded': removerAcesso(event.data); break;
  }
  res.sendStatus(200);
});
<?php
$payload = file_get_contents('php://input');
$header  = $_SERVER['HTTP_X_VOLTRA_SIGNATURE'] ?? '';
parse_str(str_replace(',', '&', $header), $sig); // ['t' => ..., 'v1' => ...]

$expected = hash_hmac('sha256', $sig['t'] . '.' . $payload, getenv('VOLTRA_WEBHOOK_SECRET'));

if (!hash_equals($expected, $sig['v1'] ?? '') || abs(time() - (int) $sig['t']) > 300) {
  http_response_code(400);
  exit('assinatura inválida');
}

$event = json_decode($payload, true);
if ($event['type'] === 'order.paid') {
  liberar_acesso($event['data']['customer']['email']);
}
http_response_code(200);
import hashlib, hmac, json, os, time
from flask import Flask, abort, request

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

@app.post("/webhooks/voltra")
def voltra_webhook():
    raw = request.get_data()
    header = request.headers.get("X-Voltra-Signature", "")
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)

    signed = f"{parts.get('t', '')}.".encode() + raw
    expected = hmac.new(SECRET, signed, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected, parts.get("v1", "")):
        abort(400)
    if abs(time.time() - int(parts.get("t", 0))) > 300:
        abort(400)

    event = json.loads(raw)
    if event["type"] == "order.paid":
        liberar_acesso(event["data"]["customer"]["email"])
    return "", 200

Retentativas

Se o seu endpoint responder com status diferente de 2xx, demorar mais de 10 segundos ou estiver inacessível, a entrega fica como retrying e é reenviada automaticamente com backoff:

TentativaQuando
1ªImediatamente, no momento do evento
2ª1 minuto depois da falha
3ª5 minutos depois
4ª30 minutos depois
5ª2 horas depois
6ª6 horas depois — se falhar, a entrega fica failed

No painel você vê cada entrega com status, código e corpo da resposta, duração e próximo agendamento, e pode reenviar manualmente qualquer uma delas.

Formatos Discord e Slack

Além do JSON padrão, um endpoint pode usar o formato discord ou slack. Nesse caso o corpo já sai no formato de mensagem esperado por essas ferramentas — basta colar a URL do webhook do canal.

  • discord: { content, embeds: [{ title, description, color, fields }] }
  • slack: { text, blocks } com Block Kit.

Use, por exemplo, um canal #vendas que recebe order.paid e outro #suporte que recebe order.refused e cart.abandoned.

Exemplo · discord
{
  "content": null,
  "embeds": [{
    "title": "Pagamento aprovado · VT-100318",
    "description": "Marina Costa comprou Método Tráfego Pro",
    "color": 1352291,
    "fields": [
      { "name": "Valor", "value": "R$ 534,00", "inline": true },
      { "name": "Método", "value": "Cartão 1x", "inline": true },
      { "name": "Gateway", "value": "Pagar.me", "inline": true }
    ]
  }]
}

Gerenciar webhooks via API

GETPOST/api/v1/webhooksDELETE/api/v1/webhooks/:id

Útil para plataformas que se conectam ao EADPay e precisam registrar o próprio endpoint automaticamente. A resposta de criação inclui o secret para validação.

CampoTipoDescrição
urlobrigatóriostringURL HTTPS que receberá os eventos.
namestringNome para identificação no painel.
eventsarrayLista de tipos, ou ["*"].
formatstringjson (padrão), discord ou slack.
checkoutIdsarrayFiltra eventos por checkout (vazio = todos).
activebooleanPausa ou retoma as entregas.
Criar endpoint
curl -X POST https://checkout.sualoja.com.br/api/v1/webhooks \
  -H "Authorization: Bearer $VOLTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Área de membros","url":"https://membros.sualoja.com.br/hooks/voltra",
       "events":["order.paid","order.refunded","order.chargeback"]}'
Resposta · 201
{
  "data": {
    "id": "wh_Rt8Lm2Qx9Vc4Nz1K",
    "name": "Área de membros",
    "url": "https://membros.sualoja.com.br/hooks/voltra",
    "events": ["order.paid", "order.refunded", "order.chargeback"],
    "secret": "whsec_4f9Kq2Lm8Xr1Tz7Vc3Nb6Hp0Wd5Ys2Ae",
    "format": "json",
    "active": true,
    "checkoutIds": []
  }
}

Receptor de testes

ANY/inspector/:bucket

O EADPay inclui um receptor local de requisições. Qualquer chamada para /inspector/demo (ou outro nome de bucket) é registrada com método, headers e corpo, e pode ser inspecionada em Painel → Webhooks → Receptor de testes. Aponte um webhook para ele e faça uma compra no checkout para ver o evento chegando, com a assinatura, antes de integrar com o seu sistema real.

O receptor guarda as 100 requisições mais recentes. Use buckets diferentes para separar testes de projetos distintos.

Gateways suportados

Cada gateway é conectado em Painel → Gateways com as credenciais da sua própria conta. Você pode ter mais de uma conta do mesmo provider (por exemplo, dois CNPJs) e escolher o ambiente de teste ou produção.

ProviderMétodosCredenciaisAPI
pagarmeCartão, PIX, boletoSecret key (sk_…) e public key (pk_…)Pagar.me Core v5
appmaxCartão, PIX, boletoClient ID, Client secret e External ID (Painel Appmax → API → Credenciais)Appmax API v1 com OAuth2 (produção e sandbox)
mercadopagoCartão, PIX, boletoAccess token e public keyMercado Pago Payments v1
asaasCartão, PIX, boletoAPI keyAsaas v3 (produção e sandbox)
sandboxCartão, PIX, boletoNenhumaSimulador interno

O botão Testar conexão faz uma chamada autenticada ao provider e mostra se as credenciais são válidas e o tempo de resposta. Na listagem, as credenciais aparecem mascaradas.

Cartão sem dados abertos no servidor

O número e o CVV do cartão nunca passam pelo servidor EADPay: o checkout gera um token no navegador para cada gateway da fila do cartão — Pagar.me com a public key (cadastre o domínio do checkout no dashboard Pagar.me), Appmax com o Appmax JS e o External ID, Mercado Pago com a public key. O servidor recebe só os tokens, a bandeira e os 4 últimos dígitos. Assim o fallback continua funcionando entre gateways.

Dados exigidos pelos gateways

O checkout pede automaticamente os campos que os gateways ativos exigem, mesmo que estejam desligados na configuração do checkout: CPF/CNPJ e celular (Pagar.me e Appmax) e endereço de cobrança para cartão e boleto na Pagar.me. Quando o endereço só é exigido para alguns métodos, ele aparece apenas quando o comprador escolhe um deles.

Roteamento e fallback

Para cada método — cartão, PIX e boleto — você define uma fila ordenada de gateways. Na hora do pagamento, o EADPay considera apenas os gateways ativos que suportam aquele método e segue estas regras:

Resultado da tentativaO que acontece
approvedPara a fila. Pedido paid.
pendingPara a fila. Pedido waiting_payment (PIX/boleto gerado ou análise).
errorFalha técnica, timeout ou indisponibilidade. Tenta o próximo se fallback estiver ativo.
refusedRecusa do emissor. Tenta o próximo apenas para cartão e se cascata em recusas estiver ativa.

Se nenhuma tentativa for aprovada, o pedido termina como refused. Se a fila estiver vazia, a resposta é 503 no_gateway. Todas as tentativas ficam em attempts, e o painel mostra a taxa de aprovação de cada gateway para você ajustar a ordem.

Postbacks dos gateways

POST/postbacks/:gatewayId

PIX e boleto são confirmados depois, de forma assíncrona. Para que o EADPay saiba quando o pagamento caiu — e também de reembolsos e chargebacks feitos no gateway — cadastre a URL de postback de cada gateway no painel do provider. A URL exata, com o id do gateway, aparece em Painel → Gateways com um botão de copiar.

GatewayOnde cadastrarEventos recomendados
Pagar.meDashboard → Configurações → Webhooks → Criar webhookorder.paid, order.payment_failed, order.canceled, charge.refunded, charge.chargedback
AppmaxPainel Appmax → API → Webhooks (URL HTTPS pública)order_paid, order_approved, order_paid_by_pix, order_pix_expired, order_billet_overdue, order_refund, payment_not_authorized, order_chargeback_in_treatment
Mercado PagoSuas integrações → Webhooks → Configurar notificaçõesPagamentos (payment). A URL também é enviada em cada cobrança.
AsaasIntegrações → WebhooksPAYMENT_CONFIRMED, PAYMENT_RECEIVED, PAYMENT_OVERDUE, PAYMENT_REFUNDED, PAYMENT_CHARGEBACK_REQUESTED
SimuladorNão é necessárioAceita { "transactionId", "status" } para testes.

O postback é convertido para um status normalizado (paid, refused, refunded, canceled, chargeback, expired, waiting_payment), o pedido correspondente é atualizado e os seus webhooks são disparados.

Confirmação na origem: os webhooks da Pagar.me e da Appmax não são assinados. Por isso, antes de alterar o pedido, o EADPay consulta o status na API do gateway (GET /orders/:id) e usa o status confirmado — um postback forjado dizendo "pago" é ignorado. Se a consulta falhar, a resposta é 503 e o gateway reenvia mais tarde; nos demais casos, 200 {"received": true}.

Preços e parcelamento

O servidor é sempre a autoridade sobre os valores. O cálculo segue esta ordem — e o checkout mostra exatamente o mesmo resultado ao cliente:

Fórmula
base           = checkout.price ?? product.price
subtotal       = base + soma(bumps selecionados)
couponDiscount = coupon.type === 'percent' ? round(subtotal * value / 100) : min(value, subtotal)
afterCoupon    = subtotal - couponDiscount
methodDiscount = round(afterCoupon * methods[method].discountPercent / 100)   // só PIX e boleto
net            = afterCoupon - methodDiscount

// Cartão: rate = interestRate / 100
// n <= freeInstallments  →  parcela = round(net / n),  total = net
// n >  freeInstallments  →  parcela = round(net * rate / (1 - (1 + rate) ** -n)),  total = parcela * n

Exemplos

CenárioCálculoTotal
PIX com 5% de descontoR$ 497,00 − R$ 24,85R$ 472,15
Bump + cupom 10% + PIX 5%R$ 534,00 − R$ 53,40 − R$ 24,03R$ 456,57
Cartão 3x sem juros3 × R$ 165,67 (total sem acréscimo)R$ 497,00
Cartão 12x a 2,99% a.m.12 × R$ 49,90R$ 598,80

No pedido, discount soma cupom e desconto do método, e interest é a diferença entre o total parcelado e o valor líquido.

API pública do checkout

Estes endpoints não exigem chave e são os mesmos usados pela página do checkout. Use-os para construir um checkout próprio (headless) mantendo regras, cupons e roteamento do EADPay.

EndpointDescrição
POST /api/public/checkouts/:slug/couponValida um cupom: { code } → { coupon: { code, type, value } } ou 422 invalid_coupon.
POST /api/public/checkouts/:slug/leadsRegistra/atualiza o lead: { leadId?, name, email, phone } → { leadId }.
POST /api/public/checkouts/:slug/ordersCria o pedido e processa o pagamento. Retorna { order, redirectUrl, message }.
GET /api/public/orders/:id/statusConsulta leve para polling do PIX: { status, paidAt, redirectUrl }.
Corpo de POST /api/public/checkouts/:slug/orders
{
  "customer": {
    "name": "Marina Costa",
    "email": "marina@email.com",
    "document": "39053344705",
    "phone": "11987654321"
  },
  "bumps": ["bump_K2m9Xq"],
  "coupon": "BEMVINDO10",
  "method": "card",
  "card": { "number": "4111111111111111", "holder": "MARINA COSTA", "expMonth": "09", "expYear": "2029", "cvv": "123", "installments": 3 },
  "utm": { "source": "instagram", "medium": "stories", "campaign": "lancamento" },
  "leadId": "lead_Vx2Lm8Qr4Tz1Nc9K"
}

Erros de validação retornam 422 com fields usando as chaves name, email, document, phone, zip, street, number, district, city, state, cardNumber, cardHolder, cardExpiry, cardCvv, installments e method.

Ping

GET/api/v1/ping

Verifica se a chave é válida e retorna o nome da conta. Ideal para o botão “Testar conexão” da sua integração.

Resposta · 200
{ "ok": true, "account": "Escala Digital Educação" }
© 2026 EADPay Pagamentos · API v1 Voltar ao site · Entrar no painel