Kommo + Todoist: Automatic Task Creation from Won Deals
Todoist is a popular task manager with a REST API v2, widely used by sales professionals for personal planning. Kommo is a CRM with a sales pipeline. Without an integration, tasks from Kommo never reach Todoist, and a manager’s personal tasks have no connection to deal cards. With the integration: when a deal is won, a set of onboarding tasks is automatically created in Todoist; when a task is completed in Todoist, a note is added back in Kommo.
Todoist is especially popular among managers who work primarily on mobile - its iOS and Android apps sync instantly, and push notifications are reliable. For B2B teams with a 2-6 week sales cycle, Todoist as a personal task manager complements Kommo: large milestones stay in the CRM, while the daily to-do list lives in Todoist.
Todoist REST API v2 is a JSON API with Bearer token authentication. Creating tasks: POST /tasks, reading: GET /tasks, completing: POST /tasks/{task_id}/close. Webhook events are sent on task creation, update, and completion.
Integration Architecture
Kommo: deal status_id = 142 (Won)
-> POST /api/v1/tasks (Todoist)
Onboarding tasks for client X
Todoist: task completed
-> POST /your-server/webhooks/todoist
{event_name: item:completed, item: {id, content}}
-> Kommo: POST /api/v4/leads/{id}/notes {text: "Todoist: completed - task name"}
Implementation: Creating Onboarding Tasks on Deal Won
import requests, os, hmac, hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
KOMMO_DOMAIN = os.environ["KOMMO_DOMAIN"]
KOMMO_TOKEN = os.environ["KOMMO_TOKEN"]
TODOIST_TOKEN = os.environ["TODOIST_API_TOKEN"]
TODOIST_SECRET = os.environ["TODOIST_CLIENT_SECRET"] # for webhook verification
KOMMO_BASE = f"https://{KOMMO_DOMAIN}/api/v4"
KOMMO_HDR = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
TD_BASE = "https://api.todoist.com/rest/v2"
TD_HDR = {"Authorization": f"Bearer {TODOIST_TOKEN}"}
KOMMO_WON_STATUS = 142
# Onboarding task template created after a deal closes
ONBOARDING_TASKS = [
("Send welcome email to client", 1, 0), # (title, priority, offset_days)
("Schedule kickoff meeting", 2, 0),
("Set up access and onboarding resources", 2, 1),
("First check-in with client (end of week 1)", 1, 7),
("Month 1 review", 1, 30),
]
@app.route("/webhooks/kommo", methods=["POST"])
def kommo_event():
import time
data = request.json or {}
for lead in data.get("leads", {}).get("status", []):
if lead.get("status_id") == KOMMO_WON_STATUS:
create_onboarding_tasks(lead["id"])
return "ok", 200
def get_lead_info(lead_id: int) -> dict:
r = requests.get(f"{KOMMO_BASE}/leads/{lead_id}", headers=KOMMO_HDR, params={"with": "contacts"})
return r.json() if r.ok else {}
def create_todoist_project(name: str) -> str | None:
r = requests.post(
f"{TD_BASE}/projects",
headers=TD_HDR,
json={"name": name},
)
return r.json().get("id") if r.ok else None
def create_onboarding_tasks(lead_id: int):
from datetime import date, timedelta
lead = get_lead_info(lead_id)
client = lead.get("name", f"Lead {lead_id}")
# Create a project for the client
project_id = create_todoist_project(f"Onboarding: {client}")
today = date.today()
for title, priority, offset_days in ONBOARDING_TASKS:
due_date = today + timedelta(days=offset_days)
payload = {
"content": f"{title} - {client}",
"priority": priority, # 1=normal, 2=high, 3=urgent, 4=very urgent
"due_string": due_date.strftime("%Y-%m-%d"),
"description": f"Kommo Lead ID: {lead_id}",
}
if project_id:
payload["project_id"] = project_id
requests.post(f"{TD_BASE}/tasks", headers=TD_HDR, json=payload)
# Note in Kommo
requests.post(
f"{KOMMO_BASE}/leads/{lead_id}/notes",
headers=KOMMO_HDR,
json=[{"note_type": "common", "params": {"text": f"Todoist: created {len(ONBOARDING_TASKS)} onboarding tasks for {client}"}}],
)
Implementation: Todoist Webhook -> Kommo
Todoist sends webhook events via the App Console (Apps -> Your App -> Webhooks). Verification uses HMAC-SHA256:
@app.route("/webhooks/todoist", methods=["POST"])
def todoist_event():
# Signature verification
user_agent = request.headers.get("X-Todoist-Hmac-SHA256", "")
raw_body = request.data
computed = hmac.new(
TODOIST_SECRET.encode(), raw_body, hashlib.sha256
).digest()
import base64
computed_b64 = base64.b64encode(computed).decode()
if not hmac.compare_digest(computed_b64, user_agent):
return "unauthorized", 401
data = request.json or {}
event_name = data.get("event_name", "")
item = data.get("event_data", {})
if event_name == "item:completed":
task_title = item.get("content", "")
task_id = item.get("id", "")
description = item.get("description", "")
# The description stored the Kommo Lead ID: "Kommo Lead ID: 123456"
lead_id = extract_lead_id_from_description(description)
if lead_id:
requests.post(
f"{KOMMO_BASE}/leads/{lead_id}/notes",
headers=KOMMO_HDR,
json=[{"note_type": "common", "params": {
"text": f"Todoist: completed - {task_title}"
}}],
)
return "ok", 200
def extract_lead_id_from_description(description: str) -> int | None:
prefix = "Kommo Lead ID: "
if prefix in description:
try:
return int(description.split(prefix)[1].strip().split()[0])
except (ValueError, IndexError):
return None
return None
Additional Scenarios
Create a Todoist task on any status change (not just Won):
Configure a stage-to-task mapping:
- “Proposal sent” -> task “Follow up in 3 days” (due: +3 days)
- “Invoice issued” -> task “Payment follow-up” (due: +5 days)
- “Deal lost” -> task “Analyze loss reason”
Sync Kommo task statuses -> Todoist:
When a task is created in Kommo via API, also create a mirrored task in Todoist. This requires subscribing to task webhook events in Kommo.
Real-World Case
An IT integrator with a team of 5 managers. Before the integration: managers manually created tasks in Todoist after every won deal - 8-10 minutes per deal, with 10-15 wins per month. After the integration: 5 onboarding tasks with due dates tied to the close date are created automatically. The manager sees the task in Todoist instantly, with no need to switch between interfaces.
Who This Is For
Sales managers who actively use Todoist as their personal planning tool. Companies with a well-defined onboarding process that kicks in after a deal closes.
An alternative approach - creating tasks in ClickUp - is described in Kommo + ClickUp: Tasks and Leads Without Zapier.
Frequently Asked Questions
Can tasks be created inside project Sections?
Yes. First create sections via POST /sections with a project_id, then specify section_id when creating each task. For onboarding you can create sections like “Week 1”, “Week 2”, “Month 1”.
How do I prevent duplicate tasks if the webhook fires twice?
Add an idempotency key: before creating tasks, check via GET /tasks?filter=#{lead_id} (if you use a label with the lead_id). Alternatively, store the list of already-processed lead_ids in a cache (Redis) with a 24h TTL.
Does the Todoist free plan support webhooks?
Webhook events in Todoist are only available through an OAuth App in the Todoist App Console - this requires creating an application in Todoist, which is free. A Personal Access Token for the API works on all plans.
Summary
Kommo + Todoist:
- Kommo Won event -> create a project + set of dated tasks in Todoist
- Store
Kommo Lead ID: {id}in the task description for the callback link - Todoist
item:completedwebhook -> note in Kommo - HMAC-SHA256 verification via
X-Todoist-Hmac-SHA256(base64-encoded)
If you need a Kommo - Todoist integration tailored to your onboarding process, describe your requirements to the Exceltic.dev team.