Kommo + DocuSign: Auto-Advance Deal After Contract Signing

DocuSign is the global leader in e-signature with over 70% market share among B2B companies in North America. Kommo is a CRM for managing sales pipelines. Without an integration between them, the legally significant moment - contract signing - is never reflected in the CRM automatically. The sales rep receives an email from DocuSign saying “Contract signed,” then manually opens Kommo and moves the deal to the next stage.

This delay and manual step create several problems: deals get stuck in “Contract Sent” status even after signing, reporting no longer reflects reality, and new client onboarding starts late. For teams with 30+ active deals and multiple reps, this is a systemic issue, not an occasional hiccup.

This article shows how to connect DocuSign to Kommo via DocuSign Connect (webhook), pass the deal ID into the envelope, and automatically advance the pipeline on every signing event.

Why standard tools fall short

DocuSign has a Zapier integration that catches envelope.completed. But it has two limitations: there is no built-in way to pass and retrieve an arbitrary Kommo identifier, and Zapier’s delay (up to 15 minutes depending on your plan) is critical when the client has just signed and your rep should already be calling to start onboarding.

Zoho Sign, Scrive, and other e-sign platforms use a similar architecture. The core principle applies to all of them: pass kommo_lead_id in the envelope metadata at creation time so the webhook always knows which deal to update.

The key mechanism: Custom Fields in the envelope

DocuSign supports Custom Fields - arbitrary envelope-level fields. They are passed at envelope creation via the API and returned in every webhook event. This is the binding point: we put kommo_lead_id into a custom field when creating the envelope, and the webhook always knows which deal to update.

import docusign_esign as dse

# Create envelope with Kommo lead ID binding
def create_docusign_envelope(lead_id: str, signer_email: str,
                             signer_name: str, doc_base64: str) -> str:
    api_client = get_ds_api_client()  # JWT Auth
    envelopes_api = dse.EnvelopesApi(api_client)

    document = dse.Document(
        document_base64=doc_base64,
        name="Contract",
        file_extension="pdf",
        document_id="1"
    )
    signer = dse.Signer(
        email=signer_email,
        name=signer_name,
        recipient_id="1",
        routing_order="1"
    )
    # Sign here tab
    sign_here = dse.SignHere(
        anchor_string="/sn1/",
        anchor_units="pixels",
        anchor_y_offset="10",
        anchor_x_offset="20"
    )
    signer.tabs = dse.Tabs(sign_here_tabs=[sign_here])

    # Custom field with kommo_lead_id
    custom_field = dse.TextCustomField(
        name="kommo_lead_id",
        value=str(lead_id),
        required="false",
        show="false"  # hidden from the signer
    )

    envelope_def = dse.EnvelopeDefinition(
        email_subject="Contract for signing",
        documents=[document],
        recipients=dse.Recipients(signers=[signer]),
        custom_fields=dse.CustomFields(text_custom_fields=[custom_field]),
        status="sent"
    )

    result = envelopes_api.create_envelope(
        account_id=DS_ACCOUNT_ID,
        envelope_definition=envelope_def
    )
    return result.envelope_id

Configuring DocuSign Connect

DocuSign Connect is the built-in webhook system. Configure it in DocuSign Admin -> Settings -> Connect.

