Когда клиент оплачивает счёт через Stripe, CRM об этом не узнает автоматически - если только вы не настроили приём webhook-событий. Кастомная схема выглядит так: Stripe отправляет payment_intent.succeeded на ваш endpoint, сервер проверяет подпись через Stripe-Signature header, извлекает kommo_lead_id из метаданных платежа и обновляет кастомные поля сделки в Kommo через PATCH /api/v4/leads.
Результат: менеджер видит статус «Оплачено», дату и сумму прямо в карточке - без захода в Stripe Dashboard.
У Exceltic.dev несколько реализованных проектов такой интеграции для B2B-компаний, принимающих платежи через Stripe в USD и EUR. Паттерн всегда один: финансовые данные живут в Stripe, сделки - в Kommo, и между ними нет автоматического канала. Менеджеры либо вручную отмечают оплату в CRM, либо финансист сверяет выписку Stripe со списком сделок в конце месяца. Эта статья описывает, как закрыть этот разрыв через прямую webhook-интеграцию.
Почему нативного не хватает
Stripe App Marketplace содержит несколько CRM-коннекторов, но Kommo там нет. Kommo Marketplace, в свою очередь, предлагает виджеты для платёжных систем СНГ-рынка, но не для Stripe.
Apps вроде Zapier или Make дают частичное решение: можно настроить триггер на webhook от Stripe и action на обновление Kommo. Проблема в деталях:
- Zapier принимает Stripe webhooks через polling или webhook trigger, но не верифицирует
Stripe-Signature- событие от любого источника будет обработано как настоящее. - При нагрузке или сбое Zapier пропускает события без алертов. Для платёжных данных это критично.
- Обновление конкретного кастомного поля в Kommo через Zapier требует знания
field_id, который нельзя выбрать из выпадающего списка - нужен ручной ввод числового ID. - Идемпотентность (защита от двойной записи при повторной доставке webhook) в Zapier не реализована.
Для одноразового тестирования Zapier подходит. Для продакшн-обработки платёжных событий - нет.
Что реализуется
Stripe webhook -> сервер -> проверка подписи -> запись в Kommo
Webhook secret и Stripe-Signature
Stripe-Signature - это HTTP-заголовок, который Stripe добавляет к каждому webhook-запросу. Он содержит timestamp и HMAC-SHA256 подпись, вычисленную от тела запроса и секрета вашего endpoint (whsec_...). Проверка подписи гарантирует, что запрос пришёл от Stripe, а не от постороннего источника.
Критическое требование: для верификации нужно сырое тело запроса (raw body). Если фреймворк парсит JSON до верификации - подпись не совпадёт. В Python/Flask это request.data, в Node.js/Express - express.raw({ type: 'application/json' }).
Пример верификации на Python:
import stripe
from flask import Flask, request, abort
app = Flask(__name__)
webhook_secret = "whsec_your_secret_here"
@app.route("/stripe/webhook", methods=["POST"])
def stripe_webhook():
payload = request.data # raw bytes, not parsed JSON
sig_header = request.headers.get("Stripe-Signature")
try:
event = stripe.Webhook.construct_event(
payload, sig_header, webhook_secret
)
except stripe.error.SignatureVerificationError:
abort(400) # подпись не совпала - отклоняем
if event["type"] == "payment_intent.succeeded":
pi = event["data"]["object"]
lead_id = pi["metadata"].get("kommo_lead_id")
amount = pi["amount"] # в минимальных единицах (центы)
currency = pi["currency"]
pi_id = pi["id"] # для идемпотентности
update_kommo_lead(lead_id, amount, currency, pi_id)
return "", 200
Какие события обрабатываем
Три основных события Stripe для учёта платежей:
payment_intent.succeeded- разовый платёж успешно завершён. Объект содержитid,amount(в центах),currency,metadata,customer.invoice.paid- подписка или инвойс оплачены. Объект содержитamount_paid,subscription,payment_intent. Срабатывает при каждом успешном списании по подписке.checkout.session.completed- клиент завершил оплату через Stripe Checkout. Содержитamount_total,payment_intent,client_reference_id- удобное поле для передачиkommo_lead_idбез метаданных.
Для большинства B2B-сценариев достаточно payment_intent.succeeded. invoice.paid нужен если вы работаете с подписками через Stripe Billing.
Идемпотентность через payment_intent.id
Stripe гарантирует доставку webhook at-least-once - одно событие может прийти дважды при таймаутах или перезапусках. Без защиты от дублей сумма оплаты запишется в Kommo дважды.
Решение: храним payment_intent.id в кастомном поле сделки. Перед записью проверяем - если поле уже содержит этот ID, обработку пропускаем.
def update_kommo_lead(lead_id, amount, currency, pi_id):
# Проверяем идемпотентность
lead = get_kommo_lead(lead_id) # GET /api/v4/leads/{id}
stored_pi_id = get_custom_field_value(lead, FIELD_ID_PAYMENT_INTENT)
if stored_pi_id == pi_id:
return # уже обработано
# Обновляем поля в Kommo
patch_payload = [{
"id": int(lead_id),
"custom_fields_values": [
{"field_id": FIELD_ID_PAYMENT_STATUS,
"values": [{"value": "paid"}]},
{"field_id": FIELD_ID_AMOUNT_PAID,
"values": [{"value": amount / 100}]}, # конвертируем центы
{"field_id": FIELD_ID_CURRENCY,
"values": [{"value": currency.upper()}]},
{"field_id": FIELD_ID_PAYMENT_INTENT,
"values": [{"value": pi_id}]},
]
}]
# PATCH https://{subdomain}.kommo.com/api/v4/leads
kommo_patch("/api/v4/leads", patch_payload)
# Добавляем примечание в карточку
note_text = f"Stripe: оплачено {amount / 100:.2f} {currency.upper()} | ID: {pi_id}"
# POST https://{subdomain}.kommo.com/api/v4/leads/notes
kommo_post(f"/api/v4/leads/notes", [{
"entity_id": int(lead_id),
"note_type": "common",
"params": {"text": note_text}
}])
Кастомные поля сделки в Kommo
Для схемы нужно создать несколько полей в типе сделки:
| Поле | Тип | Что хранит |
|---|---|---|
| Статус оплаты | Список | pending / paid / failed / refunded |
| Сумма оплаты | Число | Сумма в основных единицах (не центах) |
| Валюта | Текст | USD, EUR, GBP |
| Stripe Payment Intent ID | Текст | Для идемпотентности |
| Дата оплаты | Дата/время | Unix timestamp из события |
field_id каждого поля получаем через GET /api/v4/leads/custom_fields - это числовой идентификатор, который используется в custom_fields_values.
Передача kommo_lead_id в Stripe
Чтобы при получении webhook знать, к какой сделке относится платёж, lead_id нужно передать в Stripe при создании платёжного намерения:
payment_intent = stripe.PaymentIntent.create(
amount=amount_in_cents,
currency="usd",
metadata={
"kommo_lead_id": str(lead_id),
"kommo_contact_id": str(contact_id),
}
)
Альтернатива для Stripe Checkout: client_reference_id при создании сессии - это поле доступно в checkout.session.completed без метаданных.
Пошаговая схема
- При создании PaymentIntent в Stripe - передаём
kommo_lead_idвmetadata. - Stripe при успешной оплате отправляет
payment_intent.succeededна ваш endpoint. - Сервер читает raw body, извлекает
Stripe-Signatureheader. stripe.Webhook.construct_event()верифицирует подпись через HMAC-SHA256. Если ошибка - возвращаем400, логируем.- Сервер возвращает
200 OKнемедленно (до обработки) - иначе Stripe повторит попытку через 5 секунд. - В фоне: читаем
kommo_lead_idизevent.data.object.metadata. - Проверяем
payment_intent.idв кастомном поле сделки Kommo - идемпотентность. PATCH /api/v4/leads- обновляем статус, сумму, валюту, PI ID, дату.POST /api/v4/leads/notes- добавляем примечание с суммой и Stripe ID.- Опционально: меняем этап сделки на «Оплачено» через
status_idв том же PATCH.
Обработка invoice.paid для подписок аналогична, но kommo_lead_id берётся из event.data.object.metadata через associated payment_intent.
Реальный кейс
B2B-агентство, 8 менеджеров по продажам, средний чек - $3,000-12,000, оплата через Stripe в USD. Цикл сделки - 2-6 недель, оплата в конце.
Проблема до интеграции: финансист сверял Stripe Dashboard со списком сделок в Kommo вручную каждый понедельник - около 2 часов. Менеджеры не знали о поступившей оплате до этой сверки и не могли вовремя переходить к следующему этапу (онбординг, передача в команду).
Что реализовали: webhook-endpoint на Python/FastAPI, обработка payment_intent.succeeded и invoice.paid, запись в 5 кастомных полей сделки, примечание с суммой и Stripe ID, автоматический перевод сделки на этап «Оплачено» при успехе.
Результат через месяц после запуска:
- Ручная сверка: 0 минут в неделю (финансист проверяет отчёт в Kommo, не Stripe).
- Среднее время от оплаты до старта онбординга: с 4 рабочих дней до 2 часов (менеджер получает уведомление о смене этапа немедленно).
- За первый месяц: 0 случаев двойной записи (идемпотентность через PI ID работает).
Срок реализации проекта - 2 недели, включая тестирование в Stripe test mode.
Для кого подходит
Схема актуальна для компаний, которые:
- Ведут сделки в Kommo и принимают оплату через Stripe в иностранной валюте.
- Цикл сделки больше 1 недели - менеджер не может держать статус оплаты в голове.
- В команде есть разделение: менеджер по продажам и финансист/бухгалтер работают с разными инструментами.
- Используют подписки или рекуррентные платежи через Stripe Billing - каждое списание должно отражаться в сделке.
Если вы уже настроили кастомные интеграции для Kommo и хотите добавить платёжный слой - это логичное следующее звено. Обзор возможностей платформы и её API-модели описан в обзоре Kommo CRM.
Для компаний, которые выставляют счета через Stripe и хотят настроить автоматическую воронку в Kommo под платёжный цикл - тема связана: этапы воронки определяют, когда создаётся PaymentIntent и как обрабатывается его webhook.
Термин: Webhook endpoint - это URL вашего сервера, на который Stripe отправляет HTTP POST-запросы при наступлении платёжных событий (оплата, возврат, сбой). В отличие от polling, webhook не требует периодических запросов к API - данные приходят в момент события.
Часто задаваемые вопросы
Насколько надёжна доставка webhook от Stripe?
Stripe гарантирует at-least-once доставку: если ваш endpoint вернул не 2xx, Stripe повторяет попытку по экспоненциальному backoff - до 3 дней. На практике 99.9%+ событий доставляются при первой попытке. Ключевое условие: endpoint возвращает 200 OK немедленно, до завершения обработки. Для этого обработку выносят в очередь (Celery, RQ, Bull) - endpoint только принимает и ставит в очередь, не обрабатывает синхронно. Проверить доставку и повторить вручную можно через Stripe Workbench -> Events.
Что делать при invoice.paid для подписки - обновлять ту же сделку или создавать новую?
Зависит от модели. Для ежемесячных ретейнеров типичная схема: одна сделка на контракт, каждый invoice.paid добавляет примечание с суммой и датой, поле «Последний платёж» обновляется. Общая сумма по контракту суммируется на уровне кастомного поля или в дашборде. Если каждый период - отдельный проект, создаётся новая сделка через POST /api/v4/leads при первом invoice.paid периода.
Как передать kommo_lead_id в Stripe если оплата создаётся на стороне клиента?
Если клиент платит через Stripe Checkout (hosted page), kommo_lead_id передаётся в client_reference_id при создании Session на сервере. Это поле недоступно для изменения на клиенте и будет в checkout.session.completed. Для PaymentIntent, создаваемого на клиенте через Stripe.js, - metadata недоступны до подтверждения сервером. Корректная схема: сервер создаёт PaymentIntent с metadata, клиент только подтверждает через client_secret.
Как обрабатывать возврат через Stripe?
Stripe отправляет charge.refunded при полном или частичном возврате. Объект содержит amount_refunded и payment_intent. По payment_intent.id находим сделку (через поле идемпотентности), обновляем статус на «Возврат» и сумму возврата. Для частичных возвратов - отдельное кастомное поле или примечание с разбивкой. Менеджер видит историю платежей прямо в карточке без захода в Stripe.
Нужен ли выделенный сервер или подойдёт serverless?
Serverless (AWS Lambda, Vercel Functions, Cloudflare Workers) полностью подходит для приёма Stripe webhooks. Единственное условие: функция должна вернуть 200 OK в пределах таймаута Stripe (около 30 секунд). Если обработка длиннее - принять webhook в serverless, поставить задачу в очередь (SQS, Upstash), обработать асинхронно. Для MVP с объёмом до 100 платежей в день разница незначительна и serverless проще в деплое.
Итого
- Нативной интеграции Kommo + Stripe нет - и не появится без кастомной разработки.
- Zapier/Make не верифицируют
Stripe-Signatureи не защищают от дублей - не подходят для продакшн-обработки платёжных событий. - Три события для обработки:
payment_intent.succeeded(разовые),invoice.paid(подписки),checkout.session.completed(Checkout flow). - Идемпотентность через
payment_intent.idв кастомном поле - обязательна, Stripe доставляет события повторно. - Kommo API:
PATCH /api/v4/leadsдля полей +POST /api/v4/leads/notesдля примечания. - Документация по webhook-событиям: stripe.com/docs/webhooks. Документация по API Kommo: developers.kommo.com.
Если вы принимаете оплату через Stripe и ведёте сделки в Kommo - опишите вашу схему (типы платежей, объём сделок, какие поля важны) команде Exceltic.dev. Разберём архитектуру и оценим объём работ.
Title tag: Kommo + Stripe webhook: платежи в карточку сделки Meta description: Stripe знает об оплате сразу - Kommo нет. Настраиваем webhook: payment_intent.succeeded -> проверка подписи -> запись суммы и статуса в custom field сделки. LSI-запросы: stripe webhook kommo, payment_intent succeeded kommo, stripe signature verification, kommo custom fields api, kommo stripe интеграция без zapier Slug: /blog/kommo-stripe-webhooks-platezhi-v-sdelku