Why the native integration doesn’t work
Rocketlane is a platform for client onboarding and project delivery. Its main differentiator from Jira or Asana is the customer portal: the client can see their onboarding progress in real time, communicate with the team in the context of tasks, and upload documents. This reduces the load on CSMs (Customer Success Managers) and creates a transparent process for the client.
There is no native Rocketlane + Kommo integration. A typical problem: a deal closes in Kommo, the salesperson announces in Slack “client Acme Corp signed the contract,” the CSM manually creates a project in Rocketlane, enters client data, adds members. It takes 2-3 days for actual onboarding to begin. The client receives no information during this period.
Connecting custom Kommo integrations with the Rocketlane API solves this: the project is created automatically the moment the deal closes.
What gets built - solution architecture
Kommo: deal moves to Won status
--> Webhook --> Python service
--> Rocketlane API: POST /projects (from template_id)
--> Rocketlane API: POST /projects/{id}/members (add client)
--> Kommo API: add Note about created project
Rocketlane: milestone.completed
--> Webhook --> Python service
--> Kommo API: add Note to deal
Technical details
Rocketlane API Auth. Bearer token. Obtained in Rocketlane Settings -> API -> Generate Token. Passed in the Authorization: Bearer {token} header.
Rocketlane API endpoints:
POST /v1/projects- create a project. Parameters:name,templateId,startDate,descriptionGET /v1/templates- list project templatesPOST /v1/projects/{id}/members- add a member. Parameters:email,role(customer/team)GET /v1/projects/{id}/milestones- list milestones- Webhooks: configured in Rocketlane Settings -> Integrations -> Webhooks
Won status in Kommo. In Kommo, each pipeline has a special “Won” status. Its ID can be found via GET /api/v4/pipelines/{pipeline_id}/statuses. The Won status has is_final: true and type: won.
Rocketlane Customer Portal. When adding a member with role: customer, Rocketlane automatically sends an email invitation to the portal. The client receives a personal link and sees the onboarding plan.
Step-by-step implementation
Step 1. Identify Won status
import os
import requests
KOMMO_DOMAIN = os.environ["KOMMO_DOMAIN"]
KOMMO_TOKEN = os.environ["KOMMO_ACCESS_TOKEN"]
ROCKETLANE_TOKEN = os.environ["ROCKETLANE_TOKEN"]
ROCKETLANE_BASE = "https://api.rocketlane.com/api/v1"
def get_won_status_ids() -> set[int]:
"""Get all Won status IDs from all Kommo pipelines."""
url = f"https://{KOMMO_DOMAIN}/api/v4/pipelines"
headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
r = requests.get(url, params={"with": "statuses"}, headers=headers, timeout=10)
if not r.ok:
return set()
won_ids = set()
pipelines = r.json().get("_embedded", {}).get("pipelines", [])
for pipeline in pipelines:
statuses = pipeline.get("_embedded", {}).get("statuses", [])
for status in statuses:
if status.get("type") == 142: # 142 = Won in Kommo
won_ids.add(status["id"])
return won_ids
Step 2. Create Rocketlane project from template
from flask import Flask, request
from datetime import datetime, timedelta
app = Flask(__name__)
ROCKETLANE_TEMPLATE_ID = os.environ.get("ROCKETLANE_TEMPLATE_ID", "")
# Cache Won statuses at startup
WON_STATUS_IDS = get_won_status_ids()
def get_lead_data(lead_id: int) -> dict:
"""Get deal data including contacts and company."""
url = f"https://{KOMMO_DOMAIN}/api/v4/leads/{lead_id}"
headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
r = requests.get(url, params={"with": "contacts,companies"}, headers=headers, timeout=10)
r.raise_for_status()
return r.json()
def get_contact_email_and_name(contact_id: int) -> tuple[str, str]:
"""Get contact email and name."""
url = f"https://{KOMMO_DOMAIN}/api/v4/contacts/{contact_id}"
headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
r = requests.get(url, headers=headers, timeout=10)
if not r.ok:
return "", ""
contact = r.json()
name = contact.get("name", "")
email = ""
for cf in contact.get("custom_fields_values", []) or []:
if cf.get("field_code") == "EMAIL":
email = cf["values"][0]["value"]
break
return email, name
def create_rocketlane_project(project_name: str, template_id: str) -> dict | None:
"""Create a project in Rocketlane from a template."""
url = f"{ROCKETLANE_BASE}/projects"
headers = {
"Authorization": f"Bearer {ROCKETLANE_TOKEN}",
"Content-Type": "application/json",
}
start_date = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
payload = {
"name": project_name,
"templateId": template_id,
"startDate": start_date,
"status": "active",
}
r = requests.post(url, json=payload, headers=headers, timeout=30)
if not r.ok:
print(f"Rocketlane project creation failed: {r.status_code} {r.text}")
return None
return r.json()
def add_customer_to_project(project_id: str, email: str, name: str) -> bool:
"""Add a client to a Rocketlane project (they will receive a portal invitation)."""
url = f"{ROCKETLANE_BASE}/projects/{project_id}/members"
headers = {
"Authorization": f"Bearer {ROCKETLANE_TOKEN}",
"Content-Type": "application/json",
}
first, *last_parts = name.split(" ", 1)
payload = {
"email": email,
"role": "customer",
"firstName": first,
"lastName": last_parts[0] if last_parts else "",
}
r = requests.post(url, json=payload, headers=headers, timeout=10)
return r.ok
def add_kommo_note(lead_id: int, text: str):
url = f"https://{KOMMO_DOMAIN}/api/v4/leads/{lead_id}/notes"
headers = {
"Authorization": f"Bearer {KOMMO_TOKEN}",
"Content-Type": "application/json",
}
payload = [{"note_type": "common", "params": {"text": text}}]
requests.post(url, json=payload, headers=headers, timeout=10)
@app.route("/webhooks/kommo/won", methods=["POST"])
def kommo_won_webhook():
"""Handle deal transition to Won."""
data = request.form
lead_id = int(data.get("leads[status][0][id]", 0))
new_status_id = int(data.get("leads[status][0][status_id]", 0))
if not lead_id or new_status_id not in WON_STATUS_IDS:
return {"ok": True}
lead = get_lead_data(lead_id)
lead_name = lead.get("name", f"Deal #{lead_id}")
# Get primary contact data
contacts = lead.get("_embedded", {}).get("contacts", [])
if not contacts:
add_kommo_note(lead_id, "Rocketlane: could not create project - no contact in deal")
return {"ok": True}
contact_id = contacts[0]["id"]
email, name = get_contact_email_and_name(contact_id)
if not email:
add_kommo_note(lead_id, "Rocketlane: could not create project - no email for contact")
return {"ok": True}
# Get company name
companies = lead.get("_embedded", {}).get("companies", [])
company_name = companies[0].get("name", "") if companies else ""
project_name = f"Onboarding: {company_name or name}"
# Create project
project = create_rocketlane_project(project_name, ROCKETLANE_TEMPLATE_ID)
if not project:
add_kommo_note(lead_id, "Rocketlane: error creating project")
return {"ok": True}
project_id = project["id"]
project_url = project.get("portalUrl", "")
# Add client
add_customer_to_project(project_id, email, name)
# Record in Kommo
note_text = (
f"Rocketlane: onboarding project created\n"
f"Project: {project_name}\n"
f"ID: {project_id}\n"
f"Portal: {project_url}\n"
f"Client added: {email}"
)
add_kommo_note(lead_id, note_text)
# Save project ID to custom Kommo field
ROCKETLANE_FIELD_ID = int(os.environ.get("KOMMO_ROCKETLANE_FIELD_ID", 0))
if ROCKETLANE_FIELD_ID:
patch_url = f"https://{KOMMO_DOMAIN}/api/v4/leads"
headers = {
"Authorization": f"Bearer {KOMMO_TOKEN}",
"Content-Type": "application/json",
}
requests.patch(patch_url, json=[{
"id": lead_id,
"custom_fields_values": [{
"field_id": ROCKETLANE_FIELD_ID,
"values": [{"value": project_id}]
}]
}], headers=headers, timeout=10)
return {"ok": True}
@app.route("/webhooks/rocketlane", methods=["POST"])
def rocketlane_webhook():
"""Handle Rocketlane events (milestone completed)."""
event = request.json
event_type = event.get("eventType")
if event_type != "milestone.completed":
return {"ok": True}
project_id = event.get("projectId", "")
milestone_name = event.get("milestoneName", "")
# Find deal by project_id from custom field
lead_id = find_lead_by_rocketlane_project(project_id)
if lead_id:
add_kommo_note(
lead_id,
f"Rocketlane: milestone completed - '{milestone_name}'"
)
return {"ok": True}
def find_lead_by_rocketlane_project(project_id: str) -> int | None:
"""Find a deal in Kommo by the Rocketlane Project ID custom field."""
ROCKETLANE_FIELD_ID = int(os.environ.get("KOMMO_ROCKETLANE_FIELD_ID", 0))
if not ROCKETLANE_FIELD_ID:
return None
url = f"https://{KOMMO_DOMAIN}/api/v4/leads"
headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
params = {f"filter[custom_fields][{ROCKETLANE_FIELD_ID}][]": project_id}
r = requests.get(url, params=params, headers=headers, timeout=10)
if r.ok:
leads = r.json().get("_embedded", {}).get("leads", [])
return leads[0]["id"] if leads else None
return None
if __name__ == "__main__":
app.run(port=5000)
Real case with numbers
For a SaaS company with ARR above $500k and 5-10 new clients per month, automating onboarding through Rocketlane is critical for scaling the CSM team.
Before the integration: the CSM received a signal about a new client from a Slack notification from the salesperson. Creating a project in Rocketlane took 20-40 minutes: fill in the data, select a template, add the client, set timelines. With 8 new clients per month - 3-5 hours of CSM time just on project creation.
After the integration: the project is created automatically within 30 seconds of closing the deal. The client receives an invitation to the customer portal via email from Rocketlane before the CSM even opens a laptop. A typical outcome is reducing time-to-onboarding from 2-3 days to a few hours.
An additional effect: the CSM sees all client data in Rocketlane (company name, contact name, deal size) already filled in - no need to transfer manually from Kommo.
Who this is for
The integration is relevant for companies that:
- Use Rocketlane for structured client onboarding with a customer portal
- Track their sales pipeline in Kommo and want to automate the handoff from sales to success
- Have a repeatable onboarding process (template in Rocketlane) applied to each new client
- Work in B2B SaaS or professional services with an onboarding cycle of 2+ weeks
If you use other task management tools - for example, Kommo + Asana or Kommo + ClickUp - the Won-event automation principle is analogous.
Frequently asked questions
Can different Rocketlane templates be used for different pricing plans?
Yes. Add a “Pricing Plan” custom field in Kommo. In the webhook handler, read this field and select the corresponding templateId from a mapping. For example: Plan_Basic -> template_001, Plan_Pro -> template_002.
What if the deal was marked Won retroactively and onboarding has already started?
Add a duplicate check: before creating a project, check the rocketlane_project_id custom field in the deal. If it’s already filled - the project exists, skip creation.
Can multiple client contacts be added to Rocketlane?
Yes. Kommo allows multiple contacts to be linked to a deal. In the handler, iterate over all contacts of type “client” and call add_customer_to_project for each.
Is reverse sync of all Rocketlane tasks to Kommo needed? Not recommended - it creates noise in Kommo. Syncing key milestones is sufficient (completion of onboarding, first client success). Minor Rocketlane tasks don’t need to be in the CRM.
If you need a Kommo + Rocketlane integration - describe your stack and scenario to the Exceltic.dev team. We’ll work through the architecture in one meeting.