Обсудить задачу

HubSpot + Typeform: ответы в контакт и сделку

Через webhook form_response Typeform отправляет все ответы на форму в реальном времени. Кастомная интеграция парсит массив answers по field.ref, создаёт или обновляет контакт в HubSpot через upsert по email, создаёт связанную сделку с кастомными свойствами и пишет UTM-метки из hidden fields в карточку. Без ручного переноса, без потери данных при сложных типах полей.

В проектах Exceltic.dev мы регулярно видим одну и ту же картину: форма квалификации в Typeform заполнена, менеджер получает уведомление на email, открывает ответы в Typeform, вручную копирует поля в HubSpot - компания, бюджет, источник трафика, тип задачи. Это занимает 5-7 минут на каждый лид. При 30 лидах в неделю теряется полный рабочий день в месяц. Нативная интеграция Typeform с HubSpot закрывает базовый сценарий, но ломается именно там, где форма сложнее «имя + email».

Webhook - это HTTP POST-запрос, который Typeform автоматически отправляет на указанный URL в момент отправки формы. В теле запроса - полный JSON с ответами, метаданными и скрытыми полями.

Данная статья разбирает, что именно ломается в нативной интеграции, как устроена кастомная схема через webhook и какой результат это даёт в типовом проекте.

Что делает нативная интеграция (и где ломается)

Нативная интеграция Typeform с HubSpot работает через официальный коннектор из маркетплейса HubSpot. Она умеет создавать контакты по email, обновлять стандартные свойства и - с ограничениями - передавать данные в сделки и компании.

Ограничения, с которыми сталкиваются клиенты:

  • Только стандартные поля для сложных типов. Данные из не-HubSpot полей формы (то есть из custom вопросов Typeform) маппятся только в single-line text свойства HubSpot. Если нужно заполнить свойство типа dropdown, number или checkbox, нативная интеграция передаёт значение как текст - и HubSpot его отклоняет или игнорирует.

  • Частичные ответы не идут в сделки и компании. Partial responses - заявки, где форма не дошла до финального шага - синхронизируются только в контакты. В сделки и компании они не попадают вообще.

  • Максимум 1000 исторических ответов при первом подключении. Если форма уже использовалась до подключения интеграции, только 1000 последних ответов попадут в HubSpot.

  • Одна форма - один аккаунт HubSpot. Нельзя направлять ответы одновременно в несколько portalId, что блокирует мультиаккаунтные сценарии.

  • Отсутствие дедупликации по кастомному полю. Нативная интеграция ищет контакт только по email. Если email изменился или его нет в форме - создаётся дубль.

Для формы квалификации с 8-10 вопросами - бюджет, тип проекта, размер команды, текущий стек, источник - нативная интеграция переносит в лучшем случае половину данных в нужные поля. Остальное менеджер правит вручную.

Похожая проблема возникает и в других нативных интеграциях HubSpot: HubSpot + Slack: что не умеет нативная интеграция теряет контекст сделки при роутинге уведомлений, а HubSpot + Notion: нативная интеграция не справляется с синхронизацией в реальном времени.

Что реализуется через кастомную интеграцию

Кастомная схема использует Typeform Webhooks API для получения ответов в реальном времени и HubSpot CRM API для записи данных.

Webhook form_response -> парсинг answers

Каждый раз, когда респондент завершает форму, Typeform отправляет POST-запрос на указанный эндпоинт. Тело запроса содержит объект form_response с массивом answers.

Структура каждого элемента в answers:

{
  "type": "text",
  "text": "HubSpot + Typeform",
  "field": {
    "id": "JwWggjAKtOkA",
    "type": "short_text",
    "ref": "project_description"
  }
}

Типы ответов, которые поддерживает Typeform Webhooks API (документация):

  • text - текстовые поля (short_text, long_text)
  • choice - одиночный выбор, содержит choice.label и choice.ref
  • choices - множественный выбор, содержит массив choices.labels
  • number - числовые поля, рейтинги, opinion scale
  • email - поле типа email, значение в answer.email
  • phone_number - телефон, значение в answer.phone_number
  • boolean - yes/no вопросы

Ключевой момент: ответы в массиве расположены не в том порядке, что вопросы в форме. Для надёжного маппинга используется field.ref - это человекочитаемый идентификатор поля, который задаётся в настройках формы в Typeform.

