Kommo + Chargebee: automatic subscription creation from won deals

Kommo + Chargebee: automatic subscription creation from won deals

Chargebee is a subscription management platform for SaaS with a REST API on Basic Auth. Without the Kommo integration, a manager manually creates a customer in Chargebee after Won, selects a plan, starts a subscription — and forgets to update the CRM. With the integration, Won in Kommo triggers the entire chain automatically: a Customer is created in Chargebee, the subscription is activated, and payment status and cancellation events come back into the deal card.

What the Kommo + Chargebee combination delivers

Without integration: — Won in Kommo -> manager manually opens Chargebee, creates a customer, configures the plan — Average time from Won to subscription activation — 1–3 hours — When a subscription is cancelled in Chargebee, Kommo does not know — the deal stays as Won without an update — Billing and the manager work in different systems with no unified view

With integration: — Won -> Customer + Subscription in Chargebee within 3 minutes — payment_succeeded -> Note in Kommo: “Payment received, $X” — subscription_cancelled -> task for manager + field change in deal — invoice_generated -> invoice link in Kommo

What gets synchronized

Kommo -> Chargebee: — Contact name and email -> Customer in Chargebee — Plan from custom field -> Subscription plan ID — Deal amount -> billing amount (if non-standard plan) — Deal ID -> customer metadata (for reverse tracing)

Chargebee -> Kommo:payment_succeeded -> Note with amount and date — invoice_generated -> Note with invoice link — subscription_cancelled -> custom field subscription_status = cancelled + task for manager — subscription_renewed -> Note “Subscription renewed”

Architecture

Kommo Webhook: deal moved to Won
  ↓ Backend
  1. GET /api/v4/leads/{id} + contacts
     -> name, email, plan from custom fields
  2. Chargebee: POST /api/v2/customers
     -> first_name, last_name, email, cf_kommo_deal_id
  3. Chargebee: POST /api/v2/subscriptions
     -> customer_id + plan_id + billing_cycles
     -> receive subscription_id
  4. Kommo: PATCH /leads/{id}
     -> chargebee_customer_id, subscription_id, subscription_status = active

Chargebee Webhook: payment_succeeded
  ↓ Backend
  1. From payload: customer_id, amount, invoice_id
  2. Find deal_id by cf_kommo_deal_id in Customer
  3. Kommo: POST /leads/{deal_id}/notes
     -> "Payment received: ${amount}. Invoice: {invoice_url}"

Chargebee Webhook: subscription_cancelled
  ↓ Backend
  1. From payload: customer_id, cancellation_reason
  2. Find deal_id
  3. Kommo: PATCH /leads/{deal_id}
     -> subscription_status = cancelled
  4. Kommo: POST /tasks
     -> task for manager: "Subscription cancelled: {reason}"

Chargebee REST API: key requests

Base URL: https://{site}.chargebee.com/api/v2/. Authentication: Basic Auth, username = API_KEY, password — empty.

Create a customer:

import chargebee

chargebee.configure(api_key='your_api_key', site='your-site')

def create_customer(email: str, first_name: str, last_name: str,
                    deal_id: int) -> str:
    result = chargebee.Customer.create({
        'email': email,
        'first_name': first_name,
        'last_name': last_name,
        'cf_kommo_deal_id': str(deal_id)  # custom field for tracing
    })
    return result.customer.id

Create a subscription:

def create_subscription(customer_id: str, plan_id: str,
                        billing_cycles: int = None) -> dict:
    params = {
        'customer_id': customer_id,
        'subscription_items': [{
            'item_price_id': plan_id  # e.g. 'pro-monthly-usd'
        }]
    }
    if billing_cycles:
        params['subscription_items'][0]['billing_cycles'] = billing_cycles

    result = chargebee.Subscription.create_with_items(params)
    sub = result.subscription
    return {
        'id': sub.id,
        'status': sub.status,
        'current_term_end': sub.current_term_end
    }

Handling Chargebee webhooks:

from flask import Flask, request

app = Flask(__name__)

