Webhooks entrantes
Registra endpoints que disparan tareas o emiten eventos, con firma HMAC.
Un webhook entrante es un endpoint que Meteor expone para que un tercero le haga POST. Cuando llega un evento, Meteor puede disparar una tarea o emitir un evento interno. Necesita el scope webhooks:manage.
Estos son webhooks entrantes (tercero → Meteor). Si lo que quieres es que Meteor te avise cuando pasa algo (
run.completed, etc.), salta a Eventos salientes más abajo.
Crear un webhook
const wh = await met.webhooks.create({
name: 'Entrada de leads',
binding_type: 'generic', // 'task' ejecuta una tarea; 'generic' solo emite evento
verifier_type: 'hmac_sha256', // 'none' para sin firma
});
console.log(wh.url); // la URL a la que apunta el tercero
console.log(wh.secret); // ⚠️ se muestra UNA sola vez
Guarda el secret apenas lo recibes: no se vuelve a mostrar. Con él verificas que cada entrega vino realmente de tu integración.
Verificar la firma
El tercero firma el cuerpo con el secret (HMAC-SHA256) y manda la firma en un header. Del lado de Meteor, la verificación es automática si el webhook es hmac_sha256.
Rotar el secret
Si sospechas que se filtró:
const rotated = await met.webhooks.rotateSecret(wh.id);
console.log(rotated.secret); // nuevo secret; invalida el anterior
Ver entregas
const events = await met.webhooks.events(wh.id, { limit: 20 });
const sample = await met.webhooks.sample(wh.id); // último payload recibido
sample es útil para configurar el mapeo de campos del payload.
Conectar un flujo
Un webhook generic puede disparar un flujo cuando recibe un evento (connectFlow en la API). Así, un POST externo arranca una automatización completa en Meteor.
Eventos salientes (Meteor → tu servidor)
Un evento saliente es Meteor haciéndote POST a *ti* cuando algo pasa en tu workspace — por ejemplo, cuando un Run termina. Registras un endpoint, eliges qué eventos quieres, y Meteor entrega cada uno firmado con HMAC para que puedas verificar que vino de Meteor.
Registrar un endpoint
Registras el endpoint desde tu panel: Desarrolladores → Actividad → Webhooks. Eliges la URL de tu servidor y qué eventos quieres (o * para todos). Al crearlo, Meteor te muestra el secreto de firma (whsec_...) una sola vez — guárdalo, lo necesitas para verificar cada entrega.
Catálogo de eventos
Son 17. La columna scope es el permiso que la key debe portar para recibir ese evento: el mismo con el que leerías ese recurso por la API.
| Evento | Cuándo dispara | Scope |
|---|---|---|
run.completed | Un Run terminó con éxito. | runs:read |
run.failed | Un Run falló. | runs:read |
channel.run.completed | El Met respondió un mensaje de canal. | runs:read |
channel.run.failed | El Met falló al responder un mensaje de canal. | runs:read |
contact.created | Se creó un contacto. | contacts:read |
contact.updated | Se actualizó un contacto. | contacts:read |
contact.message.received | Entró un mensaje de un contacto por un canal (WhatsApp, etc.). | conversations:read |
conversation.handoff | Una conversación pasó a operador humano. | conversations:read |
task.completed | Una tarea agéntica terminó. | tasks:read |
billing.threshold | El gasto de Energía de una API key cruzó un umbral de su presupuesto (50/80/100%). | billing:read |
app.authorized | Un workspace autorizó tu app OAuth (nuevo grant). | integrations:read |
app.revoked | Un workspace revocó el acceso de tu app OAuth (grant revocado). | integrations:read |
snapshot.published | Una plantilla propia fue aprobada y publicada al marketplace. | snapshots:read |
snapshot.install.completed | La instalación de una plantilla terminó. | snapshots:read |
snapshot.install.failed | La instalación de una plantilla falló. | snapshots:read |
conversion.sent | Una conversión se emitió con éxito a Meta (CAPI). | conversions:read |
conversion.discarded | Una conversión propuesta fue descartada por la compuerta (incluye la razón). | conversions:read |
* no es un evento: es el comodín «todos los del catálogo», y cubre también los que agreguemos después. Si prefieres enterarte de un evento nuevo antes de empezar a recibirlo, suscríbete a la lista explícita.
Tres cosas que ahorran una depuración:
channel.run.*no esrun.*. Unrun.*es un Run de la API, conidconsultable; unchannel.run.*es el Met respondiendo un mensaje de canal (WhatsApp y demás) y no deja fila en Runs. Si escuchas solorun.completed, las respuestas de canal no te llegan nunca.app.authorizedyapp.revokedse entregan al workspace que es dueño de la app OAuth —el tuyo, el del developer—, no al que autoriza.- Las entregas de prueba llegan con
type: "ping", que no está en el catálogo. Tu handler debería ignorar lo que no reconoce en vez de fallar.
Esta misma tabla la devuelve la API, siempre al día:
const { data } = await met.webhooks.subscriptions.eventTypes();
// [{ type: 'run.completed', description: '…', scope: 'runs:read' }, …]
Verificar la firma
Cada entrega trae el header X-Met-Signature: t=<ts>,v1=<hmac>. Nunca proceses un evento sin verificarlo — usa el helper del SDK, que hace la verificación timing-safe y chequea el timestamp (anti-replay):
import Met from '@meteor.ia/sdk';
const met = new Met(process.env.MET_API_KEY!);
app.post('/webhooks/met', (req, res) => {
let event;
try {
event = met.webhooks.constructEvent(req.rawBody, req.headers['x-met-signature'], WHSEC);
} catch {
return res.status(400).send('firma inválida');
}
if (event.type === 'run.completed') { /* ... */ }
res.sendStatus(200); // responde 2xx rápido; Meteor reintenta si no.
});
Necesitas el cuerpo crudo (raw body) para verificar la firma — configura tu framework para no re-serializar el JSON antes de
constructEvent.
Reintentos
Si tu endpoint no responde 2xx, Meteor reintenta con backoff exponencial (1 min → 5 min → 30 min → 2 h → 6 h → 24 h) hasta 6 intentos. Después marca la entrega como failed. El log de intentos por endpoint está en tu Workbench (sección Desarrolladores).