HubSpot + Webflow: что теряет нативная интеграция форм и как это исправить

HubSpot + Webflow: что теряет нативная интеграция форм и как это исправить

Webflow — популярный no-code конструктор сайтов для маркетинговых команд. HubSpot — CRM и маркетинг-хаб. Нативная интеграция выглядит просто: установить HubSpot Tracking Code на Webflow сайт, подключить Webflow Forms к HubSpot — лиды идут в CRM. Проблема в деталях: нативная интеграция через стандартный HubSpot embed теряет UTM-атрибуцию, не работает с кастомными Webflow формами, не поддерживает прогрессивное профилирование. Разбираем что конкретно ломается и как строить правильно.

Как работает нативная интеграция (и в чём проблема)

Шаг 1: Добавить HubSpot Tracking Code в <head> Webflow. Это устанавливает hsq cookie и начинает трекинг сессий.

Шаг 2: Webflow Forms -> Integrations -> подключить HubSpot. Webflow при отправке формы делает POST в свой бэкенд -> Webflow передаёт данные в HubSpot через встроенную интеграцию.

Что теряется:

1. UTM-параметры и source attribution. Нативная Webflow-HubSpot интеграция не передаёт UTM-параметры в HubSpot Contact Properties. Контакт создаётся, но hs_analytics_source, utm_source, utm_campaign — пустые или «Direct Traffic». Marketing team не знает из какой кампании пришёл лид.

2. Кастомные Webflow-формы. Нативная интеграция работает только со стандартными Webflow Form элементами. Кастомная форма на JS (React, Alpine.js, кастомный fetch) — не подключается через нативную интеграцию.

3. Прогрессивное профилирование. HubSpot Progressive Profiling — заполнение разных полей при повторных визитах. Нативная Webflow-интеграция не поддерживает это: всегда запрашивает одинаковые поля.

4. File upload в формах. Webflow Forms поддерживает file upload. HubSpot нативная интеграция не передаёт файлы — только текстовые поля.

Правильная архитектура: HubSpot Forms API + JS SDK

Вместо нативной интеграции — прямая отправка данных в HubSpot Forms API при submit:

// Правильный подход: кастомный submit handler
// Подключить HubSpot Tracking Code в <head> - обязательно для cookie

const HUBSPOT_PORTAL_ID  = "YOUR_PORTAL_ID";
const HUBSPOT_FORM_GUID  = "YOUR_FORM_GUID";  // из HubSpot -> Marketing -> Forms -> Details

async function submitToHubSpot(formData) {
  // Получить UTM из URL
  const params = new URLSearchParams(window.location.search);
  const utmSource   = params.get("utm_source")   || "";
  const utmMedium   = params.get("utm_medium")   || "";
  const utmCampaign = params.get("utm_campaign") || "";
  const utmContent  = params.get("utm_content")  || "";
  const utmTerm     = params.get("utm_term")     || "";

  // Получить HubSpot cookie для identity resolution
  const getCookie = (name) => {
    const match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)"));
    return match ? match[2] : "";
  };
  const hutk = getCookie("hubspotutk");

  const payload = {
    portalId: HUBSPOT_PORTAL_ID,
    formGuid:  HUBSPOT_FORM_GUID,
    fields: [
      { name: "email",      value: formData.email },
      { name: "firstname",  value: formData.firstName },
      { name: "lastname",   value: formData.lastName },
      { name: "company",    value: formData.company    || "" },
      { name: "phone",      value: formData.phone      || "" },
      // UTM-параметры как HubSpot contact properties
      { name: "utm_source",   value: utmSource   },
      { name: "utm_medium",   value: utmMedium   },
      { name: "utm_campaign", value: utmCampaign },
    ],
    context: {
      hutk:     hutk,       // для session stitching
      pageUri:  window.location.href,
      pageName: document.title,
    },
    legalConsentOptions: {
      // GDPR consent (если нужно)
      consent: {
        consentToProcess: true,
        text: "Я согласен на обработку персональных данных.",
      },
    },
  };

  const resp = await fetch(
    `https://api.hsforms.com/submissions/v3/integration/submit/${HUBSPOT_PORTAL_ID}/${HUBSPOT_FORM_GUID}`,
    {
      method:  "POST",
      headers: { "Content-Type": "application/json" },
      body:    JSON.stringify(payload),
    }
  );

  if (!resp.ok) {
    const err = await resp.json();
    throw new Error(JSON.stringify(err));
  }

  return await resp.json();
}

// Привязать к Webflow form submit
document.querySelector("#contact-form").addEventListener("submit", async (e) => {
  e.preventDefault();
  const fd = new FormData(e.target);

  try {
    await submitToHubSpot({
      email:     fd.get("email"),
      firstName: fd.get("first-name"),
      lastName:  fd.get("last-name"),
      company:   fd.get("company"),
      phone:     fd.get("phone"),
    });
    // Показать success state
    document.querySelector("#form-success").style.display = "block";
    e.target.style.display = "none";
  } catch (err) {
    console.error("HubSpot submit error:", err);
  }
});

UTM persistence: сохранение параметров между страницами

UTM-параметры теряются при навигации если не сохранять. Правильный подход — сохранить в sessionStorage при первом визите:

// Сохранить UTM при загрузке первой страницы
(function saveUtm() {
  const params = new URLSearchParams(window.location.search);
  ["utm_source","utm_medium","utm_campaign","utm_content","utm_term"].forEach(k => {
    if (params.get(k)) {
      sessionStorage.setItem(k, params.get(k));
    }
  });
})();

// При submit - читать из sessionStorage
function getStoredUtm() {
  return {
    utmSource:   sessionStorage.getItem("utm_source")   || "",
    utmMedium:   sessionStorage.getItem("utm_medium")   || "",
    utmCampaign: sessionStorage.getItem("utm_campaign") || "",
  };
}

Server-side вариант: Webflow Webhooks + Python

Для более надёжной архитектуры (особенно если нужна server-side логика):

# Webflow Webhook -> ваш Python endpoint -> HubSpot API
import requests

HUBSPOT_TOKEN = "your_private_app_token"

@app.route("/webhooks/webflow-form", methods=["POST"])
def webflow_form():
    data = request.json
    # data содержит поля формы из Webflow

    # POST в HubSpot Contacts API с upsert по email
    resp = requests.post(
        "https://api.hubapi.com/crm/v3/objects/contacts/upsert",
        headers={"Authorization": f"Bearer {HUBSPOT_TOKEN}"},
        json={
            "properties": {
                "email":        data.get("email"),
                "firstname":    data.get("firstName"),
                "lastname":     data.get("lastName"),
                "utm_source":   data.get("utm_source", ""),
                "utm_campaign": data.get("utm_campaign", ""),
            },
            "idProperty": "email",
        },
    )
    resp.raise_for_status()
    return "", 200

Server-side преимущество: UTM передаются явно в payload, нет зависимости от браузерных cookies.

Сравнение подходов

ПодходUTM-атрибуцияКастомные формыProgressive ProfilingСложность
Нативная интеграцияНетНетНетМинимальная
HubSpot Forms API (JS)ДаДаДа (через HubSpot form settings)Средняя
Server-side webhookДаДаНетВыше

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

SaaS (US, Webflow + HubSpot, 200 лидов/мес через формы):

  • Проблема: 80% контактов в HubSpot — без источника. Marketing team не могла атрибутировать конверсии к кампаниям. $15k/мес на Google Ads — без ROI-данных.
  • Нативная интеграция: передавала имя и email, но не UTM. HubSpot Source = «Direct Traffic» для всех форм.
  • После Forms API с UTM persistence: каждый лид с utm_source, utm_campaign. ROI по Google Ads кампаниям виден напрямую в HubSpot Reports. Выяснилось: Brand кампания (30% бюджета) давала 3x лучший close rate чем Non-Brand.

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

  • Команды с Webflow + HubSpot где маркетинг жалуется на «Direct Traffic» в источниках
  • SaaS с кастомными Webflow-формами (React, Alpine.js) которые не работают с нативной интеграцией
  • Компании с EU-аудиторией где GDPR consent в форме критичен — нативная интеграция не передаёт consent данные корректно
  • Маркетинг-команды с paid ads которым нужна атрибуция до сделки

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

HubSpot Tracking Code — его всё равно нужно устанавливать?

Да, даже при прямом Forms API. HubSpot Tracking Code создаёт hubspotutk cookie для session stitching — связывает анонимную сессию с контактом. Без него context.hutk будет пустым и HubSpot не сможет связать просмотры страниц с отправкой формы.

HubSpot Forms API требует Private App token или Portal ID достаточно?

Forms API v3 (/submissions/v3/integration/submit/) не требует авторизации — только Portal ID и Form GUID. Это публичный endpoint (форма заполняется клиентом в браузере). Private App token нужен для Contacts API (server-side).

Webflow Logics vs кастомный JS — что лучше?

Webflow Logics (no-code автоматизации) — новый продукт Webflow. Имеет HubSpot-коннектор, но с теми же ограничениями что нативная интеграция: нет UTM, ограниченные field mapping. Для полного контроля — кастомный JS или server-side webhook.

Можно ли передать File upload из Webflow в HubSpot?

HubSpot Forms API не поддерживает file upload напрямую. Workaround: загрузить файл в HubSpot Files API (POST /filemanager/api/v3/files/upload), получить URL -> передать URL как text-поле в форму. Требует server-side прокси.

Итого

  • Нативная интеграция Webflow + HubSpot: работает только для базовых форм без UTM и кастомной логики
  • Правильный путь: HubSpot Forms API v3 + UTM persistence в sessionStorage
  • hubspotutk cookie обязательна для session stitching — устанавливать HubSpot Tracking Code
  • Server-side вариант (Webflow Webhook -> Python -> HubSpot Contacts API) — для надёжности и сложной бизнес-логики
  • UTM-атрибуция до закрытой сделки — без правильной интеграции невозможна

Если у вас HubSpot + Webflow и лиды приходят без источника — опишите структуру форм и текущую интеграцию. Exceltic.dev настроит Forms API с полной UTM-атрибуцией.

Ещё статьи

Все →