Kommo + Basecamp: creating projects and tasks from won deals
Basecamp is a project management tool popular among agencies, design studios, and digital consulting firms: To-do lists, Message Boards, Schedules, Docs & Files, Campfire (chat) — all in one project. Simpler than Jira and Asana, without Gantt charts and task dependencies — and that is precisely what appeals to teams that need order, not features for the sake of features. Without the Kommo integration, a manager creates a project manually on every Won. With the integration, Won -> project with tasks in seconds, without manual effort.
Basecamp vs Asana vs Trello for delivery integration
| Parameter | Basecamp | Asana | Trello |
|---|---|---|---|
| Philosophy | Simplicity + communication | Powerful workflows | Kanban |
| Project templates | Yes (Project Templates) | Yes | No (via Power-Up) |
| Webhook | Yes | Yes | Yes |
| API | REST v2 (OAuth 2.0) | REST | REST + API Key |
| Best for | Agencies, consulting | SaaS, IT | Small teams |
Basecamp is chosen by agencies with long-term clients: one Basecamp project = one client, with the full history of communications, tasks, and documents.
What gets synchronized
Kommo -> Basecamp: — Won -> create Project with client name — Won -> create To-do list with onboarding template tasks — Won -> add Description with deal data (plan, amount, manager) — Won -> assign responsible parties to tasks (manager -> Basecamp user mapping)
Basecamp -> Kommo: — To-do item completed -> Note: “Basecamp: task ‘{title}’ completed” — Project archived (via polling) -> Note: “Project completed”
Basecamp API: key requests
Base URL: https://3.basecampapi.com/{account_id}.
Account ID: from the Basecamp Dashboard URL (app.basecamp.com/{account_id}/...).
Authentication: OAuth 2.0 (recommended) or Personal Access Token.
User-Agent is required: Basecamp blocks requests without a valid UA.
import requests
BASECAMP_TOKEN = "your_personal_access_token" # or 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)", # required!
}
def get_project_templates() -> list:
# Get list of project templates
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:
# Create a project from a template
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:
# Create a project from scratch (without a template)
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:
# Get the todo-list container for a project
resp = requests.get(
f"{BASECAMP_BASE_URL}/buckets/{project_id}/todosets.json",
headers=HEADERS
)
resp.raise_for_status()
return resp.json()[0] # one todoset per project
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 = [
"Kick-off call with client team",
"Share access and materials",
"Agree on project roadmap",
"Kick-off meeting",
"First 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: {client_name}\n"
f"Plan: {plan}\n"
f"Amount: {amount}\n"
f"Kommo deal ID: {lead['id']}"
)
# Create project (from template or from scratch)
if BASECAMP_TEMPLATE_ID:
construction = create_project_from_template(
template_id=BASECAMP_TEMPLATE_ID,
name=f"Project: {client_name}",
description=description,
)
# project_construction: status pending, poll until completed
project_id = poll_construction_until_done(construction["id"])
else:
project = create_project(
name=f"Project: {client_name}",
description=description,
)
project_id = project["id"]
# Create To-do list manually
todoset = get_todoset(project_id)
todoset_id = todoset["id"]
todolist = create_todolist(
project_id, todoset_id,
name="Onboarding",
description="Project launch tasks",
)
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: project 'Project: {client_name}' created (ID: {project_id})")
Handling Basecamp Webhooks:
@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: task '{title}' completed ({creator})")
elif kind == "message_created":
# Message on Message Board -> Note in deal (optional)
subject = recording.get("subject", "")
create_kommo_note(deal_id,
f"Basecamp: new message in project - '{subject}'")
return "", 200
Registering a Webhook in Basecamp via 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 for a new project:
# register_webhook(project_id, "https://yourapp.com/webhooks/basecamp",
# ["Todo", "Message"])
Project Templates: creating from a template
Basecamp supports Project Templates — a pre-populated project with To-do lists, documents, and a schedule. POST /templates/{id}/project_constructions.json creates the project asynchronously: you need to poll GET /project_constructions/{id}.json until status != "completed".
Templates: Basecamp -> Templates (left menu) -> create new. Template ID is in the template URL.
Real case
Design agency (EU, 8–12 new projects per month, Kommo + Basecamp):
- Before: The PM manually created a project in Basecamp, added a standard To-do list, and invited the client. It took 15–20 minutes per Won. Sometimes the Basecamp project was forgotten — the client waited for access.
- After: Won -> project from template in 20 seconds. All standard tasks, schedule, and client access — from the Template. The PM receives a notification that the project was created, rather than creating it themselves.
- Additionally:
todo_completedfor the “Acceptance and signing” task -> Note in Kommo + stage change to “Project completed” -> trigger NPS survey.
Who should use this
- Agencies and consulting firms with Basecamp as their primary PM tool
- Teams with 5–20 parallel projects — at lower volumes manual work is still manageable
- Design studios and digital agencies: client-oriented projects, not development
- Companies where the client is a Basecamp project participant (client access features)
Frequently asked questions
Basecamp Personal Access Token vs OAuth — which to choose?
Personal Access Token (PAT) is simpler for server-side integration: it does not expire and does not require an OAuth flow. Create it: Basecamp -> My Profile -> Access Tokens. OAuth is only needed for multi-user applications (SaaS integration for different Basecamp accounts).
Basecamp project_construction — why is the status pending?
Creating a project from a template is an asynchronous process. Basecamp copies all template structures (todos, schedules, docs). You need to poll GET /project_constructions/{id}.json every 2–3 seconds until status == "completed". This typically takes 5–15 seconds.
How do you invite a client to a Basecamp project via API?
POST /projects/{id}/people/users.json with an array {"grant": [{"id": person_id}]}. If the client is not in Basecamp — create them first via POST /people.json. For client-access (Basecamp Clientside): there is a separate endpoint for external users.
Basecamp webhook — how to verify the request?
Basecamp signs webhooks via X-Basecamp-Signature: HMAC-SHA256 with the secret specified at registration. Verification is analogous to other platforms: hmac.new(secret, body, sha256).hexdigest().
Summary
- Basecamp API:
Bearer {token}+ requiredUser-Agent - Create from template:
POST /templates/{id}/project_constructions.json-> async, polling required - Create todo:
POST /buckets/{project_id}/todolists/{todolist_id}/todos.json - Webhook: HMAC-SHA256 via
X-Basecamp-Signature, registered via API - Project Templates: more powerful than manual creation — everything in one API call
If you use Basecamp and Kommo and want to automate project creation on Won — describe the structure of your template. Exceltic.dev will set up the integration with template_construction and webhook handling.