Kommo + Docuseal: automatic document signing via open-source platform
Docuseal is an open-source electronic signature platform: self-hosted or cloud, without vendor lock-in and with a full REST API. For teams that want e-sign without $25+/user/month for DocuSign or Adobe Sign, Docuseal is a functional alternative with complete API access. Without the Kommo integration, a manager creates a submission manually after Won. With the integration, Won automatically sends a contract from a template populated with deal data — the client receives a signing link within a minute.
Open-source vs SaaS for e-sign
Docuseal self-hosted (Docker) provides: — Full data control — all documents on your own server — Unlimited documents without plan limits — EU GDPR compliance without sending data to US SaaS — One-time hosting costs instead of $25–40/user/month
Docuseal cloud (docuseal.co) — the same API, without infrastructure management. More convenient for small teams.
Both options use the same REST API. For custom Kommo integration this means: one codebase, easy to switch between self-hosted and cloud.
What gets synchronized
Kommo -> Docuseal: — Won -> create submission from template with deal fields (name, email, amount, plan) — Won -> set signing order (client -> internal signature) — Automatically send email with signing link
Docuseal -> Kommo:
— submission.completed -> Note: “Docuseal: document signed by all parties” + stage change
— submission.declined -> Note + task: “Client declined signing”
— submission.expired -> Note + task: “Signing period expired”
— form.completed (one signer completed) -> Note: “{name} signed”
Architecture
Kommo Webhook: deal moved to Won
↓ Backend
1. GET /api/v4/leads/{id} + contacts
-> email, name, plan, amount
2. Docuseal API: POST /api/submissions
-> template_id, submitters with deal fields
3. Kommo: PATCH /leads/{id}
-> docuseal_submission_id = submission.id
4. Kommo: POST /leads/{id}/notes
-> "Docuseal: contract sent for signature {email}"
Docuseal Webhook: submission.completed
↓ Backend
1. Verify X-Docuseal-Signature
2. Find deal by docuseal_submission_id
3. Kommo: PATCH /leads/{id} -> stage change to "Contract signed"
4. Kommo: POST /notes -> "Docuseal: all signatures collected"
Docuseal API: key requests
Base URL (cloud): https://api.docuseal.co.
Base URL (self-hosted): https://your-docuseal-domain.com.
Authentication: X-Auth-Token: {api_key} (from account settings).
Create a submission from a template:
import requests
DOCUSEAL_API_KEY = "your_api_key"
DOCUSEAL_BASE_URL = "https://api.docuseal.co" # or self-hosted URL
headers = {
"X-Auth-Token": DOCUSEAL_API_KEY,
"Content-Type": "application/json"
}
def create_submission(template_id: int, submitters: list,
send_email: bool = True) -> dict:
resp = requests.post(
f"{DOCUSEAL_BASE_URL}/api/submissions",
headers=headers,
json={
"template_id": template_id,
"send_email": send_email,
"submitters": submitters
}
)
resp.raise_for_status()
return resp.json()
def on_deal_won(lead: dict, contact: dict):
email = get_contact_email(contact)
name = contact["name"]
plan = get_custom_field(lead, PLAN_FIELD_ID) or "Growth"
amount = lead.get("price", 0)
# submitters - order: client first, then internal signature
submitters = [
{
"email": email,
"name": name,
"role": "Client",
"values": {
"client_name": name,
"client_email": email,
"plan": plan,
"contract_amount": str(amount),
"contract_date": datetime.now().strftime("%d.%m.%Y"),
}
},
{
"email": INTERNAL_SIGNER_EMAIL,
"name": INTERNAL_SIGNER_NAME,
"role": "Company",
}
]
submission = create_submission(DOCUSEAL_TEMPLATE_ID, submitters)
submission_id = submission[0]["submission_id"] # returns list of submitters
update_kommo_deal(lead["id"], {"docuseal_submission_id": str(submission_id)})
create_kommo_note(lead["id"],
f"Docuseal: contract #{submission_id} sent for signature -> {email}")
Get submission status:
def get_submission_status(submission_id: int) -> dict:
resp = requests.get(
f"{DOCUSEAL_BASE_URL}/api/submissions/{submission_id}",
headers=headers
)
resp.raise_for_status()
return resp.json()
Handling Docuseal Webhooks:
import hmac, hashlib
from flask import Flask, request, abort
app = Flask(__name__)
DOCUSEAL_WEBHOOK_SECRET = "your_webhook_secret"
def verify_signature(payload: bytes, sig_header: str) -> bool:
expected = hmac.new(
DOCUSEAL_WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, sig_header)
@app.route("/webhooks/docuseal", methods=["POST"])
def docuseal_webhook():
sig = request.headers.get("X-Docuseal-Signature", "")
if not verify_signature(request.data, sig):
abort(403)
payload = request.json
event = payload.get("event_type")
submission_id = str(payload.get("data", {}).get("submission_id", ""))
deal_id = find_deal_by_field("docuseal_submission_id", submission_id)
if not deal_id:
return "", 200
if event == "submission.completed":
update_kommo_deal(deal_id, {"stage_id": STAGE_CONTRACT_SIGNED})
create_kommo_note(deal_id,
"Docuseal: all signatures collected - document finalized")
elif event == "submission.declined":
create_kommo_note(deal_id, "Docuseal: client declined signing")
create_kommo_task(deal_id, "Discuss terms - Docuseal recorded a refusal")
elif event == "submission.expired":
create_kommo_note(deal_id, "Docuseal: signing period expired")
create_kommo_task(deal_id, "Send a new signing link")
elif event == "form.completed":
submitter_name = payload.get("data", {}).get("submitter", {}).get("name", "")
create_kommo_note(deal_id, f"Docuseal: {submitter_name} signed")
return "", 200
Webhook setup in Docuseal: Settings -> Webhooks -> Add Webhook. Specify URL and secret. Events: submission.completed, submission.declined, submission.expired, form.completed.
Self-hosted: setup in 5 minutes
# docker-compose.yml
version: '3'
services:
docuseal:
image: docuseal/docuseal:latest
ports:
- "3000:3000"
volumes:
- ./data:/data
environment:
- SECRET_KEY_BASE=your_secret_key
- DATABASE_URL=sqlite3:///data/docuseal.sqlite3
docker compose up -d -> Docuseal available on port 3000. For production: Nginx reverse proxy + SSL + PostgreSQL instead of SQLite.
Real case
Law firm (EU, 50–70 contracts per month, Kommo + Docuseal self-hosted):
- Before: DocuSign $40/user/month × 8 people = $320/month. Templates were configured manually, data from Kommo was copied. Sending delay 30–90 minutes.
- After: Docuseal self-hosted on VPS at €20/month. Won -> contract with populated data in 8 seconds. Saving €3,620/year.
- Additionally: EU GDPR — all documents on their own server, not transferred to US SaaS. A strong argument when working with EU clients.
Who should use this
- Teams with GDPR requirements for document storage (legal, medical, financial)
- Those who want to control costs — self-hosted eliminates the per-user-per-month fee
- Developers who need an open API without proprietary limitations
- 20+ contracts per month — at lower volumes manual work is still justified
Frequently asked questions
Docuseal vs DocuSign: is it realistic for enterprise?
Docuseal covers 80% of enterprise use cases: templates, signing order, audit log, signed PDF download. What is missing: advanced workflow (conditional routing), native Salesforce integration, enterprise SSO (on roadmap). For teams up to 50 people — sufficient.
What template formats does Docuseal support?
PDF with fillable fields. Templates can be created directly in the Docuseal UI (drag-and-drop fields onto a PDF). The API takes template_id — the template ID from Docuseal. One template can be used multiple times with different data via the API.
Is Docuseal legally valid in the EU?
Yes. Signatures via Docuseal comply with eIDAS (EU Electronic Signatures Regulation) as a Simple Electronic Signature (SES). For a Qualified Electronic Signature (QES) you need a separate identity provider — Docuseal does not generate one.
How do you upload templates via API?
POST /api/templates with multipart/form-data — upload a PDF file. The response contains the template id for use in subsequent submissions. Templates can also be created via UI — the API uses the same ID.
Summary
- Docuseal:
X-Auth-Tokenheader,https://api.docuseal.co(cloud) or self-hosted URL - Create submission:
POST /api/submissionswithtemplate_idand array ofsubmitters submitters[].values— auto-populate template fields with Kommo data- Webhook: HMAC-SHA256 via
X-Docuseal-Signature - Self-hosted: Docker, data on your server, EU GDPR without issues
- Key events:
submission.completed,submission.declined,form.completed
If you want to automate contract signing from Kommo via Docuseal — describe your template structure and signing order. Exceltic.dev will configure the integration.