def parse_answers(form_response):
    result = {}
    for answer in form_response.get("answers", []):
        ref = answer["field"]["ref"]
        answer_type = answer["type"]
        if answer_type == "text":
            result[ref] = answer["text"]
        elif answer_type == "choice":
            result[ref] = answer["choice"]["label"]
        elif answer_type == "choices":
            result[ref] = ", ".join(answer["choices"]["labels"])
        elif answer_type == "number":
            result[ref] = answer["number"]
        elif answer_type == "email":
            result[ref] = answer["email"]
        elif answer_type == "phone_number":
            result[ref] = answer["phone_number"]
    return result

Hidden fields для UTM-меток

Hidden fields - это параметры, которые передаются в форму через URL без отображения респонденту. Стандартный сценарий: посетитель приходит с рекламы, URL содержит ?utm_source=google&utm_campaign=q2_leads, эти значения передаются в Typeform через hidden fields и возвращаются в webhook.

В payload они находятся вне массива answers, в отдельном объекте:

"hidden": {
  "utm_source": "google",
  "utm_campaign": "q2_leads",
  "utm_medium": "cpc"
}

Кастомная интеграция записывает эти значения в кастомные свойства контакта и сделки в HubSpot. Нативная интеграция не поддерживает маппинг hidden fields в произвольные свойства.

Upsert контакта в HubSpot по email

HubSpot CRM API предоставляет batch upsert эндпоинт, который с сентября 2024 года поддерживает email как idProperty (документация HubSpot):

POST https://api.hubapi.com/crm/v3/objects/contacts/batch/upsert

Тело запроса:

{
  "inputs": [
    {
      "id": "[email protected]",
      "idProperty": "email",
      "properties": {
        "firstname": "Иван",
        "company": "Acme Corp",
        "budget_range": "50k-100k",
        "hs_lead_source": "google",
        "utm_campaign": "q2_leads"
      }
    }
  ]
}

Логика upsert: если контакт с таким email уже существует - свойства обновляются. Если нет - контакт создаётся. Это исключает дубли при повторных заявках с одного email.

Создание сделки с кастомными полями из формы

После создания или обновления контакта создаётся сделка через POST /crm/v3/objects/deals. Сделка связывается с контактом через associations API. Все релевантные ответы из формы - бюджет, тип проекта, дедлайн, приоритет - записываются в кастомные свойства сделки.

Это ключевое отличие от нативной интеграции: кастомные свойства сделки принимают значения нужного типа - number, dropdown, date - потому что интеграция сама преобразует строку из ответа в правильный формат перед отправкой.

Связанный паттерн используется и для HubSpot + Slack: уведомления о новых сделках - сразу после создания сделки можно отправлять структурированное уведомление в нужный канал.

Пошаговая схема интеграции

  1. Настройка hidden fields в Typeform. В настройках формы добавляются hidden fields для utm_source, utm_campaign, utm_medium, utm_content. На посадочной странице скрипт читает URL-параметры и передаёт их в embed-код формы.

  2. Настройка field.ref для каждого вопроса. В редакторе Typeform каждый вопрос получает уникальный human-readable ref: budget_range, team_size, current_crm, project_type. Это делает маппинг устойчивым к переименованию вопросов.

  3. Деплой webhook-сервера. Небольшой сервис (Python/Node.js) разворачивается на отдельном эндпоинте. Typeform отправляет на него POST-запросы. Сервис проверяет подпись запроса через заголовок Typeform-Signature (HMAC-SHA256) и отклоняет неподписанные запросы.

  4. Регистрация webhook в Typeform. Через Typeform Webhooks API или в интерфейсе: Workspace -> Forms -> Integrations -> Webhooks. Указывается URL эндпоинта и секрет для подписи.

  5. Парсинг payload и маппинг. Сервис читает form_response.answers, итерирует по массиву, извлекает значения по field.ref. Отдельно читает form_response.hidden для UTM-меток.

  6. Upsert контакта в HubSpot. Запрос к POST /crm/v3/objects/contacts/batch/upsert с idProperty: "email". Если контакт существует - обновляются свойства. Если нет - создаётся.

  7. Создание сделки и ассоциация. Через POST /crm/v3/objects/deals создаётся сделка в нужном pipeline и стадии. Через PUT /crm/v3/objects/deals/{dealId}/associations/contacts/{contactId}/deal_to_contact сделка привязывается к контакту.

  8. Запись UTM и кастомных полей в обе карточки. UTM-метки пишутся и в контакт, и в сделку - чтобы источник трафика был виден на обоих объектах в HubSpot.

Схема, которую описывает этот процесс, аналогична интеграции Kommo + Tilda: заявки из форм в воронку без Zapier - те же принципы webhook-приёма и upsert по email, но адаптированные под модель данных HubSpot.

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

