Teamwork is a PM platform built specifically for agencies and consulting: projects, tasks, milestones, time tracking with client billing, resource planning, and a client portal. Unlike Asana or Monday.com, Teamwork has built-in time tracking with hourly billing — critical for agencies working on a Time & Materials model. Without a Kommo integration, creating a project on Won is a manual process. With the integration, Won -> project from a template with tasks in seconds.
Teamwork vs Asana vs Basecamp for Agency Delivery
| Parameter | Teamwork | Asana | Basecamp |
|---|---|---|---|
| Time tracking + billing | Native, billable/non-billable | Via integration | None |
| Client portal | Yes | No | Limited |
| Project templates | Yes | Yes | Yes |
| Resource planning | Yes | Yes (Business+) | No |
| Retrospectives | Via Reports | No | No |
| Best for | T&M agencies, consulting | All types | Simple projects |
Teamwork is chosen by agencies where every hour is billable and the client tracks progress through the client portal.
What Gets Synchronized
Kommo -> Teamwork:
— Won -> create a Project from a template with client data
— Won -> add a Description with deal data (plan, amount, manager)
— Won -> add the client as a Company in Teamwork
— Won -> assign tasks to the team via People mapping
Teamwork -> Kommo:
— task.completed for a milestone task -> Note in the deal
— project.completed -> Note + stage change to “Project Completed”
— Logged time reaches the budget -> Note: “80% of budget used”
Teamwork API: Key Requests
Base URL: https://{yourdomain}.teamwork.com/projects/api/v3.
Authentication: Basic Auth — API key as username, any password.
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") # any password
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 call with the client team",
"Requirements gathering and brief",
"Roadmap sign-off",
"First delivery review",
"Final acceptance and closure",
]
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: {client_name}\n"
f"Plan: {plan}\n"
f"Amount: ${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, "Onboarding")
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 '{project_name}' created (ID: {project_id})")
Teamwork Webhook:
@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 '{task_name}' completed")
elif event == "PROJECT.COMPLETED":
update_kommo_deal(deal_id, {"stage_id": STAGE_PROJECT_DONE})
create_kommo_note(deal_id, "Teamwork: project completed")
return "", 200
Time Tracking: Billing Cycle via Kommo
For agencies on a T&M model, Teamwork makes it possible to see how many hours have been spent on a client directly from their deal card in Kommo. Polling the API GET /projects/{id}/time.json daily:
- If billable_hours >= budget_hours × 0.8 -> Note in Kommo: “80% of project budget used”
- The manager sees this in the card and initiates a conversation about scope expansion
This is not automated natively — custom logic is required, but polling the Teamwork Time API + Kommo Note is a standard task for such an integration.
Real-World Case
Digital agency (Ireland, Teamwork + Kommo, 15–20 projects/month):
- Before: the project manager created a Teamwork project manually for every Won. 25–30 minutes for standard onboarding. Sometimes forgot to assign tasks to specific people -> tasks were left without an owner.
- After: Won -> project from template with 5 tasks in 20 seconds. Owners assigned automatically via manager -> Teamwork ID mapping. The PM receives a Teamwork notification rather than creating the project themselves.
- Additionally:
PROJECT.COMPLETED-> stage change in Kommo -> trigger for an NPS survey via GetResponse.
Who This Is Relevant For
- Agencies and consulting firms using Teamwork as their primary PM tool
- Companies on Time & Materials with hourly client billing
- Teams of 5–30 people with 10–30 active clients in parallel
- Agencies where the client portal is important — the client tracks progress in Teamwork
Frequently Asked Questions
Teamwork API v1 vs v3 — which to use?
v3 — the current version with REST architecture. v1 (deprecated) uses a different notation and is no longer developed. For new integrations: always use v3 at /{domain}.teamwork.com/projects/api/v3.
How to create Teamwork project templates?
Teamwork -> More -> Templates -> New Template. Add the tasklists and tasks needed for every new project. Template ID — from the URL when editing: /templates/{id}/edit. Via API: GET /projecttemplates.json.
How does Teamwork authenticate webhooks?
Teamwork signs webhooks via HMAC-SHA256. A Webhook Secret field is available in the webhook settings. Verification: hmac.new(secret, payload, sha256).hexdigest() compared against the X-Webhook-Signature header.
How to configure Teamwork Client Portal access for a client?
Teamwork -> project -> People -> Add Client. The client receives an email invitation. In the portal, the client only sees the permitted tasklists and tasks (not everything). For the Kommo integration: when creating a project via the API, the client email can be added immediately via POST /projects/{id}/people.json.
Summary
- Teamwork API: Basic Auth (api_key + “x”),
/{domain}.teamwork.com/projects/api/v3 - Create from template:
POST /projecttemplates/{id}/projects.json - Tasks:
POST /tasklists/{id}/tasks.jsonwith responsible-party-id - Webhook:
TASK.COMPLETED,PROJECT.COMPLETED, HMAC-SHA256 - Time tracking: polling
GET /projects/{id}/time.json-> Note when approaching the budget
If you use Teamwork and Kommo and want to automate project creation on Won — describe your template structure and manager mapping. Exceltic.dev will configure the integration.