Kommo + Better Proposals: interactive proposals from the deal card with tracking

Better Proposals is a platform for creating interactive web-based proposals (not PDFs): the prospect opens a link in their browser, sees a beautifully formatted document, can sign electronically and pay directly on the page. Unlike PandaDoc or DocuSign, Better Proposals specializes specifically in the proposal stage of the pipeline — with detailed view tracking, heat maps, and A/B template tests. Without the Kommo integration: every proposal is created manually, view data does not reach the CRM, and the manager learns about signing from email.

Better Proposals vs PandaDoc vs Qwilr

ParameterBetter ProposalsPandaDocQwilr
FormatWeb pageWeb + PDFWeb page
View trackingDetailed (scroll, time per section)BasicBasic
Electronic signatureYesYes (eSign)Yes
Built-in paymentYes (Stripe)NoYes
Templates200+750+60+
Pricefrom $19/monthfrom $35/monthfrom $35/month

Better Proposals is chosen by agencies and consulting firms where the visual quality of a proposal affects the close rate, and tracking “when the client was reading” is critical for follow-up timing.

What gets synchronized

Kommo -> Better Proposals: — Won (or custom stage) -> create proposal from template, populate contact and deal data — Change of amount in Kommo -> update price in the proposal draft

Better Proposals -> Kommo:proposal.opened -> Note: “Proposal opened by client” + timestamp — proposal.signed -> Note: “Signed” + deal status or pipeline stage change — proposal.paid -> Note: “Payment received via proposal page” — proposal.expired -> Task: “Proposal expired — follow-up”

Better Proposals API: creating a proposal

Base URL: https://api.betterproposals.io/v1. Authentication: Authorization: Bearer {api_token} header (token from Better Proposals -> Settings -> Integrations -> API).

import requests

BP_TOKEN   = "your_bearer_token"
BP_BASE    = "https://api.betterproposals.io/v1"
BP_HEADERS = {
    "Authorization": f"Bearer {BP_TOKEN}",
    "Content-Type":  "application/json",
    "Accept":        "application/json",
}

def create_proposal(template_id: int, contact_name: str, contact_email: str,
                    company: str, deal_value: float, currency: str = "USD") -> dict:
    # template_id from Better Proposals -> Templates -> Edit -> URL
    payload = {
        "template_id": template_id,
        "name":        f"Proposal for {company}",
        "contacts": [
            {
                "name":    contact_name,
                "email":   contact_email,
                "company": company,
            }
        ],
        "variables": {
            "company_name": company,
            "deal_value":   f"{deal_value:,.2f} {currency}",
        },
        "expiry_date": None,
        "currency":    currency,
        "price":       deal_value,
    }
    resp = requests.post(f"{BP_BASE}/proposals", headers=BP_HEADERS, json=payload)
    resp.raise_for_status()
    return resp.json()

def get_proposal_link(proposal_id: int) -> str:
    resp = requests.get(f"{BP_BASE}/proposals/{proposal_id}", headers=BP_HEADERS)
    resp.raise_for_status()
    data = resp.json()
    return data.get("url", "")

# Template mapping: Kommo deal type -> Better Proposals template
TEMPLATE_MAP = {
    "consulting": 12345,
    "saas":       12346,
    "retainer":   12347,
}

def on_deal_reached_proposal_stage(lead: dict, contact: dict):
    # Called when deal moves to "Proposal" stage
    company    = get_custom_field(lead, COMPANY_FIELD_ID) or contact.get("name", "")
    email      = get_contact_email(contact)
    deal_type  = get_custom_field(lead, DEAL_TYPE_FIELD_ID) or "saas"
    template_id = TEMPLATE_MAP.get(deal_type.lower(), TEMPLATE_MAP["saas"])

    proposal   = create_proposal(
        template_id   = template_id,
        contact_name  = contact.get("name", ""),
        contact_email = email,
        company       = company,
        deal_value    = lead.get("price", 0),
    )
    proposal_id  = proposal.get("id")
    proposal_url = get_proposal_link(proposal_id)

    save_to_kommo_deal(lead["id"], {
        "bp_proposal_id":  proposal_id,
        "bp_proposal_url": proposal_url,
    })
    create_kommo_note(
        lead["id"],
        f"Better Proposals: proposal created -> {proposal_url}",
    )

Webhooks: Better Proposals -> Kommo

Better Proposals supports webhooks: Settings -> Webhooks -> Add Webhook. The payload is signed via HMAC-SHA256 with the X-BP-Signature header.

import hmac, hashlib

BP_WEBHOOK_SECRET = "your_webhook_secret"

