MelioraAPI EN
Ранний доступ. Ключи выдаются по запросу. Как получить ключ

Надежная связь строится на идентификаторе записи вашей CRM, который вы передаете вместе с контактами. API возвращает его в карточке лида, а события связываются с лидом по его id.

#Поле record_id

Передайте идентификатор записи CRM при загрузке контактов в кампанию. В файле контактов добавьте колонку record_id и в окне загрузки выберите для нее «Своя колонка» с именем record_id. Значение до 128 знаков, без переводов строк.

API вернет его в fields.record_id у лида: в GET /v1/leads и GET /v1/leads/{id}.

JSON
{
  "fields": {
    "record_id": "crm-10482",
    "first_name": "Анна",
    "company": "ООО Пример"
  },
  "fields_truncated": false,
  "record_id_status": "valid"
}

#Статус record_id

Прежде чем использовать record_id, проверьте record_id_status:

ЗначениеЧто означаетЧто делать
validОдно непротиворечивое значение.Ищите запись CRM по fields.record_id.
missingЗначения нет.Сопоставьте вручную или по своим правилам.
conflictВ карточке контакта два разных значения. fields.record_id равен null.Исправьте карточку, не выбирайте значение сами.
unknownНадежность значения не подтверждена, например у старых данных. fields.record_id равен null.Сверьте лида по GET /v1/leads/{id} или вручную.

valid означает, что значение корректно в данных Meliora. Существует ли такая запись в вашей CRM, проверяет ваша интеграция.

У событий record_id_status сейчас всегда unknown, см. предупреждение выше.

#Почему не ник

Ник Telegram (username) не подходит как ключ связи:

  • человек может сменить или удалить ник, тогда username станет другим или null;
  • один контакт в двух кампаниях дает два лида с разными id и одинаковым ником;
  • у разных людей в разное время может быть один и тот же ник.

Фильтр GET /v1/leads?username=... удобен для поиска и проверки, но не для автоматического сопоставления. Поле external_id в v1 всегда null: оно зарезервировано для следующих версий.

#Какие поля выдаются

В fields попадают только перечисленные ключи, если они есть у контакта: record_id, first_name, last_name, company, position, website, source, utm_source, utm_medium, utm_campaign. Ключ record_id есть всегда, остальные только при наличии значения.

Не выдаются: телефон, email, другие свои колонки и служебные данные. Колонка «Имя и фамилия» тоже не попадает в fields: для API загружайте имя и фамилию отдельными колонками «Имя» и «Фамилия».

Значения длиннее 1024 знаков сокращаются, весь объект не больше 8 КиБ. Если что-то сокращено или скрыто из-за размера, fields_truncated равен true.

#Статусы в CRM

Пример соответствия для воронки CRM:

Событие или полеСтатус в CRM
lead.first_message_sent или непустой first_message_sent_atВ работе
lead.replied или непустой first_reply_atСвязались

Событие lead.replied приходит на каждый засчитанный ответ, поэтому у одного лида их может быть несколько. Обновляйте статус только вперед: «Связались» не должен откатываться в «В работе», даже если событие первого сообщения пришло позже ответа. Времена касания и ответа объединяйте так же: сохраняйте самое раннее известное время, не заменяйте его более поздним.

const RANK = { IN_PROGRESS: 1, CONNECTED: 2 };

// record_id берем из карточки лида и запоминаем: в событиях он не передается.
async function crmIdFor(tx, leadId) {
  const known = await tx.getCrmId(leadId);
  if (known !== undefined) return known;
  const lead = await meliora(`/leads/${leadId}`);
  const crmId = lead.record_id_status === 'valid' ? lead.fields.record_id : null;
  await tx.rememberCrmId(leadId, crmId);
  return crmId;
}

async function applyToCrm(tx, event) {
  const leadId = event.data.lead.id;
  const crmId = await crmIdFor(tx, leadId);
  if (!crmId) {
    await tx.queueForReview(event.id, leadId); // ручная проверка
    return;
  }
  const target = event.type === 'lead.replied' ? 'CONNECTED' : 'IN_PROGRESS';
  const record = await tx.crm.get(crmId);
  if (!record) return tx.queueForReview(event.id, leadId);
  if ((RANK[record.status] ?? 0) < RANK[target]) await tx.crm.setStatus(crmId, target);
  if (event.type === 'lead.replied') {
    await tx.crm.addNote(crmId, `Ответ в Telegram, ${event.data.reply.replied_at}`);
  }
}

#Переписка в CRM

Тексты сообщений можно получить двумя способами:

  • в событиях с include_text=true: текст первого сообщения и текст ответа;
  • из переписки GET /v1/leads/{id}/messages: все сохраненные сообщения с лидом.

Что важно знать об архиве переписки:

  • Архив может быть неполным: переписка до подключения API сохранена частично, сообщения, отправленные с телефона или из Telegram напрямую, в него не попадают. Сейчас meta.completeness всегда сообщает о неполноте.
  • Правки и удаления сообщений в Telegram не отслеживаются: в копии остается первоначальный текст.
  • Вложения передаются только видом в media_kind, без файлов. Если исходящее содержало вложение, его текста в копии может не быть.
  • Сообщение может появиться в архиве позже и с более ранним временем, чем уже прочитанные. Поэтому читайте переписку лида целиком, все страницы, и объединяйте с уже сохраненной по id.
  • При text_unavailable: true текст не выдается, причина в text_unavailable_reason. Не создавайте в CRM пустые заметки вместо таких сообщений.

Читайте переписку тех лидов, по которым пришло событие или изменился статус, а не всех подряд: запрос весит 3.

#Первичная загрузка

Чтобы связать уже идущие кампании:

  1. Получите курсор ленты: GET /v1/events?start=now.
  2. Обойдите GET /v1/leads нужных кампаний, сопоставьте лидов с записями CRM по record_id и сохраните пары lead.id и record_id для обработки событий.
  3. Перенесите статусы по first_message_sent_at и first_reply_at.
  4. Начните опрос ленты от курсора из шага 1.

Подробности цикла в руководстве Опрос событий.