Kommo + Salesmsg: SMS and Calls from Your Sales Pipeline Logged in the Deal Card

Salesmsg is a business SMS and calling platform popular with sales teams in the US and Canada - local numbers, two-way SMS, CRM integration. Kommo is a CRM with a sales pipeline. Without an integration, your SMS conversations with clients live inside Salesmsg while your reps constantly switch between two interfaces. With the integration, every SMS and call from Salesmsg automatically appears as a note in the Kommo deal card.

SMS remains an underrated channel in B2B: open rates are around 98% versus 20-30% for email. In the North American market, an SMS from a local number is the standard way to confirm a meeting, follow up on a decision, or share a document link. Salesmsg provides exactly this infrastructure: a local number, business messaging compliance, and A2P 10DLC registration.

Salesmsg is a two-way business SMS and calling platform with a REST API, webhook notifications for inbound messages and calls, and the ability to send SMS programmatically from any system.

Why This Matters for Your Kommo Pipeline

Kommo supports messengers (WhatsApp, Telegram, Instagram) but does not natively support SMS on US/CA numbers. Salesmsg fills this gap for companies working with the North American market.

Two directions of integration:

  1. Kommo -> Salesmsg: when a deal changes stage, automatically send the client an SMS reminder
  2. Salesmsg -> Kommo: log inbound SMS and calls as notes in the deal card

Technical Architecture

Kommo CRM
  -> deal.status_changed (stage "Meeting Scheduled")
  -> POST https://app.salesmsg.com/api/v2/messages/send
     {to: client_phone, body: "Meeting reminder..."}

Client
  -> Replies to SMS / calls back

Salesmsg
  -> POST /your-server/webhooks/salesmsg
     {type: "message.received", contact_phone, body, lead_number}

Your server
  -> Find deal in Kommo by phone number
  -> POST /api/v4/leads/{id}/notes {text: "SMS from client: ..."}

Implementation: Sending SMS from Kommo

import requests, os
from flask import Flask, request, jsonify

app = Flask(__name__)

KOMMO_DOMAIN    = os.environ["KOMMO_DOMAIN"]
KOMMO_TOKEN     = os.environ["KOMMO_TOKEN"]
SALESMSG_KEY    = os.environ["SALESMSG_API_KEY"]
SALESMSG_SECRET = os.environ["SALESMSG_WEBHOOK_SECRET"]
SMS_FROM_NUMBER = os.environ["SMS_FROM_NUMBER"]   # your Salesmsg number

KOMMO_BASE = f"https://{KOMMO_DOMAIN}/api/v4"
KOMMO_HDR  = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
SM_BASE    = "https://app.salesmsg.com/api/v2"
SM_HDR     = {"Authorization": f"Bearer {SALESMSG_KEY}"}

MEETING_STATUS_ID = 12345   # ID of the "Meeting Scheduled" stage

@app.route("/webhooks/kommo", methods=["POST"])
def kommo_event():
    data = request.json or {}
    for lead in data.get("leads", {}).get("status", []):
        if lead.get("status_id") == MEETING_STATUS_ID:
            send_meeting_reminder(lead["id"])
    return jsonify({"ok": True}), 200

def get_client_phone(lead_id: int) -> str | None:
    r = requests.get(
        f"{KOMMO_BASE}/leads/{lead_id}",
        headers=KOMMO_HDR,
        params={"with": "contacts"},
    )
    if not r.ok:
        return None
    contacts = r.json().get("_embedded", {}).get("contacts", [])
    if not contacts:
        return None

    contact_id = contacts[0]["id"]
    cr = requests.get(f"{KOMMO_BASE}/contacts/{contact_id}", headers=KOMMO_HDR)
    if not cr.ok:
        return None

    for f in cr.json().get("custom_fields_values") or []:
        if f.get("field_code") == "PHONE":
            vals = f.get("values", [])
            if vals:
                return str(vals[0]["value"])
    return None

def send_meeting_reminder(lead_id: int):
    phone = get_client_phone(lead_id)
    if not phone:
        return

    body = (
        "Hi! Just a reminder about our upcoming meeting. "
        "If you have any questions, feel free to reply to this message."
    )

    r = requests.post(
        f"{SM_BASE}/messages/send",
        headers=SM_HDR,
        json={
            "to":   phone,
            "from": SMS_FROM_NUMBER,
            "body": body,
        },
        timeout=10,
    )
    if r.ok:
        requests.post(
            f"{KOMMO_BASE}/leads/{lead_id}/notes",
            headers=KOMMO_HDR,
            json=[{"note_type": "common", "params": {"text": f"SMS sent: {body}"}}],
        )

Implementation: Logging Inbound Messages in Kommo

import hmac, hashlib

def verify_salesmsg_webhook(raw_body: bytes, sig_header: str) -> bool:
    expected = hmac.new(
        SALESMSG_SECRET.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, sig_header)

