Kommo + Recurly: Automatic Subscription Creation from Won Deals

Recurly is an enterprise subscription management platform for SaaS and media businesses: automatic dunning management, proration on plan changes, tax compliance (Avalara integration), multi-currency, and revenue recognition. Unlike Chargebee or Stripe Billing, Recurly specialises in complex subscription models with multiple plans, add-ons, and usage-based billing. Without Kommo integration, Won in the CRM and creating a subscription in Recurly are two manual steps. With the integration, Won -> account + subscription in seconds.

Recurly vs Chargebee vs Stripe Billing

ParameterRecurlyChargebeeStripe Billing
Dunning managementAutomatic, configurableAutomaticVia Smart Retries
ProrationBuilt-inBuilt-inPartial
Tax complianceAvalara nativeAvalara/TaxJarStripe Tax
Usage-based billingYesYesYes
Revenue recognitionASC 606 built-inVia integrationSeparate module
Best forEnterprise SaaS, mediaSMB–Enterprise SaaSDevelopers, marketplace

Recurly is chosen by companies with 500+ subscribers, complex pricing, and revenue recognition requirements (public companies, enterprise procurement).

What Gets Synchronised

Kommo -> Recurly: — Won -> create Account with contact data — Won -> create Subscription on the required plan — Plan change -> update Subscription (proration is automatic) — Client churn -> cancel Subscription

Recurly -> Kommo:invoice.paid -> Note: “Recurly: payment received, $amount” — invoice.past_due -> Note + task: “Invoice overdue — contact the client” — subscription.canceled -> Note: “Subscription cancelled” — subscription.reactivated -> Note: “Subscription reactivated”

Recurly API: Key Requests

Base URL: https://v3.recurly.com. Authentication: Basic Auth — API key as username, empty password (or Bearer in header). API Version: header Accept: application/vnd.recurly.v2021-02-25.

import requests
from requests.auth import HTTPBasicAuth

RECURLY_API_KEY = "your_api_key"  # from Recurly Settings -> API Credentials
RECURLY_BASE_URL = "https://v3.recurly.com"
HEADERS = {
    "Accept": "application/vnd.recurly.v2021-02-25",
    "Content-Type": "application/json",
}
AUTH = HTTPBasicAuth(RECURLY_API_KEY, "")

def create_account(code: str, email: str, first_name: str,
                   last_name: str, company: str = "") -> dict:
    # code - unique identifier (usually email or CRM deal ID)
    payload = {
        "code": code,
        "email": email,
        "first_name": first_name,
        "last_name": last_name,
        "company": company,
    }
    resp = requests.post(
        f"{RECURLY_BASE_URL}/accounts",
        headers=HEADERS, auth=AUTH, json=payload
    )
    resp.raise_for_status()
    return resp.json()

def create_subscription(account_code: str, plan_code: str,
                        currency: str = "USD") -> dict:
    # plan_code - plan code from Recurly Plans
    payload = {
        "account": {"code": account_code},
        "plan_code": plan_code,
        "currency": currency,
    }
    resp = requests.post(
        f"{RECURLY_BASE_URL}/subscriptions",
        headers=HEADERS, auth=AUTH, json=payload
    )
    resp.raise_for_status()
    return resp.json()

def change_subscription_plan(subscription_uuid: str, plan_code: str,
                              timeframe: str = "now") -> dict:
    # timeframe: "now" | "renewal" - immediately with proration or at next renewal
    payload = {
        "plan_code": plan_code,
        "timeframe": timeframe,
    }
    resp = requests.put(
        f"{RECURLY_BASE_URL}/subscriptions/{subscription_uuid}",
        headers=HEADERS, auth=AUTH, json=payload
    )
    resp.raise_for_status()
    return resp.json()

def cancel_subscription(subscription_uuid: str) -> dict:
    resp = requests.put(
        f"{RECURLY_BASE_URL}/subscriptions/{subscription_uuid}/cancel",
        headers=HEADERS, auth=AUTH
    )
    resp.raise_for_status()
    return resp.json()

# Kommo plan -> Recurly plan code mapping
PLAN_MAP = {
    "starter": "plan_starter_monthly",
    "growth":  "plan_growth_monthly",
    "scale":   "plan_scale_monthly",
}

def on_deal_won(lead: dict, contact: dict):
    email = get_contact_email(contact)
    name = contact["name"].split()
    first_name = name[0] if name else ""
    last_name = " ".join(name[1:]) if len(name) > 1 else ""
    company = get_custom_field(lead, COMPANY_FIELD_ID) or ""
    plan = get_custom_field(lead, PLAN_FIELD_ID) or "starter"
    account_code = f"kommo_{lead['id']}"

    account = create_account(
        code=account_code,
        email=email,
        first_name=first_name,
        last_name=last_name,
        company=company,
    )

    plan_code = PLAN_MAP.get(plan.lower(), "plan_starter_monthly")
    sub = create_subscription(account_code=account_code, plan_code=plan_code)

    update_kommo_deal(lead["id"], {
        "recurly_account_code": account_code,
        "recurly_subscription_uuid": sub["uuid"],
    })
    create_kommo_note(lead["id"],
        f"Recurly: account created, subscription {plan_code} active (UUID: {sub['uuid']})")

