StacePay Documentação da API / Webhooks
Markdown

Webhooks

Cadastre uma URL sua para receber um POST assim que o status de uma cobrança ou saque mudar sem precisar ficar consultando a API.

Cadastrar um endpoint

POST/webhooks
CampoTipoDescrição
urlstringobrigatórioHTTPS obrigatório.
labelstringopcionalNome para identificar o endpoint no painel.
eventsarrayopcionalLista de eventos (veja abaixo). Omitido = recebe todos.

O secret só aparece nesta resposta, na criação. Guarde-o é o que assina os eventos recebidos, e não há como recuperá-lo depois.

{
  "object": "webhook_endpoint",
  "id": "whe_1",
  "url": "https://minhaloja.com/webhooks/stacepay",
  "events": null,
  "active": true,
  "last_delivery_at": null,
  "last_status_code": null,
  "consecutive_failures": 0,
  "created_at": "2026-08-11T21:25:50-03:00",
  "secret": "052742b9296fda91caa59228e6939bbc1e651fc"
}

Listar, remover e ver entregas

GET/webhooks
DELETE/webhooks/{id}
GET/webhook-deliveries?webhook_id={id}&limit=20&offset=0

Eventos disponíveis

EventoQuando dispara
charge.paidCobrança confirmada.
charge.authorizedCartão autorizado (antes da captura).
charge.processingEm confirmação com o parceiro.
charge.refusedRecusada.
charge.failedFalhou antes do parceiro.
charge.refundedEstornada.
charge.chargebackContestada pelo pagador.
charge.disputedEm disputa.
charge.blockedBloqueada por análise de risco.
withdrawal.paidSaque concluído.
withdrawal.processingEnviado ao parceiro.
withdrawal.approvedAprovado, aguardando envio.
withdrawal.rejectedRecusado.
withdrawal.failedFalhou no envio.
withdrawal.refundedValor devolvido ao saldo.
withdrawal.canceledCancelado.
withdrawal.blockedBloqueado por análise.

Formato do evento

{
  "id": "evt_9e2f...",
  "object": "event",
  "type": "charge.paid",
  "created_at": "2026-08-11T21:40:02-03:00",
  "data": { "object": "charge", "id": "sp1_d9c51fe9", "status": "paid", "...": "..." }
}

Verificando a assinatura

Todo envio traz o cabeçalho Novex-Signature: t=<timestamp>,v1=<hmac>. O HMAC-SHA256 é calculado sobre a string "{timestamp}.{corpo cru}" usando o secret do endpoint.

const crypto = require('crypto');

function verificar(header, body, secret) {
  const [tPart, vPart] = header.split(',');
  const timestamp = tPart.split('=')[1];
  const assinaturaRecebida = vPart.split('=')[1];
  const esperada = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${body}`)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(assinaturaRecebida), Buffer.from(esperada));
}
function verificar(string $header, string $rawBody, string $secret): bool {
    [$tPart, $vPart] = explode(',', $header, 2);
    $timestamp = substr($tPart, 2);
    $recebida = substr($vPart, 3);
    $esperada = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
    return hash_equals($esperada, $recebida);
}

Sempre valide contra o corpo cru da requisição, antes de decodificar o JSON reserializar altera espaçamento e ordem de chaves, e a assinatura deixa de bater.

Reenvio

Uma resposta fora da faixa 200–299 conta como falha. Reenviamos com backoff crescente (~1min, 5min, 30min, 2h, 6h) por até 6 tentativas. Depois de 20 falhas seguidas, o endpoint é desativado automaticamente reative cadastrando de novo.