Kommo + Contractbook: automatic contract sending from the deal card
Contractbook is a CLM platform (Contract Lifecycle Management): creating contracts from templates, electronic signature, storage, and version tracking. Without the Kommo integration, a manager closes a deal, manually opens Contractbook, and copies the client name, email, and contract terms. With the integration, Won in the pipeline automatically creates a contract from a template populated with deal data and sends it for signature — the manager is only notified when the contract is signed.
How Contractbook differs from DocuSign and PandaDoc
DocuSign and Adobe Sign are electronic signature tools. They take a ready-made document and collect signatures.
Contractbook is a full-featured CLM: templates with dynamic fields, negotiation process (clause comments), versioning, automatic renewal reminders, AI risk analysis in the contract. For SaaS with non-standard terms, enterprise sales with redlines, and teams where the contract change history matters — Contractbook covers more than just signature collection.
If you have a standard contract without edits — signing via DocuSign or Dropbox Sign is simpler. Contractbook justifies itself when a contract is negotiated, not just signed.
What gets synchronized
Kommo -> Contractbook:
— Won -> create Draft contract from template with fields from the deal (name, email, amount, plan, term)
— Won -> send contract for signature to the deal contact
— Deal amount update -> update deal_amount variable in the draft
Contractbook -> Kommo:
— contract.signed -> Note: “Contractbook: contract signed” + stage change
— contract.declined -> Note + task for manager: “Client declined the contract — discuss terms”
— contract.expired -> Note + task: “Contract expired without signature”
— contract.sent -> Note: “Contractbook: contract sent for signature”
Architecture
Kommo Webhook: deal moved to Won
↓ Backend
1. GET /api/v4/leads/{id} + contacts
-> name, email, amount, plan, terms from custom fields
2. Contractbook API: POST /drafts
-> template_id + variables (deal data)
-> creates contract draft
3. Contractbook API: POST /drafts/{draft_id}/send
-> signee: {email, name}
-> sends contract for signature
4. Kommo: PATCH /leads/{id}
-> custom field contractbook_draft_id = draft_id
5. Kommo: POST /leads/{id}/notes
-> "Contractbook: contract sent for signature"
Contractbook Webhook: contract.signed
↓ Backend
1. Verify signature (X-Contractbook-Signature)
2. Find deal by contractbook_draft_id
3. Kommo: PATCH /leads/{id} -> stage change to "Contract signed"
4. Kommo: POST /leads/{id}/notes
-> "Contractbook: contract signed - deal activated"
Contractbook API: key requests
Base URL: https://api.contractbook.com/v1.
Authentication: API key in the Authorization: ApiKey {api_key} header.
Create a draft from a template:
import requests
CB_API_KEY = "your_contractbook_api_key"
CB_BASE_URL = "https://api.contractbook.com/v1"
headers = {
"Authorization": f"ApiKey {CB_API_KEY}",
"Content-Type": "application/json"
}
def create_contract_draft(template_id: str, variables: dict, signee_email: str, signee_name: str) -> dict:
resp = requests.post(
f"{CB_BASE_URL}/drafts",
headers=headers,
json={
"template_id": template_id,
"variables": variables,
"signatories": [
{"email": signee_email, "name": signee_name, "role": "client"}
]
}
)
resp.raise_for_status()
return resp.json()
def send_contract_for_signing(draft_id: str) -> None:
resp = requests.post(
f"{CB_BASE_URL}/drafts/{draft_id}/send",
headers=headers
)
resp.raise_for_status()
def on_deal_won(lead: dict, contact: dict):
email = get_contact_email(contact)
name = contact["name"]
deal_amount = lead.get("price", 0)
plan = get_custom_field(lead, PLAN_FIELD_ID) or "Growth"
# Contractbook template variables - must match the template fields
variables = {
"client_name": name,
"client_email": email,
"deal_amount": str(deal_amount),
"plan": plan,
"contract_date": datetime.now().strftime("%d.%m.%Y"),
"kommo_deal_id": str(lead["id"])
}
draft = create_contract_draft(
template_id=CONTRACTBOOK_TEMPLATE_ID,
variables=variables,
signee_email=email,
signee_name=name
)
draft_id = draft["id"]
send_contract_for_signing(draft_id)
update_kommo_deal(lead["id"], {"contractbook_draft_id": draft_id})
create_kommo_note(lead["id"], f"Contractbook: contract {draft_id} sent for signature")
Handling Contractbook Webhooks:
from flask import Flask, request, abort
import hmac, hashlib
app = Flask(__name__)
CB_WEBHOOK_SECRET = "your_webhook_secret"
def verify_cb_signature(payload: bytes, signature: str) -> bool:
expected = hmac.new(
CB_WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route("/webhooks/contractbook", methods=["POST"])
def contractbook_webhook():
sig = request.headers.get("X-Contractbook-Signature", "")
if not verify_cb_signature(request.data, sig):
abort(403)
payload = request.json
event = payload.get("event")
draft_id = payload.get("data", {}).get("id")
deal_id = find_deal_by_field("contractbook_draft_id", draft_id)
if not deal_id:
return "", 200
if event == "contract.signed":
update_kommo_deal(deal_id, {"stage_id": STAGE_CONTRACT_SIGNED})
create_kommo_note(deal_id, "Contractbook: contract signed by client")
elif event == "contract.declined":
create_kommo_note(deal_id, "Contractbook: client declined the contract")
create_kommo_task(deal_id, "Discuss terms - contract declined in Contractbook")
elif event == "contract.expired":
create_kommo_note(deal_id, "Contractbook: contract expired without signature")
create_kommo_task(deal_id, "Contract in Contractbook expired - remind client")
return "", 200
Webhook setup in Contractbook: Settings -> Integrations -> Webhooks -> Add webhook. Specify the URL and select events. The verification secret is generated at creation.
Template management: variable mapping
In Contractbook, a template contains variables — {{client_name}}, {{deal_amount}}, {{plan}}. When creating a draft, these variables are replaced with values from the variables field in the request body.
Kommo custom field -> Contractbook variable mapping is configured once:
FIELD_TO_VARIABLE = {
PLAN_FIELD_ID: "plan",
COMPANY_FIELD_ID: "client_company",
ADDRESS_FIELD_ID: "billing_address",
TERM_FIELD_ID: "contract_term_months",
}
def build_variables(lead: dict, contact: dict) -> dict:
variables = {
"client_name": contact["name"],
"client_email": get_contact_email(contact),
"deal_amount": str(lead.get("price", 0)),
"contract_date": datetime.now().strftime("%d.%m.%Y"),
}
for field_id, var_name in FIELD_TO_VARIABLE.items():
value = get_custom_field(lead, field_id)
if value:
variables[var_name] = value
return variables
Real case
B2B SaaS (EU, 25–35 new enterprise clients per month, Kommo + Contractbook):
- Before: after Won, the manager manually created a contract in Contractbook, copying data from Kommo. It took 15–20 minutes per deal. Errors in the name or amount -> resend required. When a client signed, it was learned from Contractbook email, and no one updated Kommo.
- After: Won -> contract with correct data sent in 10 seconds. On signing — automatic stage change in Kommo. CSM sees status without switching systems.
- Additionally:
contract.expiredwithout signature after 7 days -> task for manager -> 40% of unsigned contracts were eventually signed after the reminder.
Who should use this
- B2B with enterprise contracts that need negotiation (redlines, versions)
- Sales with custom terms — not a standard form contract, but an individual agreement
- Teams where contract version history and change audit matter
- 20+ new clients per month — at lower volumes manual work is still manageable
Frequently asked questions
Contractbook vs PandaDoc for Kommo integration?
PandaDoc is better for sales documents: proposals, commercial offers, price lists. Contractbook is for legal contracts with version history, AI risk analysis, and renewal management. If both scenarios are needed — it is common to use PandaDoc at the proposal stage and Contractbook at the contract stage.
How do you pass custom terms (not from the template)?
Contractbook allows editing the draft via API before sending. PATCH /drafts/{draft_id} with content in Contractbook JSON format updates the text. For typical custom clauses — add a separate template for each scenario, with a plan -> template_id mapping in code.
How do you track contract status if the webhook did not arrive?
Periodic polling: GET /drafts/{draft_id} -> status field. Statuses: draft, sent, signed, declined, expired. A cron job runs every hour to check all deals with a contractbook_draft_id and sent status older than N days.
Does Contractbook support multi-party signing?
Yes. The signatories array can include multiple recipients with different roles (client, witness, internal). An internal signature from the company CEO plus the client signature are configured in a single request.
Summary
- Contractbook: API key in
Authorization: ApiKeyheader, base URLhttps://api.contractbook.com/v1 - Create draft:
POST /draftswith template_id and variables from Kommo fields - Send for signature:
POST /drafts/{draft_id}/send - Webhook verification: HMAC-SHA256 via
X-Contractbook-Signature - Key events:
contract.signed,contract.declined,contract.expired - Store
contractbook_draft_idin deal custom field for reverse lookup
If you use Contractbook and Kommo and want to automate contract sending on deal close — describe your template structure. Exceltic.dev will configure the variable mapping and event handling.