Proposify is a platform for creating interactive sales proposals with electronic signatures, view tracking, and a REST API. Without Kommo integration, after a demo the manager manually opens Proposify, copies the name, company, and amount from the CRM, and selects a template. With the integration, a button click in the deal card creates a proposal with pre-filled variables and sends it to the client — and upon signing, it automatically moves the deal to Won.
Why Native Tools Are Not Enough
Proposify integrates natively with HubSpot, Pipedrive, Salesforce, and Zoho through built-in connectors. Kommo is not on this list. Zapier offers a basic “proposal signed” trigger but cannot pass custom Kommo fields as proposal variables and does not support two-way status synchronisation.
The problem runs deeper architecturally: variables in a Proposify template ({{client_name}}, {{deal_amount}}, {{plan}}) must be populated at document creation time — from the specific Kommo deal’s fields, including custom fields. This requires a direct call to the Proposify API with parameters from the Kommo API. No no-code connector handles this correctly.
Proposify is an interactive proposal service where the client can review the proposal online, leave a comment, and sign electronically. Every client action (opened, viewed section, signed) generates a webhook event.
What the Kommo + Proposify Combination Delivers
Without integration: — After the demo, the manager manually opens Proposify and copies data from Kommo — Average time from demo to proposal send: 20–40 minutes — When signed, Proposify does not notify Kommo — the deal is updated manually or forgotten — The Sales Director cannot see in the CRM when the client opened and read the proposal
With integration:
— Button in the deal card -> proposal created and sent in 30 seconds
— Contact name, company, plan, and amount are populated automatically from deal fields
— proposal.viewed -> Note in Kommo: “Client opened the proposal — {date}”
— proposal.signed -> deal moves to Won, task for accountant to raise invoice
— proposal.declined -> manager task + custom field with reason
What Gets Synchronised
Kommo -> Proposify:
— Contact name and email -> variables {{client_name}}, {{client_email}}
— Company name -> {{company_name}}
— Deal amount -> {{deal_amount}}
— Plan from custom field -> {{plan}}, {{billing_period}}
— Deal ID -> custom attribute for reverse traceability
Proposify -> Kommo:
— proposal.viewed -> Note with viewing time
— proposal.signed -> Won + Note “Proposal signed” + invoice task
— proposal.declined -> custom field proposal_status = declined + manager task
— Proposal URL -> custom field in deal (for quick access)
Architecture
Kommo: manager clicks "Create Proposal" button (custom widget button)
↓ Backend
1. GET /api/v4/leads/{id} + contacts
-> name, email, company, plan, amount
2. Proposify: POST /proposals
-> template_id + variables (client_name, deal_amount, plan...)
-> get proposal_id + proposal_url
3. Kommo: PATCH /leads/{id}
-> custom field proposal_url = proposal link
-> custom field proposal_status = sent
4. Kommo: POST /leads/{id}/notes
-> "Proposal sent to client: {proposal_url}"
Proposify Webhook: proposal.viewed
↓ Backend
1. From payload: proposal_id, viewed_at, contact_email
2. Find deal_id by proposal_id (saved in step 3 above)
3. Kommo: POST /leads/{deal_id}/notes
-> "Client opened the proposal: {viewed_at}"
Proposify Webhook: proposal.signed
↓ Backend
1. From payload: proposal_id, signed_at, signer_name
2. Find deal_id
3. Kommo: PATCH /leads/{deal_id}
-> pipeline_id + status_id -> Won
-> proposal_status = signed
4. Kommo: POST /tasks
-> task: "Raise invoice - proposal signed {signed_at}"
Proposify REST API: Key Requests
Base URL: https://app.proposify.com/api/v2/. Authentication: Bearer token in the Authorization: Bearer {API_KEY} header.
Create a proposal from a template:
import requests
PROPOSIFY_API_KEY = 'your_api_key'
PROPOSIFY_BASE = 'https://app.proposify.com/api/v2'
def create_proposal(template_id: str, variables: dict,
recipient_email: str, recipient_name: str) -> dict:
headers = {
'Authorization': f'Bearer {PROPOSIFY_API_KEY}',
'Content-Type': 'application/json'
}
payload = {
'document_template_id': template_id,
'recipients': [{
'email': recipient_email,
'name': recipient_name,
'role': 'client'
}],
'variables': variables # {'{client_name}': 'John Smith', '{plan}': 'Pro'}
}
resp = requests.post(
f'{PROPOSIFY_BASE}/proposals',
json=payload,
headers=headers
)
resp.raise_for_status()
data = resp.json()
return {
'proposal_id': data['proposal']['id'],
'proposal_url': data['proposal']['url']
}
Handling Proposify webhook:
import hmac
import hashlib
from flask import Flask, request
app = Flask(__name__)
WEBHOOK_SECRET = 'your_webhook_secret'
def verify_proposify_signature(payload_bytes: bytes, signature: str) -> bool:
expected = hmac.new(
WEBHOOK_SECRET.encode(),
payload_bytes,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route('/webhooks/proposify', methods=['POST'])
def proposify_webhook():
signature = request.headers.get('X-Proposify-Signature', '')
if not verify_proposify_signature(request.data, signature):
return '', 401
payload = request.json
event_type = payload.get('event') # 'proposal.signed', 'proposal.viewed', etc.
proposal = payload.get('proposal', {})
proposal_id = proposal.get('id')
deal_id = get_deal_id_by_proposal(proposal_id) # from your DB or custom field
if not deal_id:
return '', 200 # proposal not from Kommo - ignore
if event_type == 'proposal.signed':
signed_at = proposal.get('signed_at')
# Move deal to Won
move_kommo_deal_to_won(deal_id)
# Create Note
create_kommo_note(deal_id,
f'Proposify proposal signed {signed_at}. '
f'Signer: {proposal.get("signer_name")}')
# Invoice task
create_kommo_task(deal_id, 'Proposal signed - raise invoice')
elif event_type == 'proposal.viewed':
viewed_at = proposal.get('viewed_at')
create_kommo_note(deal_id,
f'Client opened the Proposify proposal: {viewed_at}')
elif event_type == 'proposal.declined':
reason = proposal.get('decline_reason', 'not specified')
update_kommo_deal(deal_id, {'proposal_status': 'declined'})
create_kommo_task(deal_id,
f'Client declined the proposal. Reason: {reason}')
return '', 200
Verification: Proposify signs every webhook via HMAC-SHA256, passing the signature in the X-Proposify-Signature header. Always verify the signature before processing — this protects against forged requests.
Idempotency: Proposify may retry a webhook on timeout. Save processed proposal_id + event_type combinations and skip duplicates.
Kommo -> Proposify Variable Mapping
def build_proposify_variables(lead: dict, contact: dict) -> dict:
custom_fields = {cf['field_id']: cf.get('values', [{}])[0].get('value')
for cf in lead.get('custom_fields_values', [])}
PLAN_FIELD_ID = 123456 # ID of "Plan" custom field
PERIOD_FIELD_ID = 123457 # ID of "Billing Period" field
return {
'{client_name}': contact.get('name', ''),
'{client_email}': next(
(e['value'] for e in contact.get('custom_fields_values', [])
if e.get('field_code') == 'EMAIL'), ''),
'{company_name}': contact.get('company', {}).get('name', ''),
'{deal_amount}': str(lead.get('price', 0)),
'{plan}': custom_fields.get(PLAN_FIELD_ID, 'Pro'),
'{billing_period}': custom_fields.get(PERIOD_FIELD_ID, 'monthly'),
'{deal_id}': str(lead.get('id'))
}
Variables in Proposify templates are created in the editor: insert {{name}} in the text — they will then be available via the API. Variable names must match the keys in the variables dictionary.
Real-World Case
B2B SaaS (DACH region, 25–30 new qualified leads per month, Kommo + Proposify + Stripe):
- Before: after a demo, a manager spent 30–40 minutes preparing a proposal. Errors in amounts and plans due to manual copying. When signed, Kommo was updated with a 1–2 day delay.
- After: “Send proposal” button in the deal card -> proposal with the client in 30 seconds. Data is populated from deal fields — no errors. Upon signing, Kommo automatically moves to Won and the accountant receives a task to raise an invoice.
- Additionally: the Sales Director can see in the deal timeline when the client opened the proposal — and how many times. This changes the follow-up approach: calling one hour after the first viewing is more effective than calling the next day.
A similar pattern applies to PandaDoc and DocuSign — the same webhook + template variable principles apply.
Who This Is Relevant For
- B2B companies sending 15+ proposals per month
- Those using Proposify as their primary tool for sales proposals
- Companies that want to see proposal status directly in Kommo without switching to Proposify
- Workflows where: demo -> proposal -> signing -> Won must be automatic
Frequently Asked Questions
Is the Proposify API public or enterprise-only?
The Proposify public API is available to users on the Business plan and above. An API Key is created in account settings: Settings -> Integrations -> API. Authentication via Bearer token in the request header.
How do I set up a webhook in Proposify?
Settings -> Integrations -> Webhooks -> Add Webhook URL. Select events: proposal.viewed, proposal.signed, proposal.declined. Proposify signs the payload via HMAC-SHA256 — the secret key is set there and used to verify requests on your server.
Can proposals be created automatically without a button click — on stage change?
Yes — via a Kommo webhook on status change. When a deal moves to the “Commercial Proposal” stage, the backend automatically creates and sends the proposal. This works when the template is standard and customisation is minimal.
What if the client wants to change the terms after sending?
Proposify allows editing a proposal after it has been sent — via the API PATCH /proposals/{id}/variables. It is more convenient to implement an “Update Proposal” button in Kommo that updates variables without recreating the document. Version history is preserved.
How do I link proposal_id to deal_id for reverse traceability?
Two options: pass deal_id as a variable in the proposal ({deal_id}) — it will be in the webhook payload. Or store a {proposal_id -> deal_id} mapping table in your database. The first option is simpler at low volumes.
Summary
- Proposify REST API: Bearer token, Base URL
https://app.proposify.com/api/v2/ - Create proposal:
POST /proposalswithdocument_template_idandvariablesfrom Kommo fields - Webhook events:
proposal.signed-> Won + task,proposal.viewed-> Note,proposal.declined-> task with reason - Webhook verification: HMAC-SHA256,
X-Proposify-Signatureheader - Idempotency: save
proposal_id + event_typeto protect against duplicates - Typical development time: 1–2 weeks
If you use Proposify and Kommo and want to automate sending proposals from the pipeline — describe your template structure and custom deal fields. Exceltic.dev will configure the variable mapping and signing event handling.