OdyOdy Ayuda

Recibir webhooks y verificar firmas

Actualizado Sun Aug 30 2026 00:00:00 GMT+0000 (Coordinated Universal Time)

Ody envía eventos del espacio de trabajo — nuevos mensajes, resultados de llamadas, cambios de contacto — a los puntos finales HTTPS que registras a través de la API, con cada entrega firmada según la especificación de Standard Webhooks para que puedas probar que provienen de Ody.

Antes de empezar

  • Necesitas una clave API con el alcance webhooks:manage — consulta Obtener tu clave API de Ody.
  • Tu punto final debe ser una URL HTTPS que responda con un estado 2xx rápidamente (las entregas agotan el tiempo de espera después de 10 segundos).
  • Cada espacio de trabajo puede registrar hasta 50 puntos finales.

Paso a paso

  1. Enumera los tipos de eventos a los que puedes suscribirte:
curl https://api.ody.co/v1/api/webhook-events \
  -H "Authorization: Bearer ody_live_…"

El catálogo actual: message.received, message.delivered, message.failed, call.ringing, call.answered, call.completed, call.forwarded, call.missed, call.recording.completed, call.summary.completed, call.transcript.completed, call.voicemail.completed, call.flow.started, call.flow.completed, call.flow.aborted, contact.updated, contact.deleted. Suscribirse a "*" cubre todos los tipos de eventos actuales y futuros.

Los eventos call.flow.* se activan cuando una llamada es respondida por un flujo de llamadas: started cuando la persona que llama entra en el flujo, completed cuando llega a un destino (con el tipo de destino — timbre, asistente de IA, buzón de voz, desvío o colgar), y aborted si el flujo no pudo finalizar.

  1. Registra tu punto final:
curl https://api.ody.co/v1/api/webhooks \
  -H "Authorization: Bearer ody_live_…" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/ody-webhook","events":["message.received"],"label":"prod"}'
  1. Guarda el secret de la respuesta inmediatamente. El secreto de firma whsec_… se devuelve solo al crear y rotar — nunca se muestra de nuevo.
  2. Envía un evento de prueba firmado y confirma que tu punto final lo verifica y acepta:
curl -X POST https://api.ody.co/v1/api/webhooks/WEBHOOK_ID/test \
  -H "Authorization: Bearer ody_live_…"

La prueba publica un evento {"type":"ping"} firmado exactamente como una entrega real.

Cómo se ve una entrega

Cada entrega es un POST con un cuerpo JSON y tres encabezados de firma:

{
  "id": "evt_…",
  "apiVersion": "2026-06-01",
  "createdAt": "2026-08-16T12:00:00.000Z",
  "type": "message.received",
  "data": { "conversationId": "…", "activityId": "…", "numberE164": "+1…" }
}
Encabezado Significado
webhook-id El ID del evento — desduplicar en él, ya que los reintentos lo reutilizan
webhook-timestamp Segundos Unix en el momento del envío
webhook-signature v1,<base64 HMAC-SHA256> sobre <id>.<timestamp>.<raw body>

Verificación de la firma

La clave HMAC es la parte decodificada en base64 de tu secreto después del prefijo whsec_, y el mensaje firmado es <webhook-id>.<webhook-timestamp>.<raw body>. Siempre verifica contra los bytes de solicitud sin procesar — volver a serializar el JSON analizado rompe la firma. Rechaza las marcas de tiempo de más de unos pocos minutos para bloquear las repeticiones.

import { createHmac, timingSafeEqual } from "node:crypto";
import express from "express";

const app = express();
app.post("/ody-webhook", express.raw({ type: "application/json" }), (req, res) => {
  const secret = process.env.ODY_WEBHOOK_SECRET; // whsec_…
  const id = req.headers["webhook-id"];
  const timestamp = req.headers["webhook-timestamp"];
  const signature = req.headers["webhook-signature"];

  // Replay guard: reject deliveries older than 5 minutes.
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.status(401).end();

  const key = Buffer.from(secret.slice("whsec_".length), "base64");
  const expected = "v1," + createHmac("sha256", key)
    .update(`${id}.${timestamp}.${req.body}`)
    .digest("base64");

  const a = Buffer.from(expected);
  const b = Buffer.from(String(signature));
  if (a.length !== b.length || !timingSafeEqual(a, b)) return res.status(401).end();

  const event = JSON.parse(req.body.toString("utf8"));
  console.log("verified event:", event.type);
  res.status(200).end();
});

Los SDK oficiales incluyen esto como una sola línea — verifyWebhookSignature (TypeScript) y verify_webhook_signature (Python), ambos con comparación de tiempo constante y la protección contra repeticiones de 5 minutos incorporada. Consulta Inicio rápido del SDK: TypeScript y Python.

Reintentos, historial y gestión

  • Reintentos: una entrega fallida (no 2xx, tiempo de espera o error de conexión) se reintenta aproximadamente 5 segundos y luego 30 segundos después del primer intento — tres intentos en total, con un tiempo de espera de 10 segundos cada uno.
  • Historial: GET /v1/api/webhooks/:id/deliveries lista las entregas recientes de la más nueva a la más antigua, con códigos de estado, errores y duraciones por intento — tu primera parada cuando parezca que faltan eventos.
  • Administrar puntos finales: GET /v1/api/webhooks los lista (nunca el secreto), PATCH /v1/api/webhooks/:id actualiza url, events, label o status (enabled/disabled), y DELETE /v1/api/webhooks/:id elimina uno junto con su historial de entregas.
  • Rotar el secreto en cualquier momento con POST /v1/api/webhooks/:id/rotate — el nuevo whsec_… se devuelve una vez, y las firmas antiguas dejan de ser válidas inmediatamente.

Solución de problemas

  • La verificación de la firma siempre falla — probablemente estás verificando un cuerpo re-serializado. Usa los bytes de la solicitud sin procesar y confirma que decodificaste en base64 el secreto después de quitar whsec_.
  • No llegan entregas — verifica que el status del punto final esté enabled, que su lista de events incluya el tipo que esperas (o "*"), y el historial de entregas para ver errores por intento.
  • Eventos duplicados — los reintentos reutilizan el mismo webhook-id. Desduplica en él.
  • webhook limit reached — has alcanzado los 50 puntos finales para el espacio de trabajo; elimina uno no utilizado primero.

Artículos relacionados

Preguntas frecuentes

¿Cómo se firman las entregas de webhook?

Según la especificación de Standard Webhooks: un encabezado webhook-signature con el formato v1,<base64 HMAC-SHA256> calculado sobre '<webhook-id>.<webhook-timestamp>.<raw body>' con tu secreto whsec_.

¿Qué sucede si mi punto final está inactivo?

Ody reintenta dos veces — aproximadamente 5 segundos y luego 30 segundos después del primer fallo. Consulta GET /v1/api/webhooks/:id/deliveries para ver los códigos de estado y errores por intento.

Perdí mi secreto de firma, ¿ahora qué?

Rótalo con POST /v1/api/webhooks/:id/rotate. El nuevo secreto whsec_ se devuelve una vez; las firmas antiguas dejan de ser válidas inmediatamente.

Más en API para desarrolladores