Why the native integration doesn’t work
Scrive is a Swedish e-signature platform widely used in the Nordics (Sweden, Norway, Denmark, Finland) and the DACH region. Unlike DocuSign and Adobe Sign, Scrive was designed from the ground up to meet European eIDAS regulatory requirements, making it the preferred choice for EU companies where the legal validity of an electronic signature in European courts matters.
There is no ready-made Scrive + Kommo integration. Scrive is not listed in the Kommo marketplace. A typical workflow without integration looks like this: a manager receives a notification “deal moved to Contract stage,” manually opens Scrive, creates a document, uploads a template, enters the contact’s email, sends it. After signing, they return to Kommo, manually change the stage, and attach the PDF. Each cycle takes 10-15 minutes of manual work.
For companies with custom Kommo integrations, this is a solvable problem through the REST APIs of both systems.
What gets built - solution architecture
Bidirectional flow via webhook:
Kommo: stage change to "Contract"
--> Webhook --> Python service
--> Scrive API: create document, upload PDF template
--> Scrive API: send signing invitations
Scrive: document.signed
--> Webhook --> Python service
--> Kommo API: update deal stage
--> Kommo API: attach signed PDF
Technical details
Scrive API Auth. Bearer token. Obtained via POST /api/v2/oauth/token with client_id and client_secret (from Scrive Admin Panel). The token is long-lived, but a refresh on 401 is recommended.
Key Scrive API v2 endpoints:
POST /api/v2/documents- create a documentPOST /api/v2/documents/{id}/files/main- upload PDFPOST /api/v2/documents/{id}/start- start the signing process (send invitations)GET /api/v2/documents/{id}/files/sealed- download signed PDFPOST /api/v2/hooks- create webhook for document events
Scrive document structure. When creating a document via API, a JSON is passed describing the participants (parties) and fields to fill. Each party has a role: signing_party (signer) or viewer. Document fields (signatory fields) can be signature, text, checkbox, date - they are anchored to coordinates on the PDF page.
Kommo Webhooks. When a deal transitions to the “Contract” stage, Kommo sends a webhook with leads[status][0][id] (deal ID) and leads[status][0][status_id] (new status). Setup: Kommo Admin -> Webhooks.
Step-by-step implementation
Step 1. Configure Kommo Webhook
In Kommo Admin Panel -> Integrations -> Webhooks, add your service URL. Select the “Deal stage change” event. Record the secret key for verification.
Step 2. Create document and send for signing
import os
import requests
import json
from flask import Flask, request
app = Flask(__name__)
SCRIVE_BASE = "https://api.scrive.com"
SCRIVE_TOKEN = os.environ["SCRIVE_BEARER_TOKEN"]
KOMMO_DOMAIN = os.environ["KOMMO_DOMAIN"]
KOMMO_TOKEN = os.environ["KOMMO_ACCESS_TOKEN"]
# ID of the "Contract" stage in your Kommo pipeline
CONTRACT_STAGE_ID = int(os.environ.get("KOMMO_CONTRACT_STAGE_ID", 0))
# ID of the "Contract Signed" stage
SIGNED_STAGE_ID = int(os.environ.get("KOMMO_SIGNED_STAGE_ID", 0))
# Path to the PDF contract template
CONTRACT_TEMPLATE_PATH = os.environ.get("CONTRACT_TEMPLATE_PATH", "contract_template.pdf")
def get_lead_details(lead_id: int) -> dict:
"""Get deal data from Kommo."""
url = f"https://{KOMMO_DOMAIN}/api/v4/leads/{lead_id}"
params = {"with": "contacts"}
headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
r = requests.get(url, params=params, headers=headers, timeout=10)
r.raise_for_status()
return r.json()
def get_contact_details(contact_id: int) -> dict:
"""Get contact data."""
url = f"https://{KOMMO_DOMAIN}/api/v4/contacts/{contact_id}"
headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
r = requests.get(url, headers=headers, timeout=10)
r.raise_for_status()
return r.json()
def create_scrive_document(title: str, signer_email: str, signer_name: str) -> dict:
"""Create a document in Scrive and upload the PDF template."""
scrive_headers = {
"Authorization": f"Bearer {SCRIVE_TOKEN}",
"Content-Type": "application/json",
}
# Create document
doc_payload = {
"title": title,
"parties": [
{
"role": "signing_party",
"is_author": False,
"delivery_method": "email",
"fields": [
{"type": "email", "value": signer_email},
{"type": "name", "value": signer_name},
{
"type": "signature",
"name": "client_signature",
"placements": [
{"xrel": 0.1, "yrel": 0.85, "wrel": 0.3, "hrel": 0.05, "page": 1}
]
}
]
},
{
"role": "signing_party",
"is_author": True,
"delivery_method": "email",
}
],
"lang": "en",
"is_template": False,
}
r = requests.post(
f"{SCRIVE_BASE}/api/v2/documents",
json=doc_payload,
headers=scrive_headers,
timeout=30,
)
r.raise_for_status()
doc = r.json()
doc_id = doc["id"]
# Upload PDF template
with open(CONTRACT_TEMPLATE_PATH, "rb") as pdf_file:
upload_r = requests.post(
f"{SCRIVE_BASE}/api/v2/documents/{doc_id}/files/main",
files={"file": ("contract.pdf", pdf_file, "application/pdf")},
headers={"Authorization": f"Bearer {SCRIVE_TOKEN}"},
timeout=60,
)
upload_r.raise_for_status()
# Start signing process
start_r = requests.post(
f"{SCRIVE_BASE}/api/v2/documents/{doc_id}/start",
headers=scrive_headers,
timeout=30,
)
start_r.raise_for_status()
return doc
def save_scrive_doc_to_kommo(lead_id: int, scrive_doc_id: str):
"""Save Scrive document ID to a custom deal field in Kommo."""
url = f"https://{KOMMO_DOMAIN}/api/v4/leads"
headers = {
"Authorization": f"Bearer {KOMMO_TOKEN}",
"Content-Type": "application/json",
}
SCRIVE_DOC_FIELD_ID = int(os.environ.get("KOMMO_SCRIVE_DOC_FIELD_ID", 0))
payload = [{
"id": lead_id,
"custom_fields_values": [
{"field_id": SCRIVE_DOC_FIELD_ID, "values": [{"value": scrive_doc_id}]}
]
}]
requests.patch(url, json=payload, headers=headers, timeout=10)
@app.route("/webhooks/kommo", methods=["POST"])
def kommo_webhook():
"""Handle Kommo webhook on stage change."""
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 lead_id and new_status_id == CONTRACT_STAGE_ID:
# Deal moved to "Contract" stage
lead = get_lead_details(lead_id)
contacts = lead.get("_embedded", {}).get("contacts", [])
if contacts:
contact_id = contacts[0]["id"]
contact = get_contact_details(contact_id)
# Find contact email
signer_email = ""
signer_name = contact.get("name", "")
for cf in contact.get("custom_fields_values", []) or []:
if cf.get("field_code") == "EMAIL":
signer_email = cf["values"][0]["value"]
break
if signer_email:
doc_title = f"Contract - {lead.get('name', 'Deal')} #{lead_id}"
doc = create_scrive_document(doc_title, signer_email, signer_name)
save_scrive_doc_to_kommo(lead_id, doc["id"])
return {"ok": True}
Step 3. Handle Scrive webhook (document.signed)
@app.route("/webhooks/scrive", methods=["POST"])
def scrive_webhook():
"""Handle Scrive notification about document signing."""
event = request.json
if event.get("event") != "document_signed":
return {"ok": True}
doc_id = event.get("document_id")
if not doc_id:
return {"ok": True}
# Find deal in Kommo by doc_id (custom field)
lead_id = find_lead_by_scrive_doc_id(doc_id)
if not lead_id:
return {"ok": True}
# Download signed PDF
scrive_headers = {"Authorization": f"Bearer {SCRIVE_TOKEN}"}
pdf_r = requests.get(
f"{SCRIVE_BASE}/api/v2/documents/{doc_id}/files/sealed",
headers=scrive_headers,
timeout=60,
)
if pdf_r.ok:
# Attach PDF to deal in Kommo
attach_pdf_to_lead(lead_id, pdf_r.content, f"contract_signed_{lead_id}.pdf")
# Change deal stage to "Contract Signed"
update_lead_stage(lead_id, SIGNED_STAGE_ID)
return {"ok": True}
def attach_pdf_to_lead(lead_id: int, pdf_bytes: bytes, filename: str):
"""Attach a PDF file to a deal in Kommo."""
url = f"https://{KOMMO_DOMAIN}/api/v4/leads/{lead_id}/files"
headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
files = {"file": (filename, pdf_bytes, "application/pdf")}
requests.post(url, headers=headers, files=files, timeout=30)
Real case with numbers
In a typical project for a B2B company in DACH with a 3-6 week sales cycle and 20-40 contracts per month, the Kommo + Scrive integration delivers the following:
Before the integration: a manager spent 12-18 minutes per cycle of “prepare contract -> send -> record signing.” With 30 contracts per month - 6-9 hours of manual work.
After the integration: moving a deal to the “Contract” stage automatically triggers sending to Scrive. After signing, the PDF is attached to the deal and the stage changes automatically. Manual effort is effectively zero.
An additional effect is status tracking. Kommo shows how many contracts are currently “awaiting signature” - a dedicated pipeline stage. Previously this information was scattered across email and Scrive.
Who this is for
The integration is relevant for companies that:
- Work with EU clients where legal validity under eIDAS matters
- Use Scrive because of its support for Advanced Electronic Signature (AES) and Qualified Electronic Signature (QES) for high-requirement contracts
- Have a dedicated “Contract” pipeline stage in Kommo and want to automate the transition
- Operate in the Nordics or DACH, where Scrive enjoys high trust from legal departments
If you work with other e-signature tools, the integration logic is analogous - only the API changes.
Frequently asked questions
Should the Scrive Bearer Token be stored encrypted? Absolutely. Use a secrets manager (AWS Secrets Manager, HashiCorp Vault) or at minimum environment variables without storing in code. The Scrive token provides full access to your documents.
How do I configure the webhook from Scrive?
In Scrive Admin Panel -> Integrations -> Webhooks. Specify your service URL and select the document_signed event. Scrive does not use HMAC for verification - check the source IP address (documented Scrive IPs) or restrict the endpoint via firewall.
What if the PDF template changes per deal (need to insert the client’s name)?
Use the reportlab or pypdf library to generate a PDF from a template with data substitution before uploading to Scrive. Or use the Scrive Templates mechanism, which allows setting placeholder fields directly in the API.
Can multiple signers be added?
Yes. In the parties array when creating a document, add multiple objects with role: signing_party. Scrive supports parallel and sequential signing - configured via the signing_order parameter.
If you need a Kommo + Scrive integration - describe your scenario to the Exceltic.dev team. We’ll work through the architecture in one meeting.