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

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

Basecamp — инструмент управления проектами, популярный среди агентств, дизайн-студий и digital-консалтинга: To-do lists, Message Boards, Schedules, Docs & Files, Campfire (чат) в одном проекте. Проще чем Jira и Asana, без Gantt и зависимостей задач — но именно это привлекает команды, которым нужен порядок, а не функциональность ради функциональности. Без интеграции с Kommo менеджер создаёт проект вручную при каждом Won. С интеграцией Won -> проект с задачами за секунды, без ручной работы.

Basecamp vs Asana vs Trello для delivery-интеграции

ПараметрBasecampAsanaTrello
ФилософияПростота + коммуникацияМощные workflowКанбан
Шаблоны проектовДа (Project Templates)ДаНет (через Power-Up)
WebhookДаДаДа
APIREST v2 (OAuth 2.0)RESTREST + API Key
Подходит дляАгентства, консалтингSaaS, ITМалые команды

Basecamp выбирают агентства с постоянными клиентами: один Basecamp-проект = один клиент, со всей историей коммуникаций, задач и документов.

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

Kommo -> Basecamp:
— Won -> создать Project с именем клиента
— Won -> создать To-do list с задачами onboarding-шаблона
— Won -> добавить Description с данными сделки (тариф, сумма, менеджер)
— Won -> назначить ответственных на задачи (маппинг менеджер -> Basecamp-пользователь)

Basecamp -> Kommo:
— To-do item completed -> Note: «Basecamp: задача «{title}» выполнена»
— Project archived (через polling) -> Note: «Проект завершён»

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

Base URL: https://3.basecampapi.com/{account_id}.
Account ID: из URL Basecamp Dashboard (app.basecamp.com/{account_id}/...).
Аутентификация: OAuth 2.0 (рекомендуется) или Personal Access Token.
User-Agent обязателен: Basecamp блокирует запросы без корректного UA.

import requests

BASECAMP_TOKEN = "your_personal_access_token"  # или OAuth access token
BASECAMP_ACCOUNT_ID = "your_account_id"
BASECAMP_BASE_URL = f"https://3.basecampapi.com/{BASECAMP_ACCOUNT_ID}"

HEADERS = {
    "Authorization": f"Bearer {BASECAMP_TOKEN}",
    "Content-Type": "application/json",
    "User-Agent": "YourApp (yourname@company.com)",  # обязателен!
}

def get_project_templates() -> list:
    # Получить список шаблонов проектов
    resp = requests.get(
        f"{BASECAMP_BASE_URL}/templates.json",
        headers=HEADERS
    )
    resp.raise_for_status()
    return resp.json()

def create_project_from_template(template_id: int, name: str,
                                  description: str = "") -> dict:
    # Создать проект из шаблона
    resp = requests.post(
        f"{BASECAMP_BASE_URL}/templates/{template_id}/project_constructions.json",
        headers=HEADERS,
        json={"project": {"name": name, "description": description}}
    )
    resp.raise_for_status()
    return resp.json()

def create_project(name: str, description: str = "") -> dict:
    # Создать проект с нуля (без шаблона)
    resp = requests.post(
        f"{BASECAMP_BASE_URL}/projects.json",
        headers=HEADERS,
        json={"name": name, "description": description}
    )
    resp.raise_for_status()
    return resp.json()

def get_todoset(project_id: int) -> dict:
    # Получить todo-list контейнер проекта
    resp = requests.get(
        f"{BASECAMP_BASE_URL}/buckets/{project_id}/todosets.json",
        headers=HEADERS
    )
    resp.raise_for_status()
    return resp.json()[0]  # один todoset на проект

def create_todolist(project_id: int, todoset_id: int, name: str,
                    description: str = "") -> dict:
    resp = requests.post(
        f"{BASECAMP_BASE_URL}/buckets/{project_id}/todosets/{todoset_id}/todolists.json",
        headers=HEADERS,
        json={"name": name, "description": description}
    )
    resp.raise_for_status()
    return resp.json()

def create_todo(project_id: int, todolist_id: int, content: str,
                assignee_ids: list = None, due_on: str = None) -> dict:
    payload: dict = {"content": content}
    if assignee_ids:
        payload["assignee_ids"] = assignee_ids
    if due_on:
        payload["due_on"] = due_on  # "2026-06-15"
    resp = requests.post(
        f"{BASECAMP_BASE_URL}/buckets/{project_id}/todolists/{todolist_id}/todos.json",
        headers=HEADERS,
        json=payload
    )
    resp.raise_for_status()
    return resp.json()

ONBOARDING_TODOS = [
    "Вводный звонок с командой клиента",
    "Передать доступы и материалы",
    "Согласовать roadmap проекта",
    "Kick-off встреча",
    "Первый milestone review",
]

