Recibir webhooks
Los webhooks te envían eventos en tiempo real en lugar de tener que hacer polling. Suscribe una URL, elige los eventos que te interesan y verifica cada entrega con su firma HMAC.
Suscribirse
Crea una suscripción a través de la API de Webhooks con la URL de tu endpoint y los eventTypes que quieres recibir. Recibirás a cambio un secreto de firma: guárdalo, porque es lo que verifica cada entrega.
curl -H "Authorization: Bearer vokse_sk_…" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -X POST https://api.vokse.ai/households/current/webhooks/subscriptions \ -d '{"url":"https://example.com/hooks/vokse","eventTypes":["transaction.created","budget.overrun"]}'Tipos de evento
La lista completa y siempre actualizada de tipos de evento se sirve desde /meta/webhook-events. El catálogo actual es este:
- transaction.created: se ha registrado una transacción.
- recurrence.materialised: una recurrencia ha generado su transacción programada.
- bank.sync.completed: una conexión bancaria ha terminado de sincronizarse.
- budget.overrun: una categoría se ha pasado de su presupuesto del mes.
- spending.anomaly: se ha detectado un gasto inusual.
- goal.reached: un objetivo de ahorro ha llegado a su importe.
- insight.generated: hay un nuevo análisis de IA disponible.
Verificar la firma
Cada entrega lleva una cabecera X-Vokse-Signature con la forma t=<timestamp>,v1=<firma>. La firma es un HMAC-SHA256, con tu secreto de firma como clave, sobre el timestamp, un punto y el cuerpo crudo de la petición. Reconstruye esa cadena con los bytes exactos que recibiste, recalcula el HMAC, compáralo con v1 en tiempo constante y rechaza los timestamps demasiado antiguos.
import crypto from 'node:crypto';// X-Vokse-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256>function verify(rawBody, signatureHeader, signingSecret) { const parts = new Map( signatureHeader.split(',').map((kv) => kv.split('=')), ); const t = parts.get('t'); const v1 = parts.get('v1'); const expected = crypto .createHmac('sha256', signingSecret) .update(`${t}.${rawBody}`) // timestamp, a dot, the exact bytes received .digest('hex'); if (!t || !v1 || v1.length !== expected.length) return false; // freshness: reject stale timestamps to block replays if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));}Reintentos y entrega
Responde 2xx en unos pocos segundos para confirmar la recepción. Las respuestas 5xx y los timeouts se reintentan con retroceso exponencial, hasta cinco intentos; un 4xx se considera definitivo y no se reintenta. Tras varios fallos consecutivos, la suscripción se pausa automáticamente y se avisa al propietario del hogar.