HubSpot + Drift: почему нативная интеграция не привязывает разговоры к сделке

Drift (теперь - Salesloft Conversations) - платформа conversational marketing: чат-боты, live chat, видео, Sales meetings. Компании используют Drift для захвата лидов с сайта и квалификации через разговор. HubSpot имеет нативную интеграцию с Drift через HubSpot Marketplace. Проблема: нативная интеграция создаёт Контакт (Contact) в HubSpot при каждом новом разговоре в Drift, но не создаёт и не обновляет Сделку (Deal). История разговора (transcript, meeting booked, playbook triggered) не попадает в Deal Timeline.

Sales ops не видит контекст Drift-разговора в сделке. Когда AE перехватывает лид от Drift-бота - история переписки и результаты квалификации хранятся в Drift, а не в HubSpot Deal. Кастомная интеграция через Drift Conversation API + HubSpot Deals API закрывает этот разрыв.

Drift Playbook - сценарий чат-бота: набор вопросов и ветвлений для квалификации лида. Результат Playbook (email, company, budget, timeline) - ценные данные для CRM, которые нативная интеграция не передаёт в Deal.

Что делает нативная интеграция

Нативная HubSpot + Drift интеграция умеет:

  • Создавать или обновлять Contact в HubSpot при начале разговора
  • Синхронизировать email и базовые поля контакта

Нативная интеграция НЕ умеет:

  • Создавать Deal при начале или завершении разговора
  • Добавлять transcript разговора в Deal Timeline
  • Передавать Playbook-ответы как Deal Properties
  • Обновлять Deal Stage при booking meeting через Drift
  • Логировать Drift meeting в Deal как активность

Правильная архитектура

Посетитель сайта -> Drift чат -> Playbook квалификации
  -> Собирает: email, company, budget, timeline, intent

Drift webhook: conversation.status_updated (status = closed/won)
  -> Ваш сервер

Ваш сервер
  -> Drift API: GET /v2/conversations/{id} (полный transcript)
  -> HubSpot Contacts API: найти/создать Contact по email
  -> HubSpot Deals API: создать Deal с:
       - Названием: "Drift: {company} - {date}"
       - Properties: drift_conversation_id, budget, timeline, playbook_outcome
  -> HubSpot Engagements API: добавить Note с transcript в Deal Timeline
  -> HubSpot Associations: связать Deal -> Contact

Реализация: Drift webhook -> HubSpot Deal

import requests, os, hmac, hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)

DRIFT_TOKEN       = os.environ["DRIFT_API_TOKEN"]
DRIFT_WEBHOOK_SEC = os.environ["DRIFT_WEBHOOK_SECRET"]
HS_TOKEN          = os.environ["HUBSPOT_PRIVATE_APP_TOKEN"]

DRIFT_BASE = "https://driftapi.com"
DRIFT_HDR  = {"Authorization": f"Bearer {DRIFT_TOKEN}", "Content-Type": "application/json"}
HS_BASE    = "https://api.hubapi.com"
HS_HDR     = {"Authorization": f"Bearer {HS_TOKEN}", "Content-Type": "application/json"}

HS_DEAL_STAGE_NEW = os.environ.get("HS_DEAL_STAGE_ID", "appointmentscheduled")