@app.route("/webhooks/salesmsg", methods=["POST"])
def salesmsg_event():
    sig = request.headers.get("X-Salesmsg-Signature", "")
    if not verify_salesmsg_webhook(request.data, sig):
        return jsonify({"error": "unauthorized"}), 401

    data       = request.json or {}
    event_type = data.get("type", "")
    from_phone = data.get("contact_phone", "")
    body_text  = data.get("body", "")

    if event_type == "message.received" and from_phone:
        log_sms_to_kommo(from_phone, body_text, direction="in")

    elif event_type == "call.completed":
        duration  = data.get("duration", 0)
        recording = data.get("recording_url", "")
        log_call_to_kommo(from_phone, duration, recording)

    return jsonify({"ok": True}), 200

def find_lead_by_phone(phone: str) -> int | None:
    # Search Kommo contacts by phone number.
    r = requests.get(
        f"{KOMMO_BASE}/contacts",
        headers=KOMMO_HDR,
        params={"query": phone, "limit": 1},
    )
    if not r.ok:
        return None
    contacts = r.json().get("_embedded", {}).get("contacts", [])
    if not contacts:
        return None
    # Find linked deals
    contact_id = contacts[0]["id"]
    lr = requests.get(
        f"{KOMMO_BASE}/contacts/{contact_id}/links",
        headers=KOMMO_HDR,
    )
    if not lr.ok:
        return None
    leads = [l for l in lr.json().get("_embedded", {}).get("links", []) if l.get("to_entity_type") == "leads"]
    return leads[0]["to_entity_id"] if leads else None

def log_sms_to_kommo(phone: str, text: str, direction: str):
    lead_id = find_lead_by_phone(phone)
    if not lead_id:
        return
    direction_label = "Inbound SMS" if direction == "in" else "Outbound SMS"
    requests.post(
        f"{KOMMO_BASE}/leads/{lead_id}/notes",
        headers=KOMMO_HDR,
        json=[{"note_type": "common", "params": {"text": f"{direction_label} ({phone}): {text}"}}],
    )

def log_call_to_kommo(phone: str, duration: int, recording_url: str):
    lead_id = find_lead_by_phone(phone)
    if not lead_id:
        return
    note_text = f"Salesmsg call ({phone}): {duration}s."
    if recording_url:
        note_text += f" Recording: {recording_url}"
    requests.post(
        f"{KOMMO_BASE}/leads/{lead_id}/notes",
        headers=KOMMO_HDR,
        json=[{"note_type": "call_in", "params": {"text": note_text, "duration": duration}}],
    )

Use Cases

Automated SMS by pipeline stage:

  • “Meeting Scheduled” -> reminder the day before the meeting
  • “Proposal Sent” -> “Did you receive our proposal? Happy to answer any questions.”
  • “Invoice Sent” -> payment reminder after 3 days

Inbound SMS in Kommo:

  • Client replies to an SMS -> note added to the deal card with the message text
  • Rep sees the full SMS thread in the deal timeline

Calls:

  • call.completed webhook -> note with call duration and a link to the recording

Real-World Case

A SaaS company with US clients and 6 sales reps. Before the integration: SMS was handled on reps’ personal phones with no log in the CRM - when a rep left, the conversation history was lost. After the integration: all SMS and calls go through the company’s Salesmsg number, and every message appears in the deal card. Onboarding a new rep with the full conversation history took 15 minutes instead of several hours of context-gathering.

Who Needs This

Companies with US/CA clients that use SMS as their primary follow-up channel. If your reps are actively texting clients from personal phones, that’s the first signal: communication is happening outside the CRM and will be lost when that employee leaves.

A related task - phone calls with transcripts - is covered in the article on VoIP integration with Kommo.

Frequently Asked Questions

How do I find a deal in Kommo if the client writes from a new number?

If the number does not match any contact in Kommo, create a new contact and a new deal via the API. Apply a tag like “Salesmsg inbound” so reps can filter these leads.

Does Salesmsg support MMS and file attachments?

Yes, Salesmsg supports MMS (photos, PDFs). In the webhook event, the media_urls field contains an array of attachment URLs. Add those links to the Kommo note along with the message text.

How do I ensure A2P 10DLC compliance?

A2P 10DLC (Application-to-Person 10-Digit Long Code) is a regulatory requirement for business SMS in the US. Salesmsg walks you through the Brand and Campaign registration process. Without registration, carriers filter your messages. Average approval time is 3-5 business days.

Summary

Kommo + Salesmsg connects SMS/calls and your CRM:

  • Kommo webhook -> automated SMS triggered by pipeline events
  • Inbound SMS -> notes in the deal card via find-by-phone
  • Completed calls -> note with duration and recording link
  • Webhook verification via HMAC-SHA256 header

If your team works with the US/CA market and wants to bring SMS communication into the CRM context - reach out to Exceltic.dev. We will set up the integration for your specific pipeline stack.

More articles

All →