Kommo + Oneflow: Automatic Contract Sending from the Sales Pipeline
Oneflow is a Swedish contract lifecycle management (CLM) platform: creation, negotiation, electronic signature, and storage. Unlike PandaDoc or DocuSign, Oneflow specialises in interactive HTML contracts (not PDF) — the client can edit specific fields directly in the browser before signing. GDPR-compliant, data stored in the EU. Without Kommo integration, a manager creates contracts manually. With the integration, Won -> contract populated with deal data is sent to the client in seconds.
Oneflow vs PandaDoc vs Docuseal for EU Teams
| Platform | Format | Data Storage | EU Focus | API |
|---|---|---|---|---|
| Oneflow | HTML contracts | Sweden / EU | Yes, Swedish vendor | REST + webhooks |
| PandaDoc | PDF + rich content | US (AWS) | Partial | REST |
| Docuseal | Self-hosted or EU cloud | Yes (self-hosted) | REST | |
| DocuSign | US | No EU cloud | REST |
Oneflow is chosen by EU teams with data residency requirements and where contracts are not just a signature form but a negotiation tool (built-in chat, redlining, versioning).
What Gets Synchronised
Kommo -> Oneflow: — Won -> create a contract from a template with deal data (name, email, company, amount, plan) — Won -> set participants (client -> internal signatory) — Won -> send contract for signing
Oneflow -> Kommo:
— contract.signed (all parties signed) -> Note + stage change to “Contract Signed”
— contract.participant_signed (one participant signed) -> Note
— contract.declined -> Note + task: “Client declined the contract”
— contract.expired -> Note + task: “Signing deadline expired”
Oneflow API: Key Requests
Base URL: https://api.oneflow.com/v1.
Authentication: x-oneflow-api-token: {api_token} (personal token from settings).
Additionally: x-oneflow-user-email: {email} — the user on whose behalf we act.
import requests
ONEFLOW_TOKEN = "your_api_token"
ONEFLOW_USER_EMAIL = "sales@yourcompany.com"
ONEFLOW_BASE_URL = "https://api.oneflow.com/v1"
HEADERS = {
"x-oneflow-api-token": ONEFLOW_TOKEN,
"x-oneflow-user-email": ONEFLOW_USER_EMAIL,
"Content-Type": "application/json",
}
def get_templates(workspace_id: str) -> list:
resp = requests.get(
f"{ONEFLOW_BASE_URL}/templates",
headers=HEADERS,
params={"workspace_id": workspace_id}
)
resp.raise_for_status()
return resp.json().get("data", [])
def create_contract(template_id: str, workspace_id: str,
name: str, parties: list) -> dict:
# parties: list of company/individual participants with roles
payload = {
"template_id": template_id,
"workspace_id": workspace_id,
"name": name,
"parties": parties,
}
resp = requests.post(
f"{ONEFLOW_BASE_URL}/contracts",
headers=HEADERS,
json=payload
)
resp.raise_for_status()
return resp.json()
def update_contract_data_fields(contract_id: str, data_fields: list) -> None:
# Fill in custom contract fields (plan, amount, date)
for field in data_fields:
resp = requests.patch(
f"{ONEFLOW_BASE_URL}/contracts/{contract_id}/data_fields/{field['id']}",
headers=HEADERS,
json={"value": field["value"]}
)
resp.raise_for_status()
def publish_contract(contract_id: str) -> dict:
# Send contract to participants for signing
resp = requests.post(
f"{ONEFLOW_BASE_URL}/contracts/{contract_id}/publish",
headers=HEADERS,
json={}
)
resp.raise_for_status()
return resp.json()
def on_deal_won(lead: dict, contact: dict):
email = get_contact_email(contact)
name = contact["name"]
company = get_custom_field(lead, COMPANY_FIELD_ID) or name
plan = get_custom_field(lead, PLAN_FIELD_ID) or "Growth"
amount = lead.get("price", 0)
from datetime import datetime
today = datetime.now().strftime("%d.%m.%Y")
# Participant structure: client + our company
parties = [
{
"type": "company",
"name": company,
"participants": [{
"name": name,
"email": email,
"signatory": True,
}]
},
{
"type": "company",
"name": "Your Company Name",
"participants": [{
"name": INTERNAL_SIGNER_NAME,
"email": INTERNAL_SIGNER_EMAIL,
"signatory": True,
}]
}
]
contract = create_contract(
template_id=ONEFLOW_TEMPLATE_ID,
workspace_id=ONEFLOW_WORKSPACE_ID,
name=f"Agreement with {company} - {plan}",
parties=parties,
)
contract_id = contract["id"]
# Fill template fields (get field IDs from Oneflow template editor)
data_fields = [
{"id": FIELD_PLAN_ID, "value": plan},
{"id": FIELD_AMOUNT_ID, "value": str(amount)},
{"id": FIELD_DATE_ID, "value": today},
{"id": FIELD_COMPANY_ID, "value": company},
]
update_contract_data_fields(contract_id, data_fields)
# Publish (send to participants)
publish_contract(contract_id)
update_kommo_deal(lead["id"], {"oneflow_contract_id": str(contract_id)})
create_kommo_note(lead["id"],
f"Oneflow: contract #{contract_id} sent for signing -> {email}")
Handling Oneflow Webhook:
from flask import Flask, request
app = Flask(__name__)
@app.route("/webhooks/oneflow", methods=["POST"])
def oneflow_webhook():
payload = request.json
event_type = payload.get("event_type")
contract_id = str(payload.get("contract_id", ""))
deal_id = find_deal_by_field("oneflow_contract_id", contract_id)
if not deal_id:
return "", 200
if event_type == "contract.signed":
update_kommo_deal(deal_id, {"stage_id": STAGE_CONTRACT_SIGNED})
create_kommo_note(deal_id, "Oneflow: all parties have signed the contract")
elif event_type == "contract.participant_signed":
signer = payload.get("participant", {}).get("name", "")
create_kommo_note(deal_id, f"Oneflow: {signer} signed the contract")
elif event_type == "contract.declined":
participant = payload.get("participant", {}).get("name", "")
create_kommo_note(deal_id,
f"Oneflow: {participant} declined the contract")
create_kommo_task(deal_id,
"Clarify objections - contract declined in Oneflow")
elif event_type == "contract.expired":
create_kommo_note(deal_id, "Oneflow: contract signing deadline expired")
create_kommo_task(deal_id, "Send a new version of the contract")
return "", 200
Setting Up Webhooks in Oneflow: Settings -> Integrations -> Webhooks -> Add webhook. Specify the URL and select the events.
Interactive HTML Contracts: Oneflow’s Key Differentiator
In Oneflow, a contract is not a PDF but an HTML document. The client can: — Comment on specific clauses directly in the browser — Edit agreed fields (if permitted by the template) — View the change history
For lengthy B2B negotiations this is critical: instead of sending 5 PDF versions — one live document with versioning. Once negotiations are complete — one “Sign” button.
Real-World Case
IT consulting firm (Sweden, 20–30 contracts per month, Kommo + Oneflow):
- Before: PandaDoc + manual creation. Contracts were sent to clients 2–3 hours after Won. 40% of contracts came back with revision requests — leading to email threads with PDF versions.
- After: Won -> contract via Oneflow in 30 seconds. The client edits disputed clauses directly in the browser — email version chains disappeared. Average time from Won to signing: 1.3 days (was 4.7 days).
- Additionally: EU GDPR — all contracts stored on servers in Sweden. A strong argument when working with EU clients who have DPA requirements.
Who This Is Relevant For
- EU companies with GDPR requirements for contract data storage
- B2B with a negotiation process: lengthy contracts, redlining, multiple clauses
- Agencies and consultancies with 10+ contracts per month
- Teams tired of “send PDF, get revisions, send again”
Frequently Asked Questions
Is Oneflow legally valid in the EU?
Yes. Signatures via Oneflow comply with eIDAS as a Simple Electronic Signature (SES). For a Qualified Electronic Signature (QES) — an additional ID provider is required. For most B2B contracts, SES is sufficient.
Where can I find the Template ID and Field ID in Oneflow?
Template ID: Oneflow Dashboard -> Templates -> select the template -> URL (/templates/{id}). Field ID: open the template in the editor -> select the data field -> the ID is visible in the right panel. Or via GET /templates/{id} — it returns all fields with their IDs.
Does Oneflow support multiple signatories and signing order?
Yes. In participants you can specify signatory_order for each participant — the contract is sent to the next person only after the previous one has signed. Parallel signing (without order) is the default.
How do I get the signed PDF from Oneflow via API?
GET /contracts/{id}/pdf -> returns the PDF with signatures. It is convenient to automatically save this to cloud storage or attach it to the deal in Kommo as a file.
Summary
- Oneflow API:
x-oneflow-api-token+x-oneflow-user-emailin headers - Create contract:
POST /contracts-> update data fields ->POST /contracts/{id}/publish - Webhook: events
contract.signed,contract.participant_signed,contract.declined - HTML contracts: negotiation, redlining, version history — an advantage over PDF-based tools
- EU data residency: data in Sweden, GDPR-compliant out of the box
If you use Oneflow and Kommo and want to automate contract creation upon Won — describe your template structure and signatory order. Exceltic.dev will configure the integration.