# Recibir eventos firmados en tu backend

Sondear la API para saber si algo pasó es caro y llega tarde. Con una suscripción,
Meteor te hace `POST` cuando el hecho ocurre. Esta receta arma el endpoint completo:
firma verificada, respuesta rápida y un procesamiento que aguanta reintentos.

## Antes de empezar

Tu key necesita `webhooks:manage`, más el scope del evento que quieras escuchar —
`runs:read` para `run.completed`, `contacts:read` para `contact.created`, y así. Si pides
un evento cuyo scope no tienes, no llega.

## 1. Suscríbete

```ts
import Met from '@meteor.ia/sdk';

const met = new Met(process.env.MET_API_KEY!, {
  workspaceId: Number(process.env.MET_WORKSPACE_ID),
});

const sub = await met.webhooks.subscriptions.create({
  url: 'https://tu-servidor.com/webhooks/met',
  enabled_events: ['run.completed', 'run.failed', 'task.completed'],
  description: 'Procesador de resultados',
});

console.log(sub.secret);   // whsec_… — se muestra UNA sola vez
```

`enabled_events: ['*']` te trae todos. Conviene enumerar los que de verdad procesas: un
endpoint que recibe diecisiete tipos y actúa sobre tres es un endpoint que se cae por un
evento que nadie miró.

Los tipos disponibles salen de la API, así que no hay que adivinarlos:

```ts
const tipos = await met.webhooks.subscriptions.eventTypes();
```

## 2. El endpoint

```ts
import express from 'express';

const app = express();
// Cuerpo CRUDO. La firma se calcula sobre los bytes exactos que mandó Meteor:
// si tu framework parsea y vuelve a serializar el JSON, deja de coincidir.
app.use('/webhooks/met', express.raw({ type: 'application/json' }));

app.post('/webhooks/met', async (req, res) => {
  let evento;
  try {
    evento = met.webhooks.constructEvent(
      req.body,
      req.headers['x-met-signature'] as string,
      process.env.MET_WEBHOOK_SECRET!,
    );
  } catch {
    // Firma inválida o timestamp viejo. No reintentar: no va a mejorar.
    return res.status(400).send('firma inválida');
  }

  // 2xx primero, trabajo después: Meteor considera fallida la entrega que no
  // responde, y tu procesamiento puede tardar más que su tiempo de espera.
  res.sendStatus(200);
  await encolar(evento);
});

app.listen(3000);
```

`constructEvent` verifica la firma en tiempo constante y chequea el timestamp del header
`X-Met-Signature: t=<ts>,v1=<hmac>`, que es lo que impide que alguien te reenvíe una
entrega vieja legítima.

## 3. Sobrevivir a los reintentos

Si tu endpoint no responde `2xx`, Meteor reintenta con espera creciente —1 min, 5 min,
30 min, 2 h, 6 h, 24 h— hasta seis veces. Eso significa que **el mismo evento puede
llegarte más de una vez**, y también que puede llegar tarde y desordenado.

Guarda el id del evento antes de actuar:

```ts
async function encolar(evento) {
  const nuevo = await tuBase.insertarSiNoExiste('eventos_met', { id: evento.id });
  if (!nuevo) return;              // ya lo procesamos: la entrega es un reintento
  await procesar(evento);
}
```

La regla que evita el susto: **el orden de llegada no es el orden de los hechos.** Si tu
lógica depende de la secuencia, ordénala por el `created` del evento —un epoch en
segundos— y no por cuándo lo recibiste.

Cada entrega viene con esta forma:

```json
{
  "id": "evt_9f2c…",
  "object": "event",
  "type": "run.completed",
  "created": 1786886400,
  "livemode": true,
  "data": { }
}
```

## 4. Probarlo antes de que pase algo de verdad

```ts
await met.webhooks.subscriptions.test(sub.id);   // entrega de prueba, firmada igual
```

Y contra tráfico real, sin desplegar:

```bash
met listen --forward http://localhost:3000/webhooks/met --types run.completed,run.failed
```

Si una entrega falló y ya arreglaste la causa, se puede reintentar a mano desde el log de
entregas, que está en tu panel en **Desarrolladores → Actividad**.

## Y después

- El catálogo completo de eventos y el detalle de los reintentos:
  [Webhooks](webhooks.html).
- Si lo que quieres es reaccionar en el mismo segundo y no en la próxima entrega:
  [Eventos en vivo](eventos.html).
