Ir para o conteúdo
DocsAPI 2026-11-13

Integração

Webhooks e verificação de assinatura

O Eclipta Pay avisa o seu sistema quando algo muda: cobrança confirmada, pedido quitado, estorno, disputa, transferência. Cada entrega é assinada com o segredo do seu endpoint.

Como funciona

  • O evento é gravado na mesma transação da mudança e enviado em segundos para cada endpoint ativo inscrito no tipo.
  • Pelo menos uma vez, sem garantia de ordem. O mesmo evento pode chegar mais de uma vez (retentativas e reenvios). Deduplique por eventId e, para estado, compare aggregateVersion por agregado e tipo de evento (tipos diferentes do mesmo aggregateId podem ter sequências próprias): descarte versões menores que a já aplicada.
  • Responda 2xx em até 10 s e processe depois (fila no seu lado). Qualquer outro status, timeout ou erro de rede conta como falha. Redirecionamentos não são seguidos.
  • Leia no máximo 64 KiB de resposta; respostas maiores são interrompidas e contam como falha. Responda algo curto, como {"received":true}.
  • data é uma projeção mínima: use o evento como gatilho e decida pelo recurso lido na API (pedido, cobrança, estorno, plano). Eventos chegam para todos os endpoints da conta inscritos no tipo: ignore recursos que não são do seu sistema. As integrações de referência mostram um receptor completo, com inbox.
Corpo de uma entrega
{
  "eventId": "evt_01JDY9C1B2C3D4E5F6G7H8J9KH",
  "type": "order.payment_completed",
  "apiVersion": "2026-11-13",
  "occurredAt": "2026-11-13T13:00:00.000Z",
  "tenantId": "org_01JDY7Q2M3N4P5R6S7T8V9W0XA",
  "accountId": "acc_01JDY7Q2M3N4P5R6S7T8V9W0XB",
  "environment": "sandbox",
  "aggregateId": "ord_01JDY8B1B2C3D4E5F6G7H8J9KB",
  "aggregateVersion": 1,
  "data": {
    "orderId": "ord_01JDY8B1B2C3D4E5F6G7H8J9KB",
    "confirmedCents": "14970",
    "currency": "BRL"
  }
}

Cadastrar o endpoint

Cada conta pode ter até 10 endpoints. Inscreva cada um nos tipos de que precisa ou em ["*"] para todos (inclusive tipos novos). O segredo whsec_… aparece só na resposta da criação: guarde-o num cofre.

BASH
curl -X POST "$ECLIPTA_PAY_API/v1/webhook-endpoints" \
  -H "Authorization: Bearer $ECLIPTA_PAY_KEY" \
  -H "X-Account-Id: $ECLIPTA_PAY_ACCOUNT" \
  -H "Idempotency-Key: webhook-producao-v1" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://loja.exemplo.com.br/webhooks/eclipta","events":["order.payment_completed","payment.refund_updated"]}'

Headers de cada entrega

Headers da entrega
HeaderConteúdo
Eclipta-Pay-Event-IdIdentificador do evento. O mesmo em todas as tentativas e reenvios: deduplique por ele.
Eclipta-Pay-Event-TypeTipo do evento (também no corpo).
Eclipta-Pay-TimestampSegundos desde 1970 (UTC) do momento da tentativa. Muda a cada tentativa.
Eclipta-Pay-SignatureUma ou mais assinaturas v1=<hex>, separadas por vírgula (duas durante a rotação do segredo).
Eclipta-Pay-Key-IdVersão de cada segredo que assinou, na mesma ordem das assinaturas.
Eclipta-Pay-Delivery-IdIdentificador da entrega (evento × endpoint).
Eclipta-Pay-AttemptNúmero da tentativa desta entrega.
User-AgentEcliptaPay-Webhooks/1.0
Content-Typeapplication/json

Verificar a assinatura

A assinatura é HMAC-SHA256 com o segredo do endpoint sobre <Eclipta-Pay-Timestamp>.<corpo cru>, em hexadecimal, com o prefixo v1=. Use o corpo cru, exatamente como chegou, antes de qualquer parse de JSON. Compare em tempo constante e recuse timestamps a mais de 5 minutos do seu relógio.

Com o SDK (Next.js, rota de API)
import { verifyWebhook, WebhookVerificationError } from "@eclipta-pay/sdk";

export async function POST(request: Request) {
  const raw = await request.text(); // corpo cru
  try {
    const evento = verifyWebhook(raw, request.headers, process.env.ECLIPTA_PAY_WEBHOOK_SECRET!);
    if (await jaProcessado(evento.eventId)) return new Response(null, { status: 200 });
    await enfileirar(evento); // processe fora da requisição
    return Response.json({ received: true });
  } catch (e) {
    if (e instanceof WebhookVerificationError) return new Response(null, { status: 400 });
    throw e;
  }
}
Sem o SDK (Node.js)
import { createHmac, timingSafeEqual } from "node:crypto";

export function assinaturaValida(raw: Buffer, headers: Record<string, string>, segredos: string[]): boolean {
  const ts = headers["eclipta-pay-timestamp"];
  if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const recebidas = (headers["eclipta-pay-signature"] ?? "")
    .split(",")
    .map((s) => s.trim())
    .filter((s) => s.startsWith("v1="))
    .map((s) => Buffer.from(s.slice(3), "hex"));
  let ok = false;
  for (const segredo of segredos) {
    const esperada = createHmac("sha256", segredo).update(`${ts}.`).update(raw).digest();
    for (const r of recebidas) if (r.length === esperada.length && timingSafeEqual(r, esperada)) ok = true;
  }
  return ok;
}

