Kommo + Linear: automatic creation of development tasks from won deals

Kommo + Linear: automatic creation of development tasks from won deals

Linear is a task tracker for engineering teams that uses a GraphQL API instead of REST. This is a fundamental difference from most other tools: a single endpoint, all operations via mutations. The Kommo integration solves a concrete problem — when a deal closes, the relevant Linear issue is created automatically with data from the CRM, without manually transferring context between the sales team and development.

The problem: sales and development work in different systems

Without integration:
— Won in Kommo -> manager writes in Slack “please create a task for client X”
— Developer opens Linear, manually creates an issue, copies context from the CRM
— A week later the client clarifies details — the manager does not know the status in Linear
— The Kommo deal does not reflect implementation progress: no current data available for the next call

With integration:
— Won in Kommo -> Linear issue created within 3 minutes with full data from the deal
— Issue status change in Linear -> Note in the Kommo card
— Manager sees progress in CRM without opening the tracker

What is synchronized

Kommo -> Linear:
— Deal name -> issue title
— Description / spec from a custom field -> description (supports Markdown)
— Won stage -> issue status “In Progress” or “Backlog” depending on team process
— Contact email and amount -> added to description for context
— Responsible manager -> assigneeId via email -> Linear userId mapping
— Deadline from custom field -> dueDate in ISO 8601
— Kommo deal ID -> stored in description or a separate label for back-tracing

Linear -> Kommo:
— Issue completed (status “Done”) -> Note in the Kommo deal
— Issue assigned to a new person -> Note with the name for the manager
— Task identifier (e.g. ENG-42) -> custom field in the Kommo card

Architecture

Kommo Webhook: deal moved to Won
  ↓ Backend
  1. GET /api/v4/leads/{id} + contacts
     -> name, description from custom fields, email, amount
  2. Linear GraphQL: mutation issueCreate
     -> teamId + title + description + projectId + assigneeId + priority
     -> receive issue.id, issue.identifier (e.g. ENG-42), issue.url
  3. Kommo: PATCH /leads/{id}
     -> custom field linear_issue_id = ENG-42
  4. Kommo: POST /leads/{id}/notes
     -> "Linear task created: ENG-42. Link: {url}"

Linear Webhook: issue.action = update, state.name = Done
  ↓ Backend
  1. From payload: identifier, assignee, state
  2. Find kommo_deal_id by linear_issue_id in storage (Redis / DB)
  3. Kommo: POST /leads/{deal_id}/notes
     -> "Task ENG-42 closed in Linear. Assignee: {assignee}"

Linear GraphQL API: key requests

Endpoint: https://api.linear.app/graphql. Authentication: Authorization: Bearer <api_key>. All requests are POST with body {"query": "..."} or {"query": "...", "variables": {...}}.

Get list of teams (teamId is required):

import requests

LINEAR_URL = 'https://api.linear.app/graphql'
HEADERS = {
    'Authorization': f'Bearer {LINEAR_API_KEY}',
    'Content-Type': 'application/json'
}

def get_teams() -> list:
    query = """
    query {
        teams {
            nodes {
                id
                name
                key
            }
        }
    }
    """
    resp = requests.post(LINEAR_URL, json={'query': query}, headers=HEADERS)
    return resp.json()['data']['teams']['nodes']
    # [{'id': 'abc-123', 'name': 'Engineering', 'key': 'ENG'}, ...]

Create an issue from deal data:

def create_issue_from_deal(
    team_id: str,
    project_id: str,
    title: str,
    description: str,
    assignee_id: str = None,
    priority: int = 2  # 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low
) -> dict:
    mutation = """
    mutation IssueCreate($input: IssueCreateInput!) {
        issueCreate(input: $input) {
            success
            issue {
                id
                identifier
                url
            }
        }
    }
    """
    variables = {
        'input': {
            'title': title,
            'description': description,
            'teamId': team_id,
            'projectId': project_id,
            'priority': priority
        }
    }
    if assignee_id:
        variables['input']['assigneeId'] = assignee_id

    resp = requests.post(
        LINEAR_URL,
        json={'query': mutation, 'variables': variables},
        headers=HEADERS
    )
    result = resp.json()['data']['issueCreate']
    if result['success']:
        return result['issue']  # {'id': '...', 'identifier': 'ENG-42', 'url': '...'}
    raise RuntimeError(f'Linear issue creation failed: {resp.text}')

Get team projects:

def get_team_projects(team_id: str) -> list:
    query = """
    query TeamProjects($teamId: String!) {
        team(id: $teamId) {
            projects {
                nodes {
                    id
                    name
                    state
                }
            }
        }
    }
    """
    resp = requests.post(
        LINEAR_URL,
        json={'query': query, 'variables': {'teamId': team_id}},
        headers=HEADERS
    )
    return resp.json()['data']['team']['projects']['nodes']