B2B-компания в сфере IT-услуг, 35 сотрудников. Форма квалификации в Typeform: 9 вопросов, включая бюджет (number), тип проекта (choice из 6 вариантов), размер команды клиента (number), текущий стек (multiple choice), дедлайн (date). Плюс hidden fields для 4 UTM-параметров.

До интеграции: каждый лид обрабатывался вручную за 6-8 минут. 40 лидов в месяц - около 5 часов операционной работы только на перенос данных. Часть данных терялась при копировании, в HubSpot оставались пустые поля, менеджер не видел UTM-источник прямо в карточке.

После запуска кастомной интеграции:

  • Все 9 полей формы автоматически попадают в свойства контакта и сделки
  • UTM-метки записываются в обе карточки
  • Сделка создаётся в нужном pipeline с первым этапом воронки автоматически
  • Время от заполнения формы до появления сделки в HubSpot - менее 3 секунд
  • Операционное время на обработку лида сократилось с 6-8 минут до 0

Время разработки и деплоя типовой интеграции такого типа - 3-5 рабочих дней.

Аналогичный подход применяется при построении HubSpot + Notion: база клиентов - данные из контакта и сделки можно дублировать в Notion-базу сразу при создании.

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

Кастомная интеграция Typeform + HubSpot оправдана, если:

  • Форма содержит 5+ вопросов, из которых хотя бы 2-3 должны попасть в кастомные свойства HubSpot (не только single-line text)
  • В форму передаются UTM-метки через hidden fields и вы хотите видеть источник трафика в карточке сделки
  • Объём лидов от 20+ в месяц - при меньшем объёме ручной перенос может быть дешевле разработки
  • Нужно автоматически создавать сделку (не только контакт) и привязывать её к правильному pipeline и стадии
  • Требуется дедупликация: повторные заявки с одного email должны обновлять существующий контакт, а не создавать дубль

Если форма собирает только имя и email для базовой подписки - нативная интеграция справится. Для квалификационных форм с бизнес-логикой нативная интеграция создаёт больше операционных проблем, чем решает.

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

Нативная интеграция Typeform с HubSpot не работает с кастомными полями - это правда?

Да, частично. Нативная интеграция ограничена в маппинге сложных типов: данные из кастомных вопросов Typeform попадают в HubSpot только как single-line text. Это означает, что если в HubSpot есть свойство типа number, dropdown или checkbox, нативная интеграция не сможет корректно заполнить его из формы. Кастомная интеграция через webhook решает эту проблему: сервис сам преобразует значение в нужный тип данных перед отправкой в HubSpot API.

Как работает дедупликация контактов при повторных заявках?

HubSpot Contacts upsert API (доступен с сентября 2024) принимает параметр idProperty: "email". Если контакт с таким email уже существует в CRM - API обновляет его свойства. Если нет - создаёт новый контакт. Это стандартная идемпотентная операция: сколько бы раз один email ни заполнил форму, дублей не будет. Для более сложной дедупликации - например, по полю phone - используется аналогичный механизм с другим idProperty или дополнительная проверка перед upsert.

Можно ли передавать в HubSpot ответы из нескольких разных форм Typeform?

Да. Каждая форма регистрирует отдельный webhook, но они могут указывать на один и тот же сервис-обработчик. Сервис идентифицирует форму по form_id в payload и применяет соответствующий маппинг. Это стандартный паттерн при работе с несколькими формами квалификации - например, отдельные формы для разных продуктов или регионов.

Что происходит, если HubSpot API недоступен в момент получения webhook?

Тypeform не повторяет доставку webhook автоматически при ошибке на вашей стороне - если сервер вернул non-200 код, событие считается потерянным. Правильная архитектура: сервис-обработчик немедленно возвращает 200 и ставит задачу в очередь (Redis, RabbitMQ, SQS). Воркер обрабатывает очередь с retry-логикой с exponential backoff. Такой подход гарантирует, что ни один ответ формы не потеряется даже при временных проблемах с HubSpot API.

Сколько времени занимает разработка такой интеграции?

Типовой проект - интеграция одной формы с созданием контакта и сделки, маппинг 8-12 полей, поддержка UTM hidden fields, деплой с retry-очередью - занимает 3-5 рабочих дней. Если нужна более сложная бизнес-логика - например, роутинг в разные pipeline в зависимости от ответа на вопрос о бюджете, или уведомления в Slack при создании сделки - добавляется 1-2 дня.


Если форма квалификации теряет данные при переносе в HubSpot или менеджеры тратят время на ручное копирование ответов - опишите задачу команде Exceltic.dev. Разберём структуру вашей формы, оценим маппинг и предложим схему интеграции под ваш стек.

Ещё статьи

Все →