Kommo + Recurly: автоматическое создание подписок из выигранных сделок
Recurly — enterprise-платформа управления подписками для SaaS и media-бизнесов: autmatic dunning management, proration при смене тарифа, tax compliance (Avalara интеграция), multi-currency, revenue recognition. В отличие от Chargebee или Stripe Billing, Recurly специализируется на сложных подписочных моделях с несколькими планами, add-ons и usage-based billing. Без интеграции с Kommo Won в CRM и создание подписки в Recurly — два ручных шага. С интеграцией Won -> аккаунт + подписка за секунды.
Recurly vs Chargebee vs Stripe Billing
| Параметр | Recurly | Chargebee | Stripe Billing |
|---|---|---|---|
| Dunning management | Автоматический, настраиваемый | Автоматический | Через Smart Retries |
| Proration | Встроенная | Встроенная | Частичная |
| Tax compliance | Avalara нативно | Avalara/TaxJar | Stripe Tax |
| Usage-based billing | Да | Да | Да |
| Revenue recognition | ASC 606 встроенный | Через интеграцию | Отдельный модуль |
| Подходит для | Enterprise SaaS, media | SMB–Enterprise SaaS | Разработчики, marketplace |
Recurly выбирают компании с 500+ подписчиками, сложной тарификацией и требованиями к revenue recognition (public companies, enterprise procurement).
Что синхронизируется
Kommo -> Recurly:
— Won -> создать Account с данными контакта
— Won -> создать Subscription на нужный план
— Смена тарифа -> обновить Subscription (proration автоматически)
— Потеря клиента -> отменить Subscription
Recurly -> Kommo:
— invoice.paid -> Note: «Recurly: платёж получен, $amount»
— invoice.past_due -> Note + задача: «Счёт просрочен — связаться с клиентом»
— subscription.canceled -> Note: «Подписка отменена»
— subscription.reactivated -> Note: «Подписка возобновлена»
Recurly API: ключевые запросы
Base URL: https://v3.recurly.com.
Аутентификация: Basic Auth — API key как username, пустой пароль (или Bearer в заголовке).
API Version: заголовок Accept: application/vnd.recurly.v2021-02-25.
import requests
from requests.auth import HTTPBasicAuth
RECURLY_API_KEY = "your_api_key" # из 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 - уникальный идентификатор (обычно email или 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 - код плана из 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" - немедленно с proration или в следующий период
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 -> Recurly plan codes
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: аккаунт создан, подписка {plan_code} активна (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: тариф изменён на {new_plan_code} (proration автоматически)")
Обработка Recurly Webhook:
import hmac, hashlib
RECURLY_WEBHOOK_KEY = "your_webhook_signing_key"
@app.route("/webhooks/recurly", methods=["POST"])
def recurly_webhook():
# Recurly подписывает webhook через 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: платёж получен - ${amount/100:.2f}")
elif event_type == "invoice.past_due":
create_kommo_note(deal_id, "Recurly: счёт просрочен")
create_kommo_task(deal_id,
"Recurly: связаться с клиентом - просрочен платёж")
elif event_type == "subscription.canceled":
create_kommo_note(deal_id, "Recurly: подписка отменена")
elif event_type == "subscription.reactivated":
create_kommo_note(deal_id, "Recurly: подписка возобновлена")
return "", 200
Dunning Management: как Recurly снижает involuntary churn
Recurly автоматически повторяет неудавшиеся платежи по настраиваемому расписанию (dunning cycles). Для Kommo-интеграции важно: когда dunning заканчивается провалом и подписка отменяется, вебхук subscription.canceled попадает в Note -> менеджер видит это в карточке и может инициировать win-back звонок.
Ретри-расписание: Recurly Dashboard -> Configuration -> Dunning Campaigns. Стандартный цикл: день 1, 3, 7, 14, 21 после первой неудачи.
Реальный кейс
B2B SaaS (US, 200+ подписчиков, Kommo + Recurly):
- До: Won в Kommo -> менеджер вручную создавал аккаунт в Recurly, добавлял план, отправлял invite. 20–30 минут на каждого нового клиента. Иногда забывали указать правильный план -> клиент на неверном тарифе.
- После: Won -> аккаунт + подписка за 10 секунд. UUID подписки сохраняется в сделке -> при апгрейде план меняется через API с автоматической proration. 0 ошибок с планом за 8 месяцев.
- Дополнительно:
invoice.past_due-> задача менеджеру в день просрочки. Involuntary churn снизился на 24% — команда реагирует до отмены подписки dunning-системой.
Для кого актуально
- SaaS с 100+ активными подписчиками и сложными планами (add-ons, usage-based)
- Компании с требованиями к revenue recognition (ASC 606) — Recurly встроен
- Enterprise-команды с procurement-процессом и multi-currency
- Компании где involuntary churn > 3% — dunning management критичен
Часто задаваемые вопросы
Recurly vs Stripe Billing — когда что выбрать?
Stripe Billing: проще начать, хорошая документация, подходит для 0–500 подписок без сложной тарификации. Recurly: сложные подписки, dunning management нативно, revenue recognition, 500+ клиентов с разными планами. Для Kommo + Stripe есть отдельное руководство — архитектура аналогична, разница в возможностях платформы.
Recurly поддерживает EU VAT и tax compliance?
Да. Recurly интегрирован с Avalara для автоматического расчёта налогов в 150+ юрисдикциях включая EU VAT. Настройка: Recurly -> Integrations -> Avalara. При Won с EU-клиентом — Recurly автоматически применяет правильную ставку НДС.
Как обработать trial -> paid conversion через API?
Recurly поддерживает trial-подписки: при create_subscription с параметром trial_ends_at. После окончания trial Recurly автоматически переводит в платную. Вебхук subscription.activated (trial -> paid) — Note в Kommo. Или subscription.canceled если клиент не конвертировал.
Можно ли прикрепить payment method к аккаунту через API?
Да, через POST /accounts/{code}/billing_info с данными карты (или tokenized через Recurly.js). Для B2B обычно проще: создать аккаунт без карты -> отправить клиенту invoice -> клиент платит через Recurly-hosted страницу. Hosted invoice payment не требует PCI compliance от вашего кода.
Итого
- Recurly API: Basic Auth (api_key + пустой пароль),
Accept: application/vnd.recurly.v2021-02-25 - Поток: create account -> create subscription -> сохранить UUID в Kommo
- Смена плана:
PUT /subscriptions/{uuid}сtimeframe: "now"— proration автоматически - Webhook: HMAC-SHA256 через
Recurly-Signature, ключевые события:invoice.paid/past_due,subscription.canceled - Dunning: настраивать в Dashboard, при провале -> Note в Kommo через webhook
Если вы используете Recurly и Kommo и хотите автоматизировать создание подписок при Won — опишите структуру планов и сценарии апгрейда. Exceltic.dev настроит интеграцию с proration и dunning-уведомлениями.