Configuration parameters:

  • URL: your endpoint (https://your-server.com/docusign/webhook)
  • Trigger Events: Envelope Completed, Envelope Voided, Recipient Completed
  • Include Data: select Custom Fields, Recipients, Envelope Fields
  • Authentication: HMAC (recommended) or Basic Auth

Handling webhook events

from flask import Flask, request, abort
import requests, hashlib, hmac, base64

app = Flask(__name__)
DS_HMAC_KEY    = "your_docusign_hmac_key"   # from Connect configuration
KOMMO_DOMAIN   = "yourdomain.kommo.com"
KOMMO_TOKEN    = "your_kommo_token"
KOMMO_BASE     = f"https://{KOMMO_DOMAIN}/api/v4"

def verify_docusign_hmac(payload: bytes, signature_b64: str) -> bool:
    """DocuSign HMAC-SHA256 verification."""
    mac = hmac.new(DS_HMAC_KEY.encode(), payload, hashlib.sha256)
    expected = base64.b64encode(mac.digest()).decode()
    return hmac.compare_digest(expected, signature_b64)

@app.route("/docusign/webhook", methods=["POST"])
def docusign_webhook():
    sig = request.headers.get("X-DocuSign-Signature-1", "")
    if not verify_docusign_hmac(request.get_data(), sig):
        abort(401)

    event = request.json
    status = event.get("status", "")

    # Extract kommo_lead_id from custom fields
    lead_id = None
    custom_fields = (
        event.get("envelopeSummary", {})
             .get("customFields", {})
             .get("textCustomFields", [])
    )
    for field in custom_fields:
        if field.get("name") == "kommo_lead_id":
            lead_id = field.get("value")
            break

    if not lead_id:
        return "ok", 200  # envelope not linked to Kommo

    envelope_id    = event.get("envelopeId", "")
    completed_time = event.get("completedDateTime", "")[:10]

    if status == "completed":
        handle_signed(lead_id, envelope_id, completed_time)
    elif status == "voided":
        void_reason = event.get("voidedReason", "")
        handle_voided(lead_id, envelope_id, void_reason)

    return "ok", 200

def get_kommo_headers():
    return {
        "Authorization": f"Bearer {KOMMO_TOKEN}",
        "Content-Type":  "application/json"
    }

def handle_signed(lead_id: str, envelope_id: str, signed_date: str):
    """Envelope completed: advance stage, save envelope ID."""
    hs = requests.Session()
    hs.headers.update(get_kommo_headers())

    # Move deal to the next stage + custom fields
    hs.patch(f"{KOMMO_BASE}/leads", json=[{
        "id":      int(lead_id),
        "pipeline_id": YOUR_PIPELINE_ID,
        "status_id":   CONTRACT_SIGNED_STAGE_ID,  # "Contract Signed" stage
        "custom_fields_values": [
            {"field_code": "DOCUSIGN_ENVELOPE_ID",
             "values":     [{"value": envelope_id}]},
            {"field_code": "CONTRACT_SIGNED_DATE",
             "values":     [{"value": signed_date}]},
        ]
    }])

    # Confirmation note
    hs.post(f"{KOMMO_BASE}/leads/notes", json=[{
        "entity_id": int(lead_id),
        "note_type":  "common",
        "params":     {"text": (
            f"DocuSign contract signed on {signed_date}.\n"
            f"Envelope ID: {envelope_id}"
        )}
    }])

def handle_voided(lead_id: str, envelope_id: str, reason: str):
    """Envelope voided: create task for manager."""
    hs = requests.Session()
    hs.headers.update(get_kommo_headers())

    import time
    hs.post(f"{KOMMO_BASE}/tasks", json=[{
        "task_type_id": 2,  # meeting/task
        "entity_type":  "leads",
        "entity_id":    int(lead_id),
        "text":         f"DocuSign envelope voided. Reason: {reason}. Review the contract and resend.",
        "complete_till": int(time.time()) + 86400,
    }])

    hs.post(f"{KOMMO_BASE}/leads/notes", json=[{
        "entity_id": int(lead_id),
        "note_type":  "common",
        "params":     {"text": f"DocuSign envelope voided. Reason: {reason}. Envelope ID: {envelope_id}"}
    }])

CONTRACT_SIGNED_STAGE_ID and YOUR_PIPELINE_ID are the values from your pipeline settings in Kommo. They are available via GET /api/v4/leads/pipelines.

JWT authentication for the DocuSign API

To create envelopes via the DocuSign API (not just receive webhooks), the JWT Grant Flow is used:

from docusign_esign import ApiClient
from jwt import encode as jwt_encode
import time

DS_INTEGRATION_KEY = "your_integration_key"
DS_ACCOUNT_ID      = "your_account_id"
DS_PRIVATE_KEY     = open("docusign_private_key.pem").read()
DS_USER_ID         = "your_user_id"

def get_ds_api_client() -> ApiClient:
    api_client = ApiClient()
    api_client.set_base_path("https://na4.docusign.net/restapi")

    jwt_payload = {
        "iss": DS_INTEGRATION_KEY,
        "sub": DS_USER_ID,
        "aud": "account-d.docusign.com",
        "iat": int(time.time()),
        "exp": int(time.time()) + 3600,
        "scope": "signature impersonation"
    }
    token = jwt_encode(jwt_payload, DS_PRIVATE_KEY, algorithm="RS256")
    api_client.set_default_header("Authorization", f"Bearer {token}")
    return api_client

The private key is generated in DocuSign Admin -> Apps and Keys -> RSA Keypairs. Access must be explicitly confirmed once via the admin consent URL.

Real-world case

A SaaS company with enterprise sales: average cycle of 45 days, 3-4 stages including signing an MSA and Order Form. Before the integration: after signing, the envelope would arrive in the rep’s email, who would then open Kommo and manually update the deal. Average delay - 2-4 hours. In 12% of cases, the rep forgot to update the deal on the same day.

After the custom integration:

  • The deal moves to the “Contract Signed” stage within 30 seconds
  • Signing date and Envelope ID are written to Kommo fields automatically
  • When an envelope is voided, a task is created for the manager immediately
  • 0 “lost” deals stuck in “Awaiting Signature” status after actual signing

Who this is relevant for

B2B teams on Kommo with a sales cycle that includes mandatory contract signing. Especially important for the enterprise segment, where contracts require multiple signers (multiple recipients in DocuSign) and the delay between the final signature and the start of work is critical.

Similar integrations are available for other e-sign platforms in our stack: Kommo + Zoho Sign, Kommo + Scrive, Kommo + Juro - the last one is relevant if you need not just signing but AI-generation of the contract itself.

Frequently asked questions

Does this work with a DocuSign Free plan?

DocuSign Connect (webhooks) is available on Business Pro and higher plans. On the basic Personal plan, webhooks are not available. API integration requires Business Pro or Enterprise. Check your plan in DocuSign Admin -> Billing.

How do I handle an envelope with multiple signers?

With two signers, DocuSign sends recipient_completed events for each signer and a final envelope_completed when everyone has signed. Our implementation only handles envelope_completed - this guarantees that all signatures have been collected. You can add an intermediate status in Kommo for each recipient_completed event.

How do I download the signed PDF and save it to Kommo?

The DocuSign API lets you download the signed document: GET /envelopes/{envelopeId}/documents/combined. In our implementation, a link to the DocuSign document is stored in Kommo rather than the PDF itself - to avoid bloating Kommo with binary attachments. If you need to store it in Kommo, download the PDF and upload it via POST /api/v4/leads/{id}/files.

How do I pass kommo_lead_id if envelopes are created manually in DocuSign Web?

If reps create envelopes through the DocuSign UI (not via API), the custom_field with kommo_lead_id can be added manually when creating the envelope - DocuSign Web supports custom fields in Advanced Options. An alternative: identify the deal by the signer’s email using Kommo API search.

Summary

The Kommo + DocuSign integration is built on two connection points: passing kommo_lead_id in a custom field at envelope creation and handling envelope.completed / envelope.voided via DocuSign Connect webhook. The flow:

  • Create envelope via DocuSign API with textCustomField: {name: "kommo_lead_id"}
  • DocuSign Connect webhook -> extract kommo_lead_id from custom fields
  • envelope.completed -> Kommo: move deal to “Signed” stage, update date and ID fields
  • envelope.voided -> Kommo: create a task for the rep to resend

If you use DocuSign and want to eliminate the manual step between “contract signed” and “deal advanced” - describe your requirements to the Exceltic.dev team. We will set it up to match your pipeline structure and number of signers.

More articles

All →