Confido Docs

Webhooks

Avisos por POST quando algo muda, com assinatura HMAC, retentativas e o evento de teste.

A Confido avisa o seu servidor com um POST JSON a cada evento: venda paga, recusada, estornada, subconta aprovada e outros. Use os webhooks para liberar pedidos, em vez de consultar o status de tempos em tempos.

Cadastrar

curl -X POST https://api.confidoscm.com/api/v1/webhook-endpoints \
  -H "X-API-Key: $CONFIDO_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "url": "https://loja.exemplo.com/webhooks/confido",
    "enabledEvents": ["payment.paid", "payment.declined", "payment.refunded"],
    "description": "Loja principal"
  }'
  • A resposta traz o secret de assinatura uma vez só. Guarde no seu servidor.
  • A URL precisa ser pública e em https na porta 443 (endereços internos, localhost e IPs privados são recusados).
  • storeId limita aos eventos de uma loja; connectScope (children ou subtree) inclui os das subcontas.
  • Também dá para cadastrar no painel: Configurações › Integrações › Webhooks.
  • Escopos: webhooks:read e webhooks:write.

Eventos

Grupo Eventos
Pagamentos payment.processing, payment.pending, payment.authorized, payment.requires_action, payment.paid, payment.declined, payment.failed, payment.expired, payment.cancelled, payment.refunded, payment.partially_refunded, payment.chargeback
Estornos e disputas refund.completed, dispute.opened, dispute.accepted, dispute.won, dispute.lost, dispute.closed, dispute.evidence_deadline_approaching
Subcontas account.created, account.updated, account.verification_updated, account.approved, account.rejected
Transferências transfer.created
Assinaturas subscription.created, subscription.renewed, subscription.payment_failed, subscription.cancel_scheduled, subscription.cancelled, subscription.completed
Teste webhook.test

O que chega

{
  "id": "evt_…",
  "object": "event",
  "type": "payment.paid",
  "api_version": "2026-01-01",
  "livemode": true,
  "created_at": "2026-11-03T18:02:09.000Z",
  "occurred_at": "2026-11-03T18:02:09.000Z",
  "data": {
    "id": "pay_9f2c4e1a7b3d",
    "object": "payment",
    "status": "paid",
    "payment_method": "pix",
    "currency": "BRL",
    "amount_cents": 15000,
    "net_amount_cents": 14801,
    "installments": 1,
    "description": "Pedido 42",
    "metadata": null,
    "idempotency_key": "pedido-42",
    "customer_id": "…",
    "paid_at": "2026-11-03T18:02:09.000Z",
    "livemode": true
  },
  "previous_data": null
}

Nos eventos de pagamento, o data é o pagamento completo, em snake_case (amount_cents, payment_method, net_amount_cents, paid_at…), com o id igual ao publicId da API (pay_…). O previous_data, quando vem preenchido, traz os valores anteriores.

Com os cabeçalhos:

Cabeçalho O que é
X-Webhook-Id Id estável da entrega: use para ignorar repetições.
X-Webhook-Event-Id, X-Webhook-Event-Type O evento e o tipo.
X-Webhook-Timestamp Segundos desde 1970 (UTC), usado na assinatura.
X-Webhook-Signature v1=<HMAC-SHA256 em hex>.
X-Webhook-Attempt O número da tentativa (1 a 4).

O campo account aparece nos eventos de subconta. Na sandbox, livemode vem sempre false.

Conferir a assinatura

Calcule o HMAC-SHA256 de "<timestamp>.<corpo cru>" com o seu secret e compare com o v1= do cabeçalho, antes de interpretar o JSON. Recuse timestamps com mais de 5 minutos de diferença. Nas 24 horas depois de trocar o segredo, o cabeçalho traz duas assinaturas separadas por vírgula: aceite qualquer uma.

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

export function webhookValido(corpoCru, headers, segredo) {
  const ts = Number(headers['x-webhook-timestamp']);
  if (!Number.isInteger(ts) || Math.abs(Math.floor(Date.now() / 1000) - ts) > 300) return false;
  const esperado = Buffer.from(
    'v1=' + createHmac('sha256', segredo).update(`${ts}.${corpoCru}`, 'utf8').digest('hex'),
  );
  return String(headers['x-webhook-signature'] ?? '')
    .split(',')
    .some((v) => {
      const recebido = Buffer.from(v.trim());
      return recebido.length === esperado.length && timingSafeEqual(recebido, esperado);
    });
}
import hmac, hashlib, time

def webhook_valido(corpo_cru: bytes, headers: dict, segredo: str) -> bool:
    ts = int(headers.get("x-webhook-timestamp", "0"))
    if abs(int(time.time()) - ts) > 300:
        return False
    esperado = "v1=" + hmac.new(segredo.encode(), f"{ts}.".encode() + corpo_cru, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(esperado, v.strip()) for v in headers.get("x-webhook-signature", "").split(","))

Autorize pela assinatura, não pelo IP: a Confido não tem uma lista fixa de IPs de saída.

Responder e repetir

  • Responda 2xx em até 5 segundos e processe depois, numa fila. Redirecionamentos não são seguidos.
  • A entrega é pelo menos uma vez e sem ordem garantida: ignore repetições pelo X-Webhook-Id e use occurred_at para saber qual estado é o mais novo.
  • Sem 2xx, a Confido tenta de novo, até 4 vezes em até 72 horas, com espera crescente (até 8 h, 16 h e 24 h, com sorteio). Retry-After na sua resposta adia a próxima tentativa. Depois disso, a entrega fica parada e pode ser reenviada pelo painel (Integrações › Logs › Reenviar).

Testar e trocar o segredo

Rota O que faz
POST /api/v1/webhook-endpoints/{id}/test Manda um webhook.test agora e devolve o resultado (succeeded ou failed, status e tempo).
POST /api/v1/webhook-endpoints/{id}/rotate-secret Gera um segredo novo; o anterior ainda assina por 24 horas.
PATCH e DELETE /api/v1/webhook-endpoints/{id} Edita ou exclui (as entregas pendentes são canceladas).