Связь с CRM
Надежная связь строится на идентификаторе записи вашей CRM, который вы передаете вместе с контактами. API возвращает его в карточке лида, а события связываются с лидом по его id.
#Поле record_id
Передайте идентификатор записи CRM при загрузке контактов в кампанию. В файле контактов добавьте колонку record_id и в окне загрузки выберите для нее «Своя колонка» с именем record_id. Значение до 128 знаков, без переводов строк.
API вернет его в fields.record_id у лида: в GET /v1/leads и GET /v1/leads/{id}.
{
"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}`);
}
}RANK = {"IN_PROGRESS": 1, "CONNECTED": 2}
def crm_id_for(tx, lead_id):
# record_id берем из карточки лида и запоминаем: в событиях он не передается.
known = tx.get_crm_id(lead_id)
if known is not None:
return known or None
lead = meliora(f"/leads/{lead_id}")
crm_id = lead["fields"]["record_id"] if lead["record_id_status"] == "valid" else None
tx.remember_crm_id(lead_id, crm_id or "")
return crm_id
def apply_to_crm(tx, event):
lead_id = event["data"]["lead"]["id"]
crm_id = crm_id_for(tx, lead_id)
if not crm_id:
tx.queue_for_review(event["id"], lead_id) # ручная проверка
return
target = "CONNECTED" if event["type"] == "lead.replied" else "IN_PROGRESS"
record = tx.crm.get(crm_id)
if record is None:
tx.queue_for_review(event["id"], lead_id)
return
if RANK.get(record.status, 0) < RANK[target]:
tx.crm.set_status(crm_id, target)
if event["type"] == "lead.replied":
tx.crm.add_note(crm_id, f"Ответ в 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.
#Первичная загрузка
Чтобы связать уже идущие кампании:
- Получите курсор ленты:
GET /v1/events?start=now. - Обойдите
GET /v1/leadsнужных кампаний, сопоставьте лидов с записями CRM поrecord_idи сохраните парыlead.idиrecord_idдля обработки событий. - Перенесите статусы по
first_message_sent_atиfirst_reply_at. - Начните опрос ленты от курсора из шага 1.
Подробности цикла в руководстве Опрос событий.