# Enlace a la conversación desde tu CRM

Tu equipo vive en tu CRM, pero las conversaciones con los clientes pasan en Meteor. Cada
contacto trae un **`app_url`**: el enlace que abre su conversación en Meteor. Si lo guardas
en la ficha del cliente de tu sistema, quien esté mirando esa ficha llega al chat con un
clic.

El enlace es estable: no lleva el nombre del workspace, así que sigue funcionando aunque
el workspace cambie de nombre. Abrirlo pide una sesión en Meteor con acceso a ese
contacto. No es un enlace público a la conversación.

```json
{
  "id": 4821,
  "data": { "$name": "Ada Lovelace", "$phone": "+573001112233", "nit": "900123456" },
  "app_url": "https://met.meteor.com.co/abrir/137/contacto/4821"
}
```

Hay dos formas de llenarlo. Puedes **consultar** a Meteor cuando lo necesites (paso 2) o
dejar que Meteor te **avise** cada vez que un contacto se crea, cambia o escribe (paso 3).
Si tu sistema tiene dónde recibir un `POST`, la segunda deja la ficha al día sin que
consultes nada.

## Antes de empezar

Tu key necesita `contacts:read`. Para recibir eventos, además `webhooks:manage` y
`conversations:read`.

## 1. Decide con qué dato cruzas

El cruce lo hace tu sistema, con un dato que tengan los dos lados:

- **Teléfono** — `data.$phone`, en formato internacional con `+` (`+573001112233`). Si tu
  sistema lo guarda distinto (`3001112233`, `57 300 111 2233`), normalízalo antes de
  comparar: el filtro compara el texto exacto.
- **NIT, cédula u otro documento** — sirve si existe como campo personalizado del contacto
  en Meteor y está lleno. Los campos personalizados van con su `field_key` sin `$`
  (`nit`, `cedula`). Descubre los del workspace con `GET /variables/contact-fields`.

## 2. Consultar el enlace de un cliente

Busca el contacto con un filtro y lee su `app_url`:

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

const met = new Met({ apiKey: process.env.MET_API_KEY! });

async function enlaceDeMeteor(nit: string): Promise<string | null> {
  const pagina = await met.contacts.list({
    filters: JSON.stringify([{ field: 'nit', op: 'eq', value: nit }]),
    limit: 2,
  });
  if (pagina.data.length !== 1) return null;   // 0: no está en Meteor · 2+: revisa duplicados
  return pagina.data[0].app_url ?? null;
}
```

Una condición mal formada **se ignora, no da error**: si escribes mal el nombre del
campo, el filtro devuelve todos los contactos. Por eso el ejemplo pide `limit: 2` y solo
acepta exactamente un resultado.

Si ya tienes el `id` del contacto, `met.contacts.retrieve(id)` también trae el `app_url`.

## 3. Que Meteor te lo mande

Suscríbete a los eventos de contacto. Todos los que hablan de un contacto llevan su
`app_url` en `data`: `contact.created`, `contact.updated`, `contact.message.received`,
`contact.message.sent` y `conversation.handoff`.

```ts
const sub = await met.webhooks.subscriptions.create({
  url: 'https://tu-crm.com/webhooks/met',
  enabled_events: ['contact.created', 'contact.updated', 'contact.message.received'],
  description: 'Enlace a la conversación en la ficha del cliente',
});

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

El endpoint verifica la firma, responde rápido y guarda el enlace en tu ficha:

```ts
app.post('/webhooks/met', express.raw({ type: 'application/json' }), 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 {
    return res.status(400).send('firma inválida');
  }
  res.sendStatus(200);

  const { contact_id, app_url } = evento.data as { contact_id: number; app_url?: string };
  if (!app_url) return;
  const contacto = await met.contacts.retrieve(contact_id);
  await tuCrm.guardarEnlace({
    telefono: contacto.data.$phone,
    nit: contacto.data.nit,
    enlace: app_url,
  });
});
```

Guardar el mismo enlace dos veces no hace daño. Por eso este endpoint aguanta los
reintentos sin llevar la cuenta de qué eventos procesó. La receta
[Recibir eventos firmados en tu backend](receta-webhooks-firmados.html) explica la firma y
los reintentos en detalle.

## Sin programar: desde un flujo

Si no tienes un backend, un flujo de Meteor puede mandar el enlace a tu sistema con un
nodo de petición HTTP. La variable es `$contact.app_url`, igual que `$contact.phone` o
`$contact.name`.
