Kommo + Teamwork: создание проектов для агентств из выигранных сделок

Kommo + Teamwork: создание проектов для агентств из выигранных сделок

Teamwork — PM-платформа, созданная специально для агентств и консалтинга: проекты, задачи, milestones, time tracking с биллингом клиентам, ресурсное планирование, client portal. В отличие от Asana или Monday.com, Teamwork имеет встроенный тайм-трекинг с hourly billing — критично для агентств работающих по Time & Materials модели. Без интеграции с Kommo создание проекта при Won — ручной процесс. С интеграцией Won -> проект из шаблона с задачами за секунды.

Teamwork vs Asana vs Basecamp для agency delivery

ПараметрTeamworkAsanaBasecamp
Time tracking + billingНативный, billable/non-billableЧерез интеграциюНет
Client portalДаНетОграниченно
Шаблоны проектовДаДаДа
Resource planningДаДа (Business+)Нет
РетроспективыЧерез ReportsНетНет
Подходит дляАгентства T&M, консалтингВсе типыПростые проекты

Teamwork выбирают агентства где каждый час — биллируемый, и клиент видит прогресс через client portal.

Что синхронизируется

Kommo -> Teamwork:
— Won -> создать Project из шаблона с данными клиента
— Won -> добавить Description с данными сделки (тариф, сумма, менеджер)
— Won -> добавить клиента как Company в Teamwork
— Won -> назначить задачи на команду через People маппинг

Teamwork -> Kommo:
task.completed milestone-задачи -> Note в сделку
project.completed -> Note + смена этапа «Проект завершён»
— Logged time достигает бюджета -> Note: «80% бюджета использовано»

Teamwork API: ключевые запросы

Base URL: https://{yourdomain}.teamwork.com/projects/api/v3.
Аутентификация: Basic Auth — API key как username, любой пароль.
API Key: Teamwork -> Profile Settings -> API Keys.

import requests
from requests.auth import HTTPBasicAuth

TW_API_KEY   = "your_api_key"
TW_DOMAIN    = "yourcompany"  # yourcompany.teamwork.com
TW_BASE_URL  = f"https://{TW_DOMAIN}.teamwork.com/projects/api/v3"
TW_AUTH      = HTTPBasicAuth(TW_API_KEY, "x")  # пароль любой

def get_project_templates() -> list:
    resp = requests.get(
        f"{TW_BASE_URL}/projecttemplates.json",
        auth=TW_AUTH,
    )
    resp.raise_for_status()
    return resp.json().get("projecttemplates", [])

def create_project_from_template(template_id: int, name: str,
                                  description: str = "") -> dict:
    payload = {
        "project": {
            "name": name,
            "description": description,
        }
    }
    resp = requests.post(
        f"{TW_BASE_URL}/projecttemplates/{template_id}/projects.json",
        auth=TW_AUTH,
        json=payload,
    )
    resp.raise_for_status()
    return resp.json().get("project", {})

def create_project(name: str, description: str = "") -> dict:
    payload = {
        "project": {
            "name": name,
            "description": description,
        }
    }
    resp = requests.post(
        f"{TW_BASE_URL}/projects.json",
        auth=TW_AUTH,
        json=payload,
    )
    resp.raise_for_status()
    return resp.json().get("project", {})

def get_tasklists(project_id: int) -> list:
    resp = requests.get(
        f"{TW_BASE_URL}/projects/{project_id}/tasklists.json",
        auth=TW_AUTH,
    )
    resp.raise_for_status()
    return resp.json().get("tasklists", [])

def create_tasklist(project_id: int, name: str) -> dict:
    payload = {"tasklist": {"name": name}}
    resp = requests.post(
        f"{TW_BASE_URL}/projects/{project_id}/tasklists.json",
        auth=TW_AUTH,
        json=payload,
    )
    resp.raise_for_status()
    return resp.json().get("tasklist", {})

def create_task(project_id: int, tasklist_id: int, content: str,
                assignee_id: int = None, due_date: str = None) -> dict:
    # due_date: "YYYYMMDD"
    payload: dict = {
        "todo-item": {
            "content": content,
        }
    }
    if assignee_id:
        payload["todo-item"]["responsible-party-id"] = str(assignee_id)
    if due_date:
        payload["todo-item"]["due-date"] = due_date

    resp = requests.post(
        f"{TW_BASE_URL}/tasklists/{tasklist_id}/tasks.json",
        auth=TW_AUTH,
        json=payload,
    )
    resp.raise_for_status()
    return resp.json().get("todo-item", {})

ONBOARDING_TASKS = [
    "Kick-off звонок с командой клиента",
    "Сбор требований и бриф",
    "Согласование roadmap",
    "Первый delivery review",
    "Финальная приёмка и закрытие",
]

MANAGER_TO_TW = {
    "alice@company.com": 100001,
    "bob@company.com":   100002,
}