def verify_drift_signature(body: bytes, sig_header: str) -> bool:
    # Drift HMAC-SHA256 hex
    computed = hmac.new(DRIFT_WEBHOOK_SECRET.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(computed, sig_header.replace("sha256=", ""))

def get_drift_conversation(conv_id: int) -> dict:
    r = requests.get(f"{DRIFT_BASE}/v2/conversations/{conv_id}/messages", headers=DRIFT_HDR)
    if r.status_code == 200:
        return r.json().get("data", {})
    return {}

def get_drift_playbook_data(conv_id: int) -> dict:
    r = requests.get(f"{DRIFT_BASE}/v2/conversations/{conv_id}", headers=DRIFT_HDR)
    if r.status_code != 200:
        return {}
    conv = r.json().get("data", {})
    return conv.get("attributes", {})

def build_transcript(messages: list) -> str:
    lines = []
    for msg in messages:
        author  = msg.get("author", {})
        name    = author.get("name", "Bot") if author.get("type") == "agent" else "Посетитель"
        body    = msg.get("body", "")
        if body:
            lines.append(f"{name}: {body}")
    return "
".join(lines)

def find_or_create_hs_contact(email: str, name: str) -> str:
    r = requests.get(
        f"{HS_BASE}/crm/v3/objects/contacts/search",
        headers=HS_HDR,
        json={"filterGroups": [{"filters": [{"propertyName": "email", "operator": "EQ", "value": email}]}]},
    )
    if r.status_code == 200:
        results = r.json().get("results", [])
        if results:
            return results[0]["id"]

    parts  = name.split(" ", 1)
    r_create = requests.post(
        f"{HS_BASE}/crm/v3/objects/contacts",
        headers=HS_HDR,
        json={"properties": {
            "email":     email,
            "firstname": parts[0],
            "lastname":  parts[1] if len(parts) > 1 else "",
        }},
    )
    return r_create.json().get("id", "")

def create_hs_deal(contact_id: str, conv_id: int, attrs: dict) -> str:
    company  = attrs.get("company", "")
    budget   = attrs.get("budget", "")
    timeline = attrs.get("timeline", "")
    import time
    now_ms   = int(time.time() * 1000)

    deal_name = f"Drift: {company} - {__import__('datetime').date.today().isoformat()}"
    r = requests.post(
        f"{HS_BASE}/crm/v3/objects/deals",
        headers=HS_HDR,
        json={
            "properties": {
                "dealname":             deal_name,
                "dealstage":            HS_DEAL_STAGE_NEW,
                "drift_conversation_id": str(conv_id),
                "budget_range":         budget,
                "timeline":             timeline,
                "lead_source":          "Drift Chat",
            },
            "associations": [{
                "to": {"id": contact_id},
                "types": [{"associationCategory": "HUBSPOT_DEFINED", "associationTypeId": 3}]
            }]
        },
    )
    r.raise_for_status()
    return r.json().get("id", "")

def add_note_to_deal(deal_id: str, transcript: str, conv_id: int):
    import time
    note_body = f"Drift разговор #{conv_id}:

{transcript[:4000]}"
    requests.post(
        f"{HS_BASE}/crm/v3/objects/notes",
        headers=HS_HDR,
        json={
            "properties": {
                "hs_note_body": note_body,
                "hs_timestamp": str(int(time.time() * 1000)),
            },
            "associations": [{
                "to": {"id": deal_id},
                "types": [{"associationCategory": "HUBSPOT_DEFINED", "associationTypeId": 214}]
            }]
        },
    )

@app.route("/webhooks/drift", methods=["POST"])
def drift_webhook():
    sig = request.headers.get("X-Drift-Signature", "")
    if DRIFT_WEBHOOK_SECRET and not verify_drift_signature(request.data, sig):
        return jsonify({"error": "invalid signature"}), 401

    event = request.json or {}
    # Drift отправляет массив событий
    for ev in (event if isinstance(event, list) else [event]):
        ev_type = ev.get("type", "")
        if ev_type != "conversation_status_updated":
            continue

        data       = ev.get("data", {})
        conv_id    = data.get("conversationId")
        new_status = data.get("statusId", "")

        if new_status not in ("closed", "won"):
            continue

        attrs    = get_drift_playbook_data(conv_id)
        email    = attrs.get("email", "")
        name     = attrs.get("name", "Неизвестный")

        if not email:
            continue

        msg_data   = get_drift_conversation(conv_id)
        messages   = msg_data.get("messages", []) if isinstance(msg_data, dict) else []
        transcript = build_transcript(messages)

        contact_id = find_or_create_hs_contact(email, name)
        deal_id    = create_hs_deal(contact_id, conv_id, attrs)

        if transcript:
            add_note_to_deal(deal_id, transcript, conv_id)

    return jsonify({"status": "ok"}), 200

Настройка Drift Webhooks

  1. Drift Dashboard -> App Settings -> Webhooks -> Add Endpoint
  2. URL: https://your-server.com/webhooks/drift
  3. Events: conversation_status_updated
  4. Скопировать Signing Secret (используется для X-Drift-Signature)

Важно: Drift (теперь в составе Salesloft) - функциональность webhook может отличаться в зависимости от вашего тарифного плана и региона. Проверьте документацию вашей версии Drift/Salesloft Conversations.

Playbook-данные как HubSpot Deal Properties

Создать кастомные properties в HubSpot для хранения Drift Playbook ответов:

  • drift_conversation_id (Text)
  • budget_range (Text или Dropdown)
  • timeline (Text или Dropdown)
  • playbook_outcome (Text): qualified / not-qualified / meeting_booked

Эти поля заполняются автоматически при создании Deal через webhook.

Реальный кейс

SaaS-компания, 300 Drift-разговоров в месяц, 40% квалифицированных. До кастомной интеграции: AE открывал Drift, находил историю разговора, вручную переносил данные в HubSpot Deal (5-7 мин на контакт). После: Deal создаётся автоматически с transcript и Playbook-ответами. AE видит полный контекст в HubSpot без переключения в Drift.

Для кого актуально

B2B SaaS и сервисные компании, использующие Drift для захвата inbound-лидов с сайта и квалификации через чат-бот. Особенно если объём Drift-разговоров >50/месяц и каждый квалифицированный лид обрабатывается AE через HubSpot.

Аналогичный антипаттерн описан для HubSpot + Zoom нативной интеграции и HubSpot + DocuSign.

Часто задаваемые вопросы

Drift переименовался в Salesloft Conversations - изменился ли API?

После приобретения Salesloft в 2023 году Drift rebrand завершился в 2024. API endpoint driftapi.com продолжает работать. Новые клиенты Salesloft могут получить доступ к Conversations через Salesloft API. Проверьте актуальную документацию для вашего аккаунта - endpoint может отличаться.

Как связать Drift meeting (Calendly внутри Drift) с HubSpot Deal?

Drift позволяет встраивать calendars (включая Drift Meetings) в чат. Webhook meeting_booked содержит contact email и meeting details. При этом событии - создать Task в HubSpot Deal (/crm/v3/objects/tasks) с деталями встречи, обновить Deal Stage на appointmentscheduled.

Нативная интеграция создаёт дублирующиеся контакты - как избежать?

В нашей кастомной реализации используется поиск Contact по email (/crm/v3/objects/contacts/search) перед созданием. Если contact существует - используется его ID. Нативная интеграция Drift создаёт контакт без проверки дублей.

Итог

Нативная HubSpot + Drift интеграция создаёт Contact, но не Deal и не привязывает transcript к сделке. Кастомная интеграция:

  • Drift webhook conversation_status_updated (status = closed/won)
  • Drift Conversation API: получить transcript + Playbook-ответы
  • HubSpot: find or create Contact -> create Deal -> add Note с transcript
  • associationTypeId: 3 для Deal -> Contact, 214 для Note -> Deal
  • Кастомные Deal properties: drift_conversation_id, budget_range, timeline

Если нативная Drift интеграция теряет контекст разговоров в HubSpot - обратитесь в Exceltic.dev.

Ещё статьи

Все →