def on_plan_upgrade(lead: dict, contact: dict, old_plan: str, new_plan: str):
    sub_uuid = get_deal_field(lead["id"], "recurly_subscription_uuid")
    if not sub_uuid:
        return
    new_plan_code = PLAN_MAP.get(new_plan.lower(), "plan_starter_monthly")
    change_subscription_plan(sub_uuid, new_plan_code, timeframe="now")
    create_kommo_note(lead["id"],
        f"Recurly: plan changed to {new_plan_code} (proration automatic)")

Handling Recurly Webhook:

import hmac, hashlib

RECURLY_WEBHOOK_KEY = "your_webhook_signing_key"

@app.route("/webhooks/recurly", methods=["POST"])
def recurly_webhook():
    # Recurly signs webhooks via HMAC-SHA256
    sig = request.headers.get("Recurly-Signature", "")
    timestamp, received_sig = (sig.split(",") + ["", ""])[:2]
    expected = hmac.new(
        RECURLY_WEBHOOK_KEY.encode(),
        f"{timestamp}.{request.data.decode()}".encode(),
        hashlib.sha256,
    ).hexdigest()
    if not hmac.compare_digest(expected, received_sig.split("=")[-1]):
        return "", 401

    payload = request.json
    event_type = payload.get("event_type")
    account_code = payload.get("account", {}).get("code", "")

    deal_id = find_deal_by_field("recurly_account_code", account_code)
    if not deal_id:
        return "", 200

    if event_type == "invoice.paid":
        amount = payload.get("invoice", {}).get("total", 0)
        create_kommo_note(deal_id,
            f"Recurly: payment received - ${amount/100:.2f}")

    elif event_type == "invoice.past_due":
        create_kommo_note(deal_id, "Recurly: invoice overdue")
        create_kommo_task(deal_id,
            "Recurly: contact client - payment is overdue")

    elif event_type == "subscription.canceled":
        create_kommo_note(deal_id, "Recurly: subscription cancelled")

    elif event_type == "subscription.reactivated":
        create_kommo_note(deal_id, "Recurly: subscription reactivated")

    return "", 200

Dunning Management: How Recurly Reduces Involuntary Churn

Recurly automatically retries failed payments on a configurable schedule (dunning cycles). For the Kommo integration, the key point is: when dunning ultimately fails and the subscription is cancelled, the subscription.canceled webhook appears as a Note -> the manager sees this in the deal card and can initiate a win-back call.

Retry schedule: Recurly Dashboard -> Configuration -> Dunning Campaigns. Standard cycle: days 1, 3, 7, 14, 21 after the first failure.

Real-World Case

B2B SaaS (US, 200+ subscribers, Kommo + Recurly):

  • Before: Won in Kommo -> manager manually created an account in Recurly, added the plan, sent an invite. 20–30 minutes per new client. Sometimes the wrong plan was specified -> client on the incorrect tier.
  • After: Won -> account + subscription in 10 seconds. Subscription UUID is saved in the deal -> on upgrade, the plan is changed via API with automatic proration. 0 plan errors over 8 months.
  • Additionally: invoice.past_due -> manager task on the day of the overdue. Involuntary churn decreased by 24% — the team responds before the dunning system cancels the subscription.

Who This Is Relevant For

  • SaaS companies with 100+ active subscribers and complex plans (add-ons, usage-based)
  • Companies with revenue recognition requirements (ASC 606) — Recurly has this built in
  • Enterprise teams with procurement processes and multi-currency
  • Companies where involuntary churn exceeds 3% — dunning management is critical

Frequently Asked Questions

Recurly vs Stripe Billing — when to choose which?

Stripe Billing: easier to start, good documentation, suitable for 0–500 subscriptions without complex pricing. Recurly: complex subscriptions, native dunning management, revenue recognition, 500+ clients with different plans. A separate guide for Kommo + Stripe is available — the architecture is similar, the difference lies in platform capabilities.

Does Recurly support EU VAT and tax compliance?

Yes. Recurly is integrated with Avalara for automatic tax calculation in 150+ jurisdictions including EU VAT. Setup: Recurly -> Integrations -> Avalara. On Won with an EU client — Recurly automatically applies the correct VAT rate.

How do I handle trial -> paid conversion via API?

Recurly supports trial subscriptions: when calling create_subscription with the trial_ends_at parameter. After the trial ends, Recurly automatically moves to paid. Webhook subscription.activated (trial -> paid) — Note in Kommo. Or subscription.canceled if the client did not convert.

Can I attach a payment method to an account via API?

Yes, via POST /accounts/{code}/billing_info with card data (or tokenised via Recurly.js). For B2B it is typically simpler: create account without card -> send client an invoice -> client pays via Recurly-hosted page. Hosted invoice payment does not require PCI compliance from your code.

Summary

  • Recurly API: Basic Auth (api_key + empty password), Accept: application/vnd.recurly.v2021-02-25
  • Flow: create account -> create subscription -> save UUID in Kommo
  • Plan change: PUT /subscriptions/{uuid} with timeframe: "now" — proration is automatic
  • Webhook: HMAC-SHA256 via Recurly-Signature, key events: invoice.paid/past_due, subscription.canceled
  • Dunning: configure in Dashboard, on failure -> Note in Kommo via webhook

If you use Recurly and Kommo and want to automate subscription creation on Won — describe your plan structure and upgrade scenarios. Exceltic.dev will configure the integration with proration and dunning notifications.

More articles

All →