Borrador

Webhooks

Eventos firmados de reservas, pagos y conversaciones, con reintentos automáticos.

Eventos

EventoCuándo
booking.createdSe creó una pre-reserva o reserva
booking.confirmedSe confirmó una reserva (también después del pago)
booking.cancelledCancelada o vencida
booking.rescheduledSe movió de horario (el payload trae el id viejo y el nuevo)
booking.no_showMarcada como ausencia
payment.paidSe recibió una seña por Pix
message.receivedLlegó un mensaje de un cliente
conversation.handoffUna conversación se derivó a una persona

Creá endpoints en Desarrolladores → Webhooks o con POST /v1/webhook-endpoints. El secreto de firma se muestra una sola vez.

Payload

{
  "id": "evt_01J9ZK3T6",
  "type": "booking.confirmed",
  "created_at": "2026-10-14T15:21:07-03:00",
  "workspace_id": "ws_9f2c",
  "data": { "booking": { "id": "bkg_7Qx1", "status": "confirmed", "start": "2026-10-15T09:45:00-03:00" } }
}

La entrega es al menos una vez: usá el id para ignorar duplicados.

Verificar la firma

Cada request trae el header:

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

v1 es HMAC-SHA256(secreto, t + "." + body_crudo) en hexadecimal. Rechazá requests de más de 5 minutos.

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", ""))

Reintentos

Respondé con cualquier 2xx en menos de 10 segundos. Si no, reintentamos a los 1 min, 5 min, 30 min, 2 h y 12 h. Cada intento se ve en el log de entregas, donde también podés reenviar a mano.