MANAGER_TO_BASECAMP = {
    "alice@company.com": 1234567,  # Basecamp person ID
    "bob@company.com":   7654321,
}

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 ID: {lead['id']}"
    )

    # Создать проект (из шаблона или с нуля)
    if BASECAMP_TEMPLATE_ID:
        construction = create_project_from_template(
            template_id=BASECAMP_TEMPLATE_ID,
            name=f"Проект: {client_name}",
            description=description,
        )
        # project_construction: статус pending, poll пока не completed
        project_id = poll_construction_until_done(construction["id"])
    else:
        project = create_project(
            name=f"Проект: {client_name}",
            description=description,
        )
        project_id = project["id"]

        # Создать To-do list вручную
        todoset = get_todoset(project_id)
        todoset_id = todoset["id"]
        todolist = create_todolist(
            project_id, todoset_id,
            name="Онбординг",
            description="Задачи запуска проекта",
        )
        assignee_id = MANAGER_TO_BASECAMP.get(manager_email)
        for task in ONBOARDING_TODOS:
            create_todo(
                project_id, todolist["id"], task,
                assignee_ids=[assignee_id] if assignee_id else None,
            )

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

Обработка Basecamp Webhook:

@app.route("/webhooks/basecamp", methods=["POST"])
def basecamp_webhook():
    payload = request.json
    kind = payload.get("kind")          # "todo_completed", "todo_uncompleted" etc.
    recording = payload.get("recording", {})
    bucket = payload.get("bucket", {})

    project_id = str(bucket.get("id", ""))
    deal_id = find_deal_by_field("basecamp_project_id", project_id)
    if not deal_id:
        return "", 200

    if kind == "todo_completed":
        title = recording.get("title", "")
        creator = payload.get("creator", {}).get("name", "")
        create_kommo_note(deal_id,
            f"Basecamp: задача «{title}» выполнена ({creator})")

    elif kind == "message_created":
        # Сообщение на Message Board -> Note в сделку (по желанию)
        subject = recording.get("subject", "")
        create_kommo_note(deal_id,
            f"Basecamp: новое сообщение в проекте - «{subject}»")

    return "", 200

Регистрация Webhook в Basecamp через API:

def register_webhook(project_id: int, payload_url: str, types: list) -> dict:
    resp = requests.post(
        f"{BASECAMP_BASE_URL}/buckets/{project_id}/webhooks.json",
        headers=HEADERS,
        json={"payload_url": payload_url, "types": types}
    )
    resp.raise_for_status()
    return resp.json()

# Зарегистрировать для нового проекта:
# register_webhook(project_id, "https://yourapp.com/webhooks/basecamp",
#                  ["Todo", "Message"])

Project Templates: создание из шаблона

Basecamp поддерживает Project Templates — предзаполненный проект с To-do lists, документами, расписанием. POST /templates/{id}/project_constructions.json создаёт проект асинхронно: нужно polling GET /project_constructions/{id}.json пока status != "completed".

Шаблоны: Basecamp -> Templates (в левом меню) -> создать новый. Template ID — из URL шаблона.

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

Дизайн-агентство (EU, 8–12 новых проектов в месяц, Kommo + Basecamp):

  • До: PM вручную создавал проект в Basecamp, добавлял стандартный To-do list, приглашал клиента. Занимало 15–20 минут при каждом Won. Иногда забывал создать Basecamp-проект — клиент ждал доступ.
  • После: Won -> проект из шаблона за 20 секунд. Все стандартные задачи, расписание, доступ клиента — из Template. PM получает уведомление что проект создан, а не создаёт его сам.
  • Дополнительно: todo_completed для задачи «Приёмка и подписание акта» -> Note в Kommo + смена этапа «Проект завершён» -> триггер NPS-опроса.

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

  • Агентства и консалтинг с Basecamp как основным PM-инструментом
  • Команды с 5–20 параллельными проектами — при меньшем объёме ручная работа терпима
  • Дизайн-студии и digital-агентства: клиент-ориентированные проекты, не разработка
  • Компании где клиент — участник Basecamp-проекта (client access features)

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

Basecamp Personal Access Token vs OAuth — что выбрать?

Personal Access Token (PAT) — проще для серверной интеграции: не истекает, не требует OAuth flow. Создать: Basecamp -> My Profile -> Access Tokens. OAuth нужен только для мультипользовательских приложений (SaaS-интеграция для разных Basecamp-аккаунтов).

Basecamp project_construction — почему статус pending?

Создание проекта из шаблона — асинхронный процесс. Basecamp копирует все структуры шаблона (todos, schedules, docs). Нужно poll GET /project_constructions/{id}.json каждые 2–3 секунды пока status == "completed". Обычно занимает 5–15 секунд.

Как пригласить клиента в Basecamp-проект через API?

POST /projects/{id}/people/users.json с массивом {"grant": [{"id": person_id}]}. Если клиента нет в Basecamp — сначала создать через POST /people.json. Для client-access (Basecamp Clientside): отдельный endpoint для внешних пользователей.

Basecamp webhook — как верифицировать запрос?

Basecamp подписывает webhook через X-Basecamp-Signature: HMAC-SHA256 с секретом, указанным при регистрации. Верификация аналогична другим платформам: hmac.new(secret, body, sha256).hexdigest().

Итого

  • Basecamp API: Bearer {token} + обязательный User-Agent
  • Создать из шаблона: POST /templates/{id}/project_constructions.json -> async, нужен polling
  • Создать todo: POST /buckets/{project_id}/todolists/{todolist_id}/todos.json
  • Webhook: HMAC-SHA256 через X-Basecamp-Signature, регистрация через API
  • Project Templates: мощнее ручного создания — всё за один API-вызов

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

Ещё статьи

Все →