@app.route('/webhooks/chargebee', methods=['POST'])
def chargebee_webhook():
    payload = request.json
    event_type = payload.get('event_type')
    content = payload.get('content', {})

    if event_type == 'payment_succeeded':
        transaction = content.get('transaction', {})
        customer_id = transaction.get('customer_id')
        amount = transaction.get('amount', 0) / 100  # cents -> dollars

        deal_id = get_deal_id_by_customer(customer_id)
        if deal_id:
            create_kommo_note(deal_id, f'Chargebee: payment received ${amount:.2f}')

    elif event_type == 'subscription_cancelled':
        subscription = content.get('subscription', {})
        customer_id = subscription.get('customer_id')
        reason = subscription.get('cancel_reason', 'not specified')

        deal_id = get_deal_id_by_customer(customer_id)
        if deal_id:
            update_kommo_deal(deal_id, {'subscription_status': 'cancelled'})
            create_kommo_task(deal_id, f'Chargebee subscription cancelled. Reason: {reason}')

    elif event_type == 'invoice_generated':
        invoice = content.get('invoice', {})
        customer_id = invoice.get('customer_id')
        invoice_url = invoice.get('invoice_url', '')
        total = invoice.get('total', 0) / 100

        deal_id = get_deal_id_by_customer(customer_id)
        if deal_id:
            create_kommo_note(deal_id, f'Chargebee invoice: ${total:.2f}. Link: {invoice_url}')

    return '', 200

Idempotency: Chargebee may retry a webhook on error. Store event_id from the payload and check it before processing — otherwise the Note will be created twice.

Kommo plan -> Chargebee mapping

A Chargebee plan (plan_id) follows the format {plan}-{period}-{currency}, e.g. pro-monthly-usd. Linking it to a Kommo custom field:

PLAN_MAP = {
    'Starter':    'starter-monthly-usd',
    'Pro':        'pro-monthly-usd',
    'Enterprise': 'enterprise-monthly-usd',
    'Pro Annual': 'pro-yearly-usd'
}

def get_plan_id(kommo_tariff_field: str) -> str:
    return PLAN_MAP.get(kommo_tariff_field, 'pro-monthly-usd')

Real case

B2B SaaS (EU market, 40–60 new customers per quarter, Kommo + Chargebee + Stripe):

  • Before: after Won, a manager spent 15–30 minutes creating a customer in Chargebee. Errors in the plan or missed billing_cycles were common — clients received open-ended subscriptions.
  • After: Won -> subscription active within 5 minutes. The manager never opens Chargebee. On first payment — automatic Note in Kommo; on cancellation — a task with the reason.
  • Additionally: Customer Success sees the full payment history in the deal card — without needing access to Chargebee.

A similar pattern for Stripe — there it uses payment links; here it is full subscription management via Chargebee.

Who should use this

  • SaaS companies with Chargebee as their subscription management platform
  • Need to connect Won in the CRM to subscription activation without a manual step
  • Customer Success should see subscription status and payment history in Kommo
  • 20+ new subscriptions per month — manual work becomes unprofitable

Frequently asked questions

Chargebee API — is OAuth needed or is an API key enough?

For server-side integrations — API Key via Basic Auth: key as username, empty password. OAuth is not needed. The key is created in Chargebee: Settings -> Configure Chargebee -> API Keys.

How do you configure a webhook in Chargebee?

Settings -> Configure Chargebee -> Webhooks -> Add Webhook URL. Select events: payment_succeeded, subscription_cancelled, invoice_generated, subscription_renewed. Chargebee signs the payload via HMAC-SHA256 with the Chargebee-Webhook-Signature header for verification.

Chargebee vs Stripe Billing for Kommo integration?

Both work with an API Key. Chargebee specializes in subscription management: plan changes, pause, dunning management, revenue recognition. Stripe Billing is simpler with fewer out-of-the-box features. If you use Chargebee — integrate via the Chargebee API. If you only use Stripe — via Stripe Payment Links or the Stripe Billing API.

Two options: custom field in Chargebee Customer (cf_kommo_deal_id) — available from the webhook payload, no separate storage needed. Or a DB table {chargebee_customer_id -> kommo_deal_id}. The first option is simpler and works without additional storage.

What if a client upgrades their plan?

The subscription_changed webhook contains the old and new plan_id. Update the custom field in Kommo and create a Note: “Plan changed: {old_plan} -> {new_plan}”.

Summary

  • Chargebee REST API: Basic Auth (API key), Python SDK pip install chargebee
  • Create Customer + Subscription on Won via chargebee.Customer.create and Subscription.create_with_items
  • Webhook events: payment_succeeded, subscription_cancelled, invoice_generated -> Notes and tasks in Kommo
  • Idempotency: store event_id to prevent duplicates on retry
  • Typical development timeline — 1–2 weeks

If you use Chargebee and Kommo and want to automate subscription activation on deal close — describe your plan structure and billing scheme. Exceltic.dev will configure the mapping and payment event handling.

More articles

All →