Recibir eventos en tiempo real

De cero a un consumidor de eventos en producción, con cola y reintentos.

Antes de empezar

  • Un endpoint HTTPS público
  • Una cola o worker: Redis, SQS, Cloud Tasks o similar

Tinkay emite 126 eventos distintos. Un consumidor bien hecho tiene tres partes: verificar, encolar y procesar. Separarlas es lo que evita perder eventos cuando tu servicio tiene un pico o se cae.

1. Elegí los eventos

Suscribite solo a lo que vas a usar. Podés empezar con un grupo entero y afinar después.

  1. En Configuración, Desarrolladores, Webhooks, creá el webhook con tu URL.
  2. Elegí los eventos con el buscador o seleccionando un grupo completo.
  3. Copiá el signing secret y guardalo como variable de entorno.

2. Verificá y encolá

El endpoint HTTP hace lo mínimo: valida la firma, encola y responde 200. Nada de lógica de negocio acá.

server.js
import express from "express";import crypto from "node:crypto";import { queue } from "./queue.js";const app = express();const SECRET = process.env.TINKAY_WEBHOOK_SECRET;app.post("/hooks/tinkay", express.raw({ type: "application/json" }), async (req, res) => {  const raw = req.body.toString("utf8");  if (!verify(raw, req.get("X-Tinkay-Timestamp"), req.get("X-Tinkay-Signature"))) {    return res.status(400).send("invalid signature");  }  const event = JSON.parse(raw);  await queue.add("tinkay-event", event, { jobId: event.id }); // jobId evita duplicados  res.sendStatus(200);});function verify(raw, timestamp, signature) {  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;  const expected = crypto.createHmac("sha256", SECRET).update(`${timestamp}.${raw}`).digest("hex");  const a = Buffer.from(expected, "hex");  const b = Buffer.from(String(signature).replace("sha256=", ""), "hex");  return a.length === b.length && crypto.timingSafeEqual(a, b);}

Usar el id del evento como jobId de la cola resuelve la idempotencia sin escribir una línea extra.

3. Procesá por tipo

El worker despacha por nombre de evento. Lo que no conocés se ignora sin romper nada.

worker.js
const handlers = {  "conversation.created": onConversationCreated,  "conversation.closed": onConversationClosed,  "ai.handoff": onHandoff,  "contact.updated": onContactUpdated,};export async function process(event) {  const handler = handlers[event.name];  if (!handler) return; // evento nuevo o no suscrito: se ignora  await handler(event.data, event);}

4. Monitoreá

  • El registro de entregas del webhook muestra código, duración e intento de cada entrega.
  • Alertá sobre el evento webhook.delivery_failed: significa que agotamos los reintentos.
  • Si acumulás fallas, el webhook se desactiva solo y recibís webhook.disabled.

Cómo saber que quedó bien

  • El registro de entregas en Configuración, Desarrolladores muestra 200 en todas las entregas.
  • Reiniciar el worker no pierde eventos: quedan en la cola.
  • Reenviar una entrega desde el panel no duplica el efecto en tu sistema.