@app.route("/webhooks/better-proposals", methods=["POST"])
def bp_webhook():
    sig = request.headers.get("X-BP-Signature", "")
    expected = hmac.new(
        BP_WEBHOOK_SECRET.encode(),
        request.data,
        hashlib.sha256,
    ).hexdigest()
    if not hmac.compare_digest(sig, expected):
        return "", 401

    payload     = request.json
    event       = payload.get("event")
    proposal_id = payload.get("proposal", {}).get("id")

    lead_id = find_kommo_deal_by_custom_field("bp_proposal_id", str(proposal_id))
    if not lead_id:
        return "", 200

    if event == "proposal.opened":
        opened_at = payload.get("proposal", {}).get("opened_at", "")
        create_kommo_note(lead_id, f"Better Proposals: proposal opened by client ({opened_at})")

    elif event == "proposal.signed":
        signed_at = payload.get("proposal", {}).get("signed_at", "")
        create_kommo_note(lead_id,
            f"Better Proposals: proposal signed ({signed_at}) - awaiting payment")
        move_kommo_deal_to_stage(lead_id, SIGNED_STAGE_ID)

    elif event == "proposal.paid":
        amount = payload.get("proposal", {}).get("price", 0)
        create_kommo_note(lead_id,
            f"Better Proposals: payment received - ${amount:.2f}")
        move_kommo_deal_to_stage(lead_id, WON_STAGE_ID)

    elif event == "proposal.expired":
        create_kommo_note(lead_id, "Better Proposals: proposal has expired")
        create_kommo_task(lead_id, "Better Proposals: follow-up - send updated proposal")

    return "", 200

View tracking: how to use the data

Better Proposals records: when each section was opened, how many seconds the client spent on each section, and which device was used. This data can be retrieved via the API:

def get_proposal_analytics(proposal_id: int) -> dict:
    resp = requests.get(
        f"{BP_BASE}/proposals/{proposal_id}/analytics",
        headers=BP_HEADERS,
    )
    resp.raise_for_status()
    return resp.json()

def sync_proposal_analytics_to_kommo(lead_id: int, proposal_id: int):
    analytics = get_proposal_analytics(proposal_id)
    views      = analytics.get("views", 0)
    avg_time   = analytics.get("average_time_spent", 0)
    note = (
        f"Better Proposals analytics: views {views}, "
        f"average time on proposal {avg_time} sec."
    )
    create_kommo_note(lead_id, note)

This data helps the manager choose the right moment for a follow-up: if the client spent 3 minutes on the pricing page — it is the right time to call.

Real case

Digital agency (EU, 25 people, Kommo + Better Proposals):

  • Before: the manager manually copied data from Kommo into Better Proposals. 15–20 minutes per proposal. Signing notifications came by email, which sometimes went unnoticed. The Proposal stage -> Won took 2–3 days.
  • After: moving to the “Proposal” stage in the pipeline -> proposal automatically created from template (based on deal type) -> link saved in Kommo. proposal.opened -> Note. proposal.signed -> automatic stage change to “Won”.
  • Result: time from “Proposal” to “Won” was reduced from 4.2 days to 1.8 days — managers saw in real time when clients were reviewing the proposal and called at the right moment.

Who should use this

  • Agencies (digital, consulting, marketing) where the proposal is a key pipeline stage
  • B2B with several standard proposal types (templates per service line)
  • Companies where follow-up timing matters — view tracking provides the right moment
  • Teams where the proposal stage takes more than 3 days — automation accelerates the cycle

Frequently asked questions

Does Better Proposals support variables for auto-populating deal data?

Yes. In a Better Proposals template you can create variables ({company_name}, {deal_value}, {manager_name}) — they are populated when the proposal is created via the API through the variables field. Standard contact fields (name, email, company) are auto-populated from the contacts object in the payload.

Can you update the price in a proposal that has already been sent?

Yes, via PATCH /proposals/{id} with a new price — but only if the proposal has not been signed. After signing, the proposal is locked. If the amount changed after sending — a new proposal is created (POST /proposals) with “Revised” in the name, and the old one is closed via DELETE /proposals/{id}.

How do you set up different templates for different deal types?

In Kommo, add a custom field “Service type” (select). When moving to the Proposal stage — the webhook passes the field value -> the Python handler looks up TEMPLATE_MAP -> selects the appropriate Better Proposals template_id. Templates are created in Better Proposals once by the team and then used automatically via the API.

Better Proposals vs Qwilr — what is the difference for Kommo integration?

The API level is similar: Bearer token, CRUD proposals, webhooks. The main difference: Better Proposals provides more detailed view analytics (section by section), Qwilr offers more flexible design. For Kommo integration the architecture is identical. The platform choice is a question of UX for managers and proposal design, not the technical side.

Summary

  • API: Bearer token, Authorization: Bearer {token}, base URL https://api.betterproposals.io/v1
  • Flow: “Proposal” stage in Kommo -> POST /proposals with template_id + contact data
  • Webhook: proposal.opened/signed/paid/expired -> Note/Task/stage change in Kommo
  • View analytics: GET /proposals/{id}/analytics -> Note with engagement data
  • The trigger does not have to be Won — any pipeline stage where proposal work begins will do

If you have an agency or B2B with a proposal stage in the pipeline and want to automate proposal creation from Kommo — describe your template structure and deal types. Exceltic.dev will set up the two-way integration with tracking.

More articles

All →