def on_deal_won(lead: dict, contact: dict):
    client_name = contact["name"]
    plan        = get_custom_field(lead, PLAN_FIELD_ID) or "Growth"
    amount      = lead.get("price", 0)
    manager_email = get_manager_email(lead)

    description = (
        f"Клиент: {client_name}\n"
        f"Тариф: {plan}\n"
        f"Сумма: ${amount}\n"
        f"Kommo deal: {lead['id']}"
    )
    project_name = f"{client_name} - {plan}"

    if TW_TEMPLATE_ID:
        project = create_project_from_template(TW_TEMPLATE_ID, project_name, description)
    else:
        project = create_project(project_name, description)

    project_id = project["id"]
    tasklists  = get_tasklists(project_id)

    if not tasklists:
        tl = create_tasklist(project_id, "Онбординг")
        tasklist_id = tl["id"]
    else:
        tasklist_id = tasklists[0]["id"]

    assignee_id = MANAGER_TO_TW.get(manager_email)
    for task_name in ONBOARDING_TASKS:
        create_task(project_id, tasklist_id, task_name, assignee_id)

    update_kommo_deal(lead["id"], {"teamwork_project_id": str(project_id)})
    create_kommo_note(lead["id"],
        f"Teamwork: проект «{project_name}» создан (ID: {project_id})")

Webhook Teamwork:

@app.route("/webhooks/teamwork", methods=["POST"])
def teamwork_webhook():
    payload   = request.json
    event     = payload.get("eventName")
    project   = payload.get("project", {})
    project_id = str(project.get("id", ""))

    deal_id = find_deal_by_field("teamwork_project_id", project_id)
    if not deal_id:
        return "", 200

    if event == "TASK.COMPLETED":
        task_name = payload.get("task", {}).get("name", "")
        create_kommo_note(deal_id,
            f"Teamwork: задача «{task_name}» выполнена")

    elif event == "PROJECT.COMPLETED":
        update_kommo_deal(deal_id, {"stage_id": STAGE_PROJECT_DONE})
        create_kommo_note(deal_id, "Teamwork: проект завершён")

    return "", 200

Time Tracking: биллинговый цикл через Kommo

Для агентств на T&M-модели Teamwork даёт возможность видеть сколько часов потрачено на клиента напрямую из его сделки в Kommo. Polling API GET /projects/{id}/time.json каждый день:

  • Если billable_hours >= budget_hours × 0.8 -> Note в Kommo: «80% бюджета проекта использовано»
  • Менеджер видит это в карточке и инициирует разговор о расширении scope

Это не автоматизируется нативно — нужна кастомная логика, но polling Teamwork Time API + Kommo Note — стандартная задача для такой интеграции.

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

Digital-агентство (Ирландия, Teamwork + Kommo, 15–20 проектов/мес):

  • До: project manager создавал Teamwork-проект вручную при каждом Won. 25–30 минут на стандартный онбординг. Иногда забывал назначить задачи на конкретных людей -> задачи зависали без ответственного.
  • После: Won -> проект из шаблона с 5 задачами за 20 секунд. Ответственные назначены автоматически по маппингу менеджер -> Teamwork ID. PM получает уведомление от Teamwork, не создаёт проект сам.
  • Дополнительно: PROJECT.COMPLETED -> смена этапа в Kommo -> триггер NPS-опроса через GetResponse.

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

  • Агентства и консалтинг с Teamwork как основным PM-инструментом
  • Компании на Time & Materials с биллингом часов клиентам
  • Команды 5–30 человек с 10–30 активными клиентами параллельно
  • Агентства где client portal важен — клиент видит прогресс в Teamwork

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

Teamwork API v1 vs v3 — какую использовать?

v3 — актуальная версия с REST-архитектурой. v1 (устаревшая) использует другую нотацию и не развивается. Для новых интеграций: всегда v3 на /{domain}.teamwork.com/projects/api/v3.

Teamwork шаблоны проектов — как создать?

Teamwork -> More -> Templates -> New Template. Добавьте tasklists и задачи которые нужны при каждом новом проекте. Template ID — из URL при редактировании: /templates/{id}/edit. Через API: GET /projecttemplates.json.

Как Teamwork авторизует webhook?

Teamwork подписывает webhook через HMAC-SHA256. В настройках webhook — поле Webhook Secret. Верификация: hmac.new(secret, payload, sha256).hexdigest() сравнить с заголовком X-Webhook-Signature.

Teamwork Client Portal — как настроить доступ для клиента?

Teamwork -> проект -> People -> Add Client. Клиент получает email-приглашение. В portal клиент видит только разрешённые tasklists и задачи (не всё). Для Kommo-интеграции: при создании проекта через API можно сразу добавить клиентский email через POST /projects/{id}/people.json.

Итого

  • Teamwork API: Basic Auth (api_key + «x»), /{domain}.teamwork.com/projects/api/v3
  • Создать из шаблона: POST /projecttemplates/{id}/projects.json
  • Задачи: POST /tasklists/{id}/tasks.json с responsible-party-id
  • Webhook: TASK.COMPLETED, PROJECT.COMPLETED, HMAC-SHA256
  • Time tracking: polling GET /projects/{id}/time.json -> Note при приближении к бюджету

Если вы используете Teamwork и Kommo и хотите автоматизировать создание проектов при Won — опишите структуру шаблонов и маппинг менеджеров. Exceltic.dev настроит интеграцию.

Ещё статьи

Все →