Webhooks

Avisos assinados do que acontece com seus agendamentos, com reenvio automático, log de entregas e exemplos para verificar a assinatura.

Atualizado:

Eventos

Hoje é possível assinar estes eventos de agendamentos:

EventoQuando
booking.createdUma pré-reserva ou agendamento foi criado
booking.confirmedUm agendamento foi confirmado
booking.cancelledCancelado ou expirado
booking.rescheduledMudou de horário (o payload traz o id antigo e o novo)
booking.no_showMarcado como falta
booking.completedAtendimento concluído

O contrato reserva também payment.paid, message.received e conversation.handoff, mas ainda não podem ser assinados nem são emitidos (a cobrança do sinal é Em breve). Um endpoint que os peça recebe 422.

Crie endpoints em Configurações → Webhooks no painel ou com POST /v1/webhook-endpoints (url e events). O segredo de assinatura (whsec_…) aparece uma única vez.

curl -X POST https://api.fiuit.com/v1/webhook-endpoints \
  -H "Authorization: Bearer $WAGEND_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/wagend", "events": ["booking.confirmed", "booking.cancelled"] }'

Payload

{
  "id": "evt_3f6c1d9e0b7a4c2f8e5d1a9b7c3e6f20",
  "type": "booking.confirmed",
  "created_at": "2026-10-14T15:21:07-03:00",
  "workspace_id": "b3a6c8e2-5f1d-4c7a-9e0b-2d8f4a1c6e53",
  "data": { "booking": { "id": "6d1f0c4a-7b2e-4a58-9c3d-0e5f8a2b1c47", "status": "confirmed", "start_at": "2026-10-15T12:45:00+00:00" } }
}

A entrega é pelo menos uma vez: use o id para ignorar duplicados.

Verificando a assinatura

Toda requisição traz o header:

Wagend-Signature: t=1791040867,v1=5c2b9f...e81

v1 é HMAC-SHA256(segredo, t + "." + corpo_bruto) em hexadecimal. Rejeite requisições com mais de 5 minutos e calcule a assinatura sobre o corpo bruto, sem serializar o JSON de novo.

import crypto from 'node:crypto'

export function verifyWagend(rawBody: string, header: string, secret: string) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=') as [string, string]))
  const age = Math.abs(Date.now() / 1000 - Number(parts.t))
  if (!parts.t || !parts.v1 || age > 300) return false
  const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex')
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
}
import hashlib, hmac, time

def verify_wagend(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts.get("t", "0"))) > 300:
        return False
    signed = f"{parts['t']}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

Reenvios

Responda com qualquer 2xx em até 10 segundos. Caso contrário, tentamos de novo após 1 min, 5 min, 30 min, 2 h e 12 h. Cada tentativa aparece no log de entregas, onde você também pode reenviar manualmente.

Segurança e limites

  • A URL deve ser https e resolver para um endereço público: rejeitamos localhost, redes privadas, link-local e endereços de metadados de nuvem (ao cadastrar e a cada entrega). Não seguimos redirecionamentos (um 3xx conta como falha).
  • Cabeçalhos extras: Wagend-Event (tipo) e Wagend-Delivery (id da entrega). Cada tentativa é assinada com um timestamp novo.
  • Até 10 endpoints por workspace. Após 10 falhas consecutivas o endpoint é desativado (reative com PATCH /v1/webhook-endpoints/{id} e {"active": true}).
  • POST /v1/webhook-endpoints/{id}/ping envia um evento de teste webhook.ping; POST /v1/webhook-endpoints/{id}/rotate-secret gera um novo segredo (mostrado uma única vez; o anterior deixa de valer na hora).
  • Com API key use o escopo webhooks:manage. O log fica em GET /v1/webhook-endpoints/{id}/deliveries e o reenvio em POST …/deliveries/{delivery_id}/resend.