Kommo + Paddle: автоматическое создание подписок через Merchant of Record
Paddle — не просто платёжный шлюз, а Merchant of Record: он берёт на себя начисление и уплату EU VAT, UK VAT, US sales tax и GST по всему миру. Без интеграции с Kommo менеджер закрывает сделку, затем вручную открывает Paddle и создаёт клиента и подписку — с задержкой и риском ошибки в данных. С интеграцией Won в Kommo автоматически запускает Paddle-подписку, а события оплаты и отмены мгновенно появляются в карточке сделки.
Почему Paddle отличается от Stripe и Chargebee
Merchant of Record (MoR) — модель, при которой платёжный провайдер юридически выступает продавцом в транзакции. Paddle выставляет счёт клиенту от своего имени, собирает налог и сам перечисляет его в налоговые органы.
Для SaaS-компаний, продающих в ЕС, Великобритании или США, это снимает три проблемы:
— EU VAT: Paddle регистрирует НДС в каждой стране ЕС самостоятельно — вам не нужна регистрация в 27 странах
— US sales tax: Paddle отслеживает nexus и перечисляет tax по штатам
— Compliance: инвойсы с корректным VAT-номером Paddle, а не вашей компании
В сравнении с подпиской через Chargebee, где налоговая ответственность остаётся на вас, Paddle полностью снимает этот вопрос.
Это критично для продукта с клиентами в разных юрисдикциях — и именно поэтому Paddle выбирают B2B SaaS при выходе на международный рынок.
Что синхронизируется между Kommo и Paddle
Kommo -> Paddle:
— Won -> создать Paddle customer (email, имя, страна из полей сделки)
— Won -> создать Paddle subscription (price_id из кастомного поля «Тариф»)
— Обновление контакта (email/имя) -> обновить Paddle customer
Paddle -> Kommo:
— subscription.created -> Note: «Paddle: подписка активирована, ID = sub_xxx»
— transaction.completed -> Note: «Paddle: платёж $X за период»
— transaction.payment_failed -> Note + задача менеджеру: «Paddle: платёж не прошёл, уточнить данные карты»
— subscription.canceled -> Note + кастомное поле paddle_status = canceled
— subscription.paused -> Note + поле paddle_status = paused
Архитектура интеграции
Kommo Webhook: сделка перешла в Won
↓ Backend
1. GET /api/v4/leads/{id} + contacts
-> email, имя, страна, тариф из кастомного поля
2. Paddle API: POST /customers
-> создать или обновить клиента
3. Paddle API: POST /subscriptions
-> price_id = маппинг тарифа, customer_id из шага 2
-> collection_mode = "automatic" (автооплата)
4. Kommo: POST /leads/{id}/notes
-> «Paddle: подписка {subscription_id} активирована»
Paddle Webhook: transaction.payment_failed
↓ Backend
1. Верификация HMAC-SHA256 (Paddle-Signature header)
2. Извлечь customer.id -> найти сделку по полю paddle_customer_id
3. Kommo: POST /leads/{deal_id}/notes -> «Paddle: платёж не прошёл»
4. Kommo: POST /tasks -> «Уточнить платёжные данные клиента в Paddle»
Paddle API: ключевые запросы
Base URL: https://api.paddle.com. Sandbox: https://sandbox-api.paddle.com.
Аутентификация: Bearer token (Authorization: Bearer {api_key}).
Создать клиента в Paddle:
import requests
PADDLE_API_KEY = "your_paddle_api_key"
PADDLE_BASE_URL = "https://api.paddle.com"
headers = {
"Authorization": f"Bearer {PADDLE_API_KEY}",
"Content-Type": "application/json"
}
def create_paddle_customer(email: str, name: str, country_code: str) -> dict:
resp = requests.post(
f"{PADDLE_BASE_URL}/customers",
headers=headers,
json={
"email": email,
"name": name,
"locale": "en",
"custom_data": {"country": country_code}
}
)
resp.raise_for_status()
return resp.json()["data"]
Создать подписку:
PLAN_TO_PRICE_ID = {
"starter": "pri_01abc123",
"growth": "pri_01def456",
"scale": "pri_01ghi789",
}
def create_paddle_subscription(customer_id: str, plan: str) -> dict:
price_id = PLAN_TO_PRICE_ID.get(plan)
if not price_id:
raise ValueError(f"Unknown plan: {plan}")
resp = requests.post(
f"{PADDLE_BASE_URL}/subscriptions",
headers=headers,
json={
"customer_id": customer_id,
"items": [{"price_id": price_id, "quantity": 1}],
"collection_mode": "automatic",
}
)
resp.raise_for_status()
return resp.json()["data"]
def on_deal_won(lead: dict, contact: dict):
email = get_contact_email(contact)
name = contact["name"]
plan = get_custom_field(lead, PLAN_FIELD_ID) or "starter"
country = get_custom_field(lead, COUNTRY_FIELD_ID) or "US"
customer = create_paddle_customer(email, name, country)
subscription = create_paddle_subscription(customer["id"], plan)
# Сохранить paddle_customer_id в Kommo для обратного поиска
update_kommo_deal(lead["id"], {
"paddle_customer_id": customer["id"],
"paddle_subscription_id": subscription["id"],
"paddle_status": "active"
})
create_kommo_note(
lead["id"],
f"Paddle: подписка {subscription['id']} активирована (тариф {plan})"
)
Обработка Paddle Webhook с верификацией HMAC:
import hashlib
import hmac
from flask import Flask, request, abort
app = Flask(__name__)
PADDLE_WEBHOOK_SECRET = "your_webhook_secret"
def verify_paddle_signature(payload: bytes, signature_header: str) -> bool:
# Paddle-Signature: ts=1234567890;h1=abc123...
parts = dict(part.split("=", 1) for part in signature_header.split(";"))
ts = parts.get("ts", "")
h1 = parts.get("h1", "")
signed_payload = f"{ts}:{payload.decode()}"
expected = hmac.new(
PADDLE_WEBHOOK_SECRET.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, h1)
@app.route("/webhooks/paddle", methods=["POST"])
def paddle_webhook():
signature = request.headers.get("Paddle-Signature", "")
if not verify_paddle_signature(request.data, signature):
abort(403)
payload = request.json
event_type = payload.get("event_type")
event_id = payload.get("event_id") # idempotency key
data = payload.get("data", {})
if event_type == "transaction.payment_failed":
customer_id = data.get("customer_id")
amount = data.get("details", {}).get("totals", {}).get("total", "?")
currency = data.get("currency_code", "USD")
deal_id = find_deal_by_paddle_customer(customer_id)
if deal_id:
create_kommo_note(deal_id,
f"Paddle: платёж не прошёл (сумма {amount} {currency})")
create_kommo_task(deal_id,
"Уточнить платёжные данные - Paddle зафиксировал неудачный платёж")
elif event_type == "subscription.canceled":
customer_id = data.get("customer_id")
deal_id = find_deal_by_paddle_customer(customer_id)
if deal_id:
update_kommo_deal(deal_id, {"paddle_status": "canceled"})
create_kommo_note(deal_id,
"Paddle: подписка отменена")
elif event_type == "transaction.completed":
customer_id = data.get("customer_id")
totals = data.get("details", {}).get("totals", {})
amount = totals.get("total", "?")
currency = data.get("currency_code", "USD")
deal_id = find_deal_by_paddle_customer(customer_id)
if deal_id:
create_kommo_note(deal_id,
f"Paddle: платёж {amount} {currency} получен")
return "", 200
Настройка Webhook в Paddle: Paddle Dashboard -> Notifications -> New destination. Указать URL, выбрать события. Secret key генерируется автоматически — скопировать в PADDLE_WEBHOOK_SECRET.
Налоговая логика Paddle: что это означает на практике
Paddle автоматически определяет налоговую ставку по IP и billing country клиента. Для бизнес-клиентов из ЕС — запрашивает VAT-номер и применяет reverse charge. Для физлиц в ЕС — начисляет НДС по ставке страны клиента.
В инвойсе указывается Paddle как продавец. Клиент получает корректный налоговый документ без вашего участия. Для Kommo это означает: поле «тариф» в сделке -> подписка создана -> Paddle сам разбирается с налогами.
Реальный кейс
B2B SaaS (Европа, 40–60 новых клиентов в месяц, Kommo + Paddle + Stripe был заменён на Paddle как MoR):
- До: при каждом Won менеджер вручную открывал Paddle, создавал клиента, выбирал план. Задержка 30–90 минут, иногда — ошибочный тариф в подписке. Налоговые вопросы от EU-клиентов по VAT закрывал финдиректор вручную.
- После: Won -> подписка в Paddle за 8 секунд. Тариф берётся из кастомного поля сделки — ошибок нет. EU VAT автоматически. При
payment_failed— задача менеджеру в тот же день. - Дополнительно:
subscription.canceled-> кастомное поле + задача CSM -> retention-аутрич в течение 24 часов вместо «случайно узнали через неделю».
Для кого актуально
- SaaS с клиентами в ЕС, UK, США — там, где нужен корректный VAT/sales tax на инвойсе
- Команды, уже работающие с Kommo как основной CRM и выбирающие Paddle как MoR
- Продукты с несколькими тарифами — чтобы тариф из сделки автоматически попадал в Paddle без ручного выбора
- Компании, которые хотят видеть историю платежей в карточке клиента без переключения между системами
Для оценки кастомных интеграций Kommo с платёжными системами — объём работы обычно 1–2 недели: маппинг тарифов, обработка webhook, хранение paddle_customer_id.
Часто задаваемые вопросы
Чем Paddle отличается от Stripe Billing?
Stripe — payment processor: вы отвечаете за налоги сами. Paddle — Merchant of Record: Paddle юридически продавец, он собирает и перечисляет налоги. Для компаний с EU-клиентами это принципиально: Stripe требует вашей регистрации НДС в ЕС или подключения TaxJar/Avalara, Paddle делает это сам. Kommo + Stripe интеграция описана отдельно.
Paddle поддерживает разовые платежи или только подписки?
Оба варианта. transaction.completed приходит и для разовых платежей (payment links), и для recurring. Для Kommo-интеграции разовый платёж обрабатывается аналогично: находим сделку по customer_id и пишем Note.
Как хранить paddle_customer_id в Kommo?
Создайте кастомное поле типа «Текст» в настройках Kommo -> Сделки -> Поля. При создании клиента в Paddle сразу записывайте ID через PATCH /api/v4/leads/{id}. Это поле используется для поиска сделки по входящим webhook-событиям.
Что происходит если клиент уже есть в Paddle?
Paddle возвращает ошибку 409 при дублирующемся email. Перед созданием — делайте GET /customers?search={email}. Если клиент найден — обновляйте данные через PATCH /customers/{id} и создавайте новую подписку к существующему клиенту.
Как тестировать без реальных платежей?
Paddle предоставляет sandbox-среду: https://sandbox-api.paddle.com. Sandbox-ключи создаются в Paddle Dashboard -> Developer Tools -> Authentication. Webhook события можно триггерить вручную из Paddle Dashboard в sandbox-режиме.
Итого
- Paddle: Bearer auth, base URL
https://api.paddle.com - Создать клиента:
POST /customers, создать подписку:POST /subscriptions - Хранить
paddle_customer_idв кастомном поле Kommo для обратного поиска - Webhook верификация: HMAC-SHA256 через
Paddle-Signatureheader,ts:payloadформат - Ключевые события:
subscription.created,transaction.completed,transaction.payment_failed,subscription.canceled - MoR-модель: EU VAT, UK VAT, US sales tax — Paddle берёт на себя полностью
Если вы используете Paddle и Kommo и хотите замкнуть подписку на CRM — опишите тарифную сетку и кастомные поля. Exceltic.dev настроит маппинг и обработку webhook-событий.