Retentativas

Uma entrega com falha é repetida até 9 vezes, com intervalos crescentes e variação aleatória de ±20% (para não sincronizar retentativas). Depois da última, a entrega fica FAILED e pode ser reenviada.

Cronograma de retentativas
TentativaIntervalo antesAcumulado aproximado
1imediataimediata
230 s30 s
32 min2.5 min
410 min12.5 min
51 h1.2083333333333333 h
66 h7.208333333333333 h
724 h31.208333333333332 h
848 h79.20833333333333 h
972 h151.20833333333334 h

Desativação automática: um endpoint que falha sem nenhum sucesso por 3 dias seguidos passa para AUTO_DISABLED. As entregas pendentes ficam pausadas, os responsáveis técnicos da conta recebem um e-mail e nada novo é enviado até a reativação (PATCH /v1/webhook-endpoints/{id} com {"enabled": true}), que retoma as pendentes.

Reenvio e recuperação de perdas

  • POST /v1/webhook-deliveries/{id}/replay reenvia uma entrega com o mesmo eventId e assinatura nova. Cada reenvio fica registrado na trilha de auditoria.
  • POST /v1/events/{id}/resend reenvia um evento para um endpoint (mesmo que ele não existisse quando o evento ocorreu) ou para todos os inscritos.
  • GET /v1/events lista os eventos dos últimos 30 dias, com filtros por tipo e agregado: use para reconciliar depois de uma indisponibilidade.
  • GET /v1/webhook-deliveries/{id} mostra cada tentativa: status HTTP, duração, resultado e até 1 KB do início da sua resposta (dados que parecem sensíveis são mascarados).

Rotação do segredo

POST /v1/webhook-endpoints/{id}/rotate-secret gera um segredo novo (exibido só nessa resposta). Durante a sobreposição (padrão 24 h), cada entrega vai com as duas assinaturas e as duas versões em Eclipta-Pay-Key-Id. Passe os dois segredos ao verificador, troque o segredo no seu cofre e, depois da janela, remova o antigo.

Regras de destino

  • HTTPS obrigatório em produção. No Sandbox, http:// é aceito para testes.
  • O DNS é resolvido em cada tentativa e todos os endereços são conferidos. Destinos em loopback, redes privadas, link-local, CGNAT, multicast, metadata de nuvem e seus equivalentes IPv6 (inclusive IPv4 mapeado) são bloqueados sem conexão.
  • A conexão vai para o endereço validado, sem nova consulta de DNS, e o certificado é conferido pelo nome do host da URL.
  • Usuário e senha na URL, fragmento (#) e nomes internos (localhost, *.internal, *.local) são recusados no cadastro.

Catálogo de eventos (2026-11-13)

O envelope é o mesmo para todos os tipos. data traz uma projeção sem dados pessoais nem de cartão; para o estado completo, consulte o recurso pela API. Receptores devem aceitar tipos novos sem falhar.

Tipos de evento
TipoAgregadoQuandoOrigem
payment.confirmedpaymentPagamento confirmado pelo provedor; o valor ainda está a liberar (não é saldo disponível).Base
payment.settlement_reportedpaymentO provedor informou a liquidação. O caixa só entra pela importação do extrato (recebido, conciliando).Base
payment.refund_updatedpaymentMudança no estorno da cobrança (solicitado, parcial, total ou revertido).Base
payment.dispute_updatedpaymentMudança em disputa (chargeback) da cobrança.Base
transfer.updatedtransferMudança de situação de uma transferência (saque).Base
account.status_updatedaccountMudança na situação cadastral da conta no provedor.Base
organization.activatedaccountConta aprovada em todas as etapas do onboarding: pode emitir cobranças (uma vez por conta).Extensão
subscription.updatedsubscriptionMudança de situação de uma assinatura.Base
reconciliation.case_openedorderCaso de conciliação aberto (por exemplo, sobrepagamento ou lançamento sem correspondência).Base
order.payment_completedorderPedido quitado: o total confirmado cobre o pedido sem parte incerta. Libere o produto uma única vez por pedido. Pedido aberto pelo link de uma oferta traz também `offerId` e `productId`.Base
payment_group.updatedpayment_groupMudança no pagamento com dois cartões (autorização, captura ou compensação).Base
payment_plan.installment_updatedpayment_planMudança numa parcela de Pix em parcelas ou de parcelamento mensal no cartão.Base
payment_plan.updatedpayment_planMudança de situação do plano de parcelas.Base
payment.createdpaymentCobrança registrada no provedor (A charge.created).Extensão
payment.failedpaymentCobrança recusada (cartão recusado, reprovada na análise de risco ou rejeitada pelo provedor) (A charge.failed).Extensão
payment.overduepaymentCobrança venceu sem pagamento (A charge.overdue).Extensão
payment.cancelledpaymentCobrança cancelada no provedor (A charge.canceled).Extensão
payment.restoredpaymentCobrança cancelada foi reaberta no provedor (A charge.restored).Extensão
payment.updatedpaymentValor, vencimento ou descrição da cobrança alterados no provedor; um novo QR Code/linha digitável é gerado.Extensão
checkout.session_updatedcheckout_sessionMudança na sessão de checkout (concluída ou expirada localmente) (A checkout.session.*).Extensão
webhook_endpoint.testwebhook_endpointEvento de teste enviado só para o endpoint escolhido, a pedido do integrador. Não representa mudança financeira.Plataforma

Próximo: SDK TypeScript