Нативная интеграция HubSpot + Asana создаёт задачи в Asana из HubSpot, но не показывает выполненные задачи в Deal Activity Timeline. Менеджер по продажам заходит в сделку - и не видит, что ops-команда уже выполнила 3 из 5 onboarding-задач. Вся коммуникация про статус идёт через Slack или email - вместо одного источника правды в CRM.
Правильное решение: Asana webhook при завершении задачи -> ваш сервер -> HubSpot Note с привязкой к Deal через associationTypeId: 214. После этого каждое завершение Asana-задачи мгновенно появляется в Deal Timeline без ручного действия.
HubSpot Deal Activity Timeline - лента активностей внутри сделки: звонки, emails, notes, встречи. Основной инструмент sales-команды для понимания текущего статуса клиента. Если событие не появилось в Timeline - для менеджера оно не произошло.
Почему нативная интеграция не решает задачу
HubSpot и Asana имеют официальную интеграцию. Она позволяет:
- Создавать задачи Asana из HubSpot (при создании Deal, вручную)
- Видеть статус Asana-задачи в виджете на странице Deal
Чего она не делает:
- Не добавляет события завершения задачи в Activity Timeline
- Не создаёт Notes или Activities в HubSpot при изменениях в Asana
- Не позволяет настроить кастомные триггеры (только при создании Deal)
Менеджер может посмотреть виджет - но это не то же самое, что видеть таймлайн с “Задача ‘Настроить интеграцию’ завершена Ивановым 12.06.2026 в 14:30”.
Что теряет бизнес
В типовом B2B SaaS с onboarding-циклом 30 дней: 5-7 задач в Asana создаются при выигрыше сделки. Sales-менеджер должен знать статус клиента перед upsell-звонком. Без видимости Asana-задач в CRM:
- Менеджер звонит клиенту, не зная что онбординг ещё не завершён
- Ops-команда не видит когда sales ставит follow-up (без двусторонней синхронизации)
- Customer Success не знает, какие задачи из onboarding выполнены
При 20+ активных клиентах это становится системной проблемой координации.
Архитектура решения
Asana: задача выполнена (completed = true)
-> Asana webhook -> POST your-server/webhooks/asana
-> Ваш сервер:
-> GET Asana /tasks/{gid}: получить название, проект, исполнитель
-> Найти HubSpot Deal по кастомному полю (asana_project_id или deal_id)
-> POST HubSpot /crm/v3/objects/notes
{properties: {hs_note_body, hs_timestamp},
associations: [{to: {id: dealId}, types: [{associationTypeId: 214}]}]}
Настройка Asana webhook
import requests, os, hashlib, hmac
from flask import Flask, request, jsonify, abort
app = Flask(__name__)
ASANA_PAT = os.environ["ASANA_ACCESS_TOKEN"]
ASANA_BASE = "https://app.asana.com/api/1.0"
ASANA_HDR = {"Authorization": f"Bearer {ASANA_PAT}",
"Content-Type": "application/json"}
HS_TOKEN = os.environ["HUBSPOT_PRIVATE_APP_TOKEN"]
HS_BASE = "https://api.hubapi.com"
HS_HDR = {"Authorization": f"Bearer {HS_TOKEN}",
"Content-Type": "application/json"}
# Хранить: Asana project_gid -> HubSpot deal_id
# В production - PostgreSQL или Redis
project_to_deal: dict = {}
def register_asana_webhook(project_gid: str,
callback_url: str, deal_id: str) -> str:
r = requests.post(
f"{ASANA_BASE}/webhooks",
headers=ASANA_HDR,
json={
"data": {
"resource": project_gid,
"target": callback_url,
"filters": [
{"resource_type": "task", "action": "changed",
"fields": ["completed"]},
{"resource_type": "task", "action": "added"},
],
}
},
)
r.raise_for_status()
webhook_id = r.json()["data"]["gid"]
project_to_deal[project_gid] = deal_id
return webhook_id
@app.route("/webhooks/asana", methods=["POST"])
def asana_webhook():
# Handshake: при первом запросе Asana присылает X-Hook-Secret
secret = request.headers.get("X-Hook-Secret")
if secret:
# Записать секрет для верификации последующих запросов
# В production сохраните в env/DB
return "", 200, {"X-Hook-Secret": secret}
# Верификация HMAC (после первого handshake)
hook_secret = os.environ.get("ASANA_HOOK_SECRET", "")
if hook_secret:
sig = request.headers.get("X-Hook-Signature", "")
body = request.get_data()
expected = hmac.new(
hook_secret.encode(), body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
events = request.json.get("events", [])
for event in events:
if event.get("type") != "task":
continue
if event.get("action") != "changed":
continue
task_gid = event.get("resource", {}).get("gid", "")
project_gid = event.get("parent", {}).get("gid", "")
if not task_gid:
continue
# Получить детали задачи
rt = requests.get(
f"{ASANA_BASE}/tasks/{task_gid}",
headers=ASANA_HDR,
params={"opt_fields": "name,completed,assignee.name,due_on,notes"},
)
task = rt.json().get("data", {})
if not task.get("completed"):
continue # нас интересует только завершение
deal_id = project_to_deal.get(project_gid)
if not deal_id:
continue
task_name = task.get("name", "Task")
assignee = (task.get("assignee") or {}).get("name", "Unassigned")
completed_at = event.get("created_at", "")
note_body = (
f"Asana task completed: {task_name}\n"
f"Assignee: {assignee}\n"
f"Project GID: {project_gid}"
)
create_hubspot_note(deal_id, note_body, completed_at)
return jsonify({"status": "ok"}), 200
def create_hubspot_note(deal_id: str,
note_body: str, timestamp: str) -> str:
from datetime import datetime
ts_ms = int(
datetime.fromisoformat(
timestamp.replace("Z", "+00:00")
).timestamp() * 1000
) if timestamp else None
payload = {
"properties": {
"hs_note_body": note_body,
"hs_timestamp": str(ts_ms) if ts_ms else "",
},
"associations": [
{
"to": {"id": deal_id},
"types": [
{
"associationCategory": "HUBSPOT_DEFINED",
"associationTypeId": 214,
}
],
}
],
}
r = requests.post(
f"{HS_BASE}/crm/v3/objects/notes",
headers=HS_HDR,
json=payload,
)
r.raise_for_status()
return r.json()["id"]
Связь Asana project с HubSpot deal
Mapping project_gid -> deal_id нужно создавать при выигрыше сделки. Обычный flow:
# При HubSpot webhook deal.propertyChange (dealstage = closedwon):
def on_deal_won(deal_id: str):
asana_project_gid = create_asana_project_for_deal(deal_id)
register_asana_webhook(
asana_project_gid,
"https://your-server.com/webhooks/asana",
deal_id,
)
# Сохранить mapping в БД
db.save_mapping(asana_project_gid, deal_id)
Или: если Asana проекты создаются вручную - добавьте кастомное поле “HubSpot Deal ID” в Asana project и читайте его при обработке webhook-событий.
Реальный кейс
SaaS компания, 30 клиентов в онбординге одновременно. Asana - инструмент CS-команды (5-7 задач на клиента: setup, training, first value, NPS). Sales-менеджеры не могли видеть статус онбординга перед renewal-звонками. После интеграции: каждое завершение Asana-задачи появляется в HubSpot Deal Timeline. Менеджер заходит в сделку и видит полную картину без вопросов в Slack. Время подготовки к renewal-звонку сократилось с 15 до 3 минут.
Для кого актуально
B2B компании с разделением ролей sales / CS / ops, которые используют HubSpot как CRM и Asana как инструмент реализации. Особенно актуально для SaaS с онбордингом, консалтинговых компаний и агентств. Если у вас больше 10 активных клиентов в Asana - отсутствие видимости в CRM становится системной проблемой.
Другие HubSpot-антипаттерны: HubSpot + Harvest: time tracking не в Deal (logged hours), HubSpot + Loom: видео не в Deal Timeline.
Часто задаваемые вопросы
Как Asana верифицирует webhook при первом запросе?
Первый запрос от Asana содержит заголовок X-Hook-Secret. Ваш сервер должен вернуть этот же секрет в заголовке ответа X-Hook-Secret. После этого Asana начинает подписывать запросы через HMAC-SHA256 - ваш сервер верифицирует подпись в X-Hook-Signature.
Почему associationTypeId: 214 для Note -> Deal?
214 - стандартный HubSpot ID для связи Note с Deal. Полный список: GET /crm/v4/associations/{fromObjectType}/{toObjectType}/labels с fromObjectType=notes, toObjectType=deals. Для Note -> Contact - 202, Note -> Company - 190.
Можно ли синхронизировать задачи в обратную сторону (HubSpot -> Asana)?
Да: HubSpot webhook deal.propertyChange при изменении нужного поля -> ваш сервер -> POST /tasks в Asana. Это двусторонняя синхронизация, которую нативная интеграция также не поддерживает.
Как найти HubSpot deal_id по email клиента в Asana?
В Asana нет прямой связи с HubSpot. Решение: хранить mapping в PostgreSQL (asana_project_gid -> hubspot_deal_id), который создаётся при создании проекта. Или добавить кастомное поле “HubSpot Deal ID” в Asana Project и читать его при обработке событий через GET /projects/{gid}?opt_fields=custom_fields.
Итог
HubSpot + Asana нативная интеграция - неполная:
- Нативная: создаёт задачи, но не добавляет события в Deal Timeline
- Правильный подход: Asana webhook -> HubSpot Note с
associationTypeId: 214 - Handshake: вернуть
X-Hook-Secretв заголовке ответа - Хранить mapping
project_gid -> deal_idв БД для связи событий - Результат: полная видимость Asana-задач в HubSpot Deal Timeline
Если ваша команда использует HubSpot + Asana и хочет настроить правильную двустороннюю видимость - опишите задачу команде Exceltic.dev.