# Link to the conversation from your CRM

Your team lives in your CRM, but customer conversations happen in Meteor. Every contact
comes with an **`app_url`**: the link that opens their conversation in Meteor. Store it on
the customer record in your system and whoever is looking at that record reaches the chat
in one click.

The link is stable: it doesn't include the workspace name, so it keeps working if the
workspace is renamed. Opening it requires a Meteor session with access to that contact.
It is not a public link to the conversation.

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

There are two ways to fill it in. You can **ask** Meteor whenever you need it (step 2), or
let Meteor **notify** you every time a contact is created, changes or writes in (step 3).
If your system can receive a `POST`, the second keeps the record current without any
polling.

## Before you start

Your key needs `contacts:read`. To receive events, also `webhooks:manage` and
`conversations:read`.

## 1. Pick the value you match on

Your system does the matching, with a value both sides have:

- **Phone** — `data.$phone`, in international format with `+` (`+573001112233`). If your
  system stores it differently (`3001112233`, `57 300 111 2233`), normalize it before
  comparing: the filter compares the exact text.
- **Tax ID, national ID or another document** — works if it exists as a custom contact
  field in Meteor and is filled in. Custom fields use their `field_key` without `$`
  (`nit`, `cedula`). List the workspace's fields with `GET /variables/contact-fields`.

## 2. Ask for a customer's link

Find the contact with a filter and read its `app_url`:

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

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

async function meteorLink(taxId: string): Promise<string | null> {
  const page = await met.contacts.list({
    filters: JSON.stringify([{ field: 'nit', op: 'eq', value: taxId }]),
    limit: 2,
  });
  if (page.data.length !== 1) return null;   // 0: not in Meteor · 2+: check for duplicates
  return page.data[0].app_url ?? null;
}
```

A malformed condition **is ignored, it doesn't error**: if you misspell the field name,
the filter returns every contact. That's why the example asks for `limit: 2` and only
accepts exactly one result.

If you already have the contact's `id`, `met.contacts.retrieve(id)` also returns the
`app_url`.

## 3. Let Meteor send it

Subscribe to contact events. Every event about a contact carries its `app_url` in
`data`: `contact.created`, `contact.updated`, `contact.message.received`,
`contact.message.sent` and `conversation.handoff`.

```ts
const sub = await met.webhooks.subscriptions.create({
  url: 'https://your-crm.com/webhooks/met',
  enabled_events: ['contact.created', 'contact.updated', 'contact.message.received'],
  description: 'Conversation link on the customer record',
});

console.log(sub.secret);   // whsec_… — shown ONCE
```

The endpoint verifies the signature, answers fast and stores the link on your record:

```ts
app.post('/webhooks/met', express.raw({ type: 'application/json' }), async (req, res) => {
  let event;
  try {
    event = met.webhooks.constructEvent(
      req.body,
      req.headers['x-met-signature'] as string,
      process.env.MET_WEBHOOK_SECRET!,
    );
  } catch {
    return res.status(400).send('invalid signature');
  }
  res.sendStatus(200);

  const { contact_id, app_url } = event.data as { contact_id: number; app_url?: string };
  if (!app_url) return;
  const contact = await met.contacts.retrieve(contact_id);
  await yourCrm.saveLink({
    phone: contact.data.$phone,
    taxId: contact.data.nit,
    link: app_url,
  });
});
```

Saving the same link twice does no harm, so this endpoint survives retries without
keeping track of which events it already processed. The recipe
[Receive signed events in your backend](recipe-signed-webhooks.html) covers signatures and
retries in detail.

## No code: from a flow

If you don't have a backend, a Meteor flow can send the link to your system with an HTTP
request node. The variable is `$contact.app_url`, just like `$contact.phone` or
`$contact.name`.