Handle Linear webhook:

from flask import Flask, request
import hmac, hashlib

app = Flask(__name__)

@app.route('/webhooks/linear', methods=['POST'])
def linear_webhook():
    # Signature verification (recommended for production)
    signature = request.headers.get('X-Linear-Signature', '')
    secret = LINEAR_WEBHOOK_SECRET.encode()
    expected = hmac.new(secret, request.data, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, signature):
        return 'Invalid signature', 401

    payload = request.json
    action = payload.get('action')      # 'create' | 'update' | 'remove'
    entity_type = payload.get('type')   # 'Issue' | 'Comment' | 'Project'
    data = payload.get('data', {})

    if entity_type == 'Issue' and action == 'update':
        state = data.get('state', {})
        if state.get('name') == 'Done':
            identifier = data.get('identifier')  # 'ENG-42'
            assignee = data.get('assignee', {}).get('name', 'unknown')

            deal_id = get_deal_id_by_linear_issue(identifier)
            if deal_id:
                create_kommo_note(
                    deal_id,
                    f'Task {identifier} closed in Linear. Assignee: {assignee}.'
                )

    return '', 200

Linear webhook setup: Settings -> API -> Webhooks -> Create webhook. Specify the URL, select event types (Issue), and the team. The webhook secret is used to verify the X-Linear-Signature HMAC-SHA256.

Field mapping: Kommo -> Linear

KommoLinearNotes
Deal nametitleDirect mapping
Custom field “Spec”descriptionMarkdown is supported
Deal amountIn descriptionLinear has no native “amount” field
Responsible managerassigneeIdRequires email -> Linear userId mapping
Deadline from custom fielddueDateISO 8601, optional field
Deal label/typepriority0=None, 1=Urgent, 2=High, 3=Medium, 4=Low

User mapping is a separate configuration step. Linear has no “external ID” field, so you need to maintain a table of {kommo_user_email -> linear_user_id}. Retrieve users:

query = """query { users { nodes { id name email } } }"""

Real-world case

B2B SaaS company (EU market, 30–40 new clients per quarter, separate sales and development teams):

  • Before: Won in Kommo -> manager sent an email to development with a description. Average time from Won to a Linear issue — 1–2 days. Issues were often created without a description or with incorrect priorities.
  • After: Won -> Linear issue within 3 minutes with the full spec from the “Spec” field, the correct project, and High priority. The manager sees the Note “Task ENG-42 created” directly in Kommo.
  • Additional effect: when the task is completed, the manager automatically receives a signal to make a service call to the client — no separate reminders or Slack messages needed.

If the team uses ClickUp — it has a REST API, not GraphQL: a different stack, the same logic. For non-technical teams with broader workflows — see Monday.com.

Who this is relevant for

  • Development team works in Linear, sales in Kommo
  • Need automatic transfer of deal context to a task without manual copying
  • A Won client requires onboarding or technical implementation — the process must start immediately
  • Important to see Linear task progress directly from the Kommo card without switching tools

Frequently asked questions

Linear API — REST or GraphQL?

GraphQL. Single endpoint https://api.linear.app/graphql, all operations — POST with a GraphQL query body. This distinguishes Linear from most SaaS trackers: Jira, Asana, and ClickUp use REST.

How do I get teamId and projectId for the API?

Via a GraphQL query: query { teams { nodes { id name } } } — returns a list of teams with UUIDs. Similarly for projects: team(id: "...") { projects { nodes { id name } } }. These IDs are stored in the integration config and do not change.

Does Linear support priorities in issueCreate?

Yes. The priority field accepts: 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low. When integrating with Kommo, it is convenient to map a deal label or type to a task priority — for example, “Enterprise client” -> Urgent.

How do I verify a webhook from Linear?

Linear signs payloads via HMAC-SHA256. The header X-Linear-Signature contains the hex digest of the request body signed with the webhook secret. Verification: hmac.new(secret, request.data, sha256).hexdigest() == signature. Optional but mandatory for production.

Is OAuth required or is an API key sufficient?

A personal API key is sufficient for server-side integration: Settings -> API -> Personal API keys. OAuth 2.0 is only needed if you are building a public app for multiple Linear accounts. For a single-workspace integration — an API key is simpler and more reliable.

Summary

  • Linear GraphQL API: Bearer token, single endpoint, mutations issueCreate / issueUpdate
  • issueCreate accepts teamId, projectId, assigneeId, priority, dueDate — data from the Kommo deal
  • Webhook payload: action + type + data — filter by type=Issue, state.name=Done
  • Kommo -> Linear user mapping by email — a separate configuration step
  • Typical development timeline — 1–2 weeks

If your development team works in Linear and sales in Kommo — describe the setup: which pipeline stages create tasks, and to which project and team. Exceltic.dev will configure the mapping and two-way event handling.

More articles

All →