Webhook EasyPay: структура данных платежей Stripe

Этот документ описывает webhook, который EasyPay отправляет на ваш сервер при платёжных событиях Stripe. Если вам нужно получить платёжные объекты по запросу (а не push-уведомлением), см. Endpoint: Получение платёжных объектов Stripe.

Общая информация

EasyPay получает данные из API Stripe и передаёт их партнёру, сохраняя оригинальную структуру объектов Stripe.

Передача id клиента/заказа через форму оплаты

Вы можете передать свой идентификатор клиента или заказа через параметр client_reference_id в URL платёжной ссылки:

https://short.appsign.me/test_xxx?client_reference_id=your_order_id

Подробнее: Stripe URL Parameters

Принцип формирования payload

Мы получаем необходимые объекты через Stripe API и объединяем их в единый JSON, размещая каждый объект в отдельное поле. Итоговая структура содержит исчерпывающую информацию о событии, хотя и имеет некоторое дублирование данных между объектами.

Как передаётся webhook

Мы отправляем данные на ваш webhook endpoint:

Параметр

Значение

Метод

POST

Content-Type

application/json

Timeout

10 секунд на ответ

Успешный ответ

любой 2xx

Повторы

до 6 попыток с нарастающим интервалом: ~1 → 3 → 9 → 27 → 30 минут (всего ~70 минут)

Как убедиться, что запрос действительно пришёл от EasyPay (HMAC-подпись в заголовках, включается после настройки секрета), — Webhook EasyPay: проверка подписи.

Когда мы повторяем отправку

Повторяем, если повтор способен помочь:

  • ответ 5xx, обрыв соединения или отсутствие ответа за 10 секунд;

  • ответ 429 (слишком много запросов), а также 408, 409, 425.

На остальные 4xx (400, 401, 404 и подобные) повторов не будет — повтор их не исправит. Вместо этого срабатывает внутренний алерт, и с вами свяжется команда EasyPay.

Если вы отдаёте заголовок Retry-After (например, вместе с 429 или 503), мы его учитываем, но ждём не дольше 30 минут.

Идемпотентность на вашей стороне обязательна

Если ответ не пришёл за 10 секунд, мы не знаем, дошёл ли запрос, и повторим отправку. Доставка гарантируется по принципу «хотя бы один раз», поэтому одно и то же событие может прийти к вам дважды.

Повтор несёт ровно то же тело, что и первая отправка. Для дедупликации используйте пару «Event + идентификатор объекта, к которому относится событие»:

  • Charge.id — для платежей, возвратов и диспутов;

  • Subscription.id — для событий подписки.

Отдельный случай — Stripe.payment_intent.payment_failed: если платёж отклонён до того, как списание было создано (например, сработала защита от фрода), объект Charge приходит без id.

Если событие уже обработано, просто верните 2xx.

Поддерживаемые события

Каждый payload содержит поле Event, которое указывает тип события в формате Stripe.<event_type>.

Event

Описание

Stripe.checkout.session.completed

Завершена оплата через Checkout (только для one-time payment)

Stripe.invoice.payment_succeeded

Успешный платёж по инвойсу (первый и повторные платежи подписки)

Stripe.customer.subscription.created

Создана новая подписка

Stripe.customer.subscription.updated

Подписка обновлена (изменение плана, статуса и т.д.)

Stripe.customer.subscription.deleted

Подписка отменена/удалена

Stripe.payment_intent.payment_failed

Платёж не прошёл

Stripe.charge.refunded

Выполнен возврат средств

Stripe.charge.dispute.funds_withdrawn

Открыт диспут — средства списаны

Stripe.charge.dispute.funds_reinstated

Диспут выигран — средства возвращены

Структура payload по типам событий

Успешный платёж

Отправляется при событиях:

  • Stripe.checkout.session.completed

  • Stripe.invoice.payment_succeeded

Структура payload

{
  "Event": "Stripe.checkout.session.completed",
  "Initial_Checkout_Session": <Checkout Session Object>,
  "Subscription": <Subscription Object>,
  "Invoice": <Invoice Object>,
  "Charge": <Charge Object>,
  "Balance_Transaction": <Balance Transaction Object>
}

Описание полей

Поле

Тип

Описание

Event

string

Тип события

Initial_Checkout_Session

object

Объект сессии оплаты (для первого платежа). Содержит client_reference_id, данные клиента, параметры оплаты

Subscription

object

Сырой объект подписки Stripe (Subscription object). null для one-time payments

Invoice

object

Инвойс платежа (для подписок). Содержит детали выставленного счёта. null для one-time payments

Charge

object

Детали списания. Содержит информацию о карте, статус, результат проверок

Balance_Transaction

object

Транзакция баланса. Содержит комиссии Stripe, нетто-сумму

Пример payload

Подписка

Отправляется при событиях:

  • Stripe.customer.subscription.created

  • Stripe.customer.subscription.updated

  • Stripe.customer.subscription.deleted

Структура payload

{
  "Event": "Stripe.customer.subscription.created",
  "Initial_Checkout_Session": <Checkout Session Object>,
  "Subscription": <Subscription Object>,
  "Subscription_Product": <Product Object>,
  "Subscription_from_db": <Subscription Summary Object>
}

Описание полей

Поле

Тип

Описание

Event

string

Тип события

Initial_Checkout_Session

object

Объект сессии оплаты (может быть пустым {} для update/delete событий)

Subscription

object

Полный объект подписки из Stripe

Subscription_Product

object

Объект продукта подписки

Subscription_from_db

object

Сокращённый объект с ключевой информацией о подписке

Структура Subscription_from_db

{
  "subscription_id": "sub_1ROFheLoVAqE08foy7R5vWgT",
  "easypay_partner_name": "your_partner_name",
  "creation_date": "2025-05-13T12:04:54.855Z",
  "customer_id": "cus_SIrQVLdMX8FBGs",
  "customer_name": "Test Customer",
  "customer_email": "test@example.com",
  "subscription_product_name": "Premium Plan",
  "subscription_product_id": "prod_S3bQuNWvDDRHEf",
  "amount_in_cents": 999,
  "currency": "usd",
  "source": "Stripe",
  "current_status": "active",
  "cancel_requested_at": null,
  "ended_at": null
}

Пример payload

Неуспешный платёж

Отправляется при событии:

  • Stripe.payment_intent.payment_failed

Структура payload

{
  "Event": "Stripe.payment_intent.payment_failed",
  "Initial_Checkout_Session": <Checkout Session Object>,
  "Subscription": <Subscription Object>,
  "Invoice": <Invoice Object>,
  "Charge": <Charge Object>,
  "Balance_Transaction": {}
}

Описание полей

Поле

Тип

Описание

Event

string

Тип события

Initial_Checkout_Session

object

Объект сессии оплаты (может быть пустым null / {} для событий)

Subscription

object

Сырой объект подписки Stripe (Subscription object). null для one-time payments

Invoice

object

Инвойс (для подписок)

Charge

object

Детали неуспешного списания. Содержит информацию об ошибке

Balance_Transaction

object

Пустой объект {} (транзакция не создаётся при неуспешном платеже)

Ключевые поля для обработки ошибки

Информация об ошибке находится в объекте Charge (или в Payment Intent):

{
  "failure_code": "card_declined",
  "failure_message": "Your card was declined.",
  "outcome": {
    "type": "issuer_declined",
    "network_decline_code": "05",
    "seller_message": "The bank returned the decline code `do_not_honor`."
  }
}

Пример payload

Возврат (Refund)

Отправляется при событии:

  • Stripe.charge.refunded

Структура payload

{
  "Event": "Stripe.charge.refunded",
  "Initial_Checkout_Session": <Checkout Session Object>,
  "Subscription": <Subscription Object>,
  "Invoice": <Invoice Object>,
  "Charge": <Charge Object>,
  "Balance_Transaction": <Balance Transaction Object>,
  "Refund": <Refund Object>,
  "Dispute": {}
}

Описание полей

Поле

Тип

Описание

Event

string

Тип события

Initial_Checkout_Session

object

Объект сессии оплаты (может быть пустым null / {} для событий)

Subscription

object

Объект подписки (если возврат связан с подпиской)

Invoice

object

Инвойс оригинального платежа

Charge

object

Детали оригинального списания. amount_refunded содержит сумму возврата

Balance_Transaction

object

Транзакция возврата (отрицательная сумма)

Refund

object

Детали возврата — ID, сумма, причина, статус

Dispute

object

Пустой объект {}

Пример payload

Диспут (Dispute)

Отправляется при событиях:

  • Stripe.charge.dispute.funds_withdrawn — открыт диспут, средства списаны

  • Stripe.charge.dispute.funds_reinstated — диспут выигран, средства возвращены

Структура payload

{
  "Event": "Stripe.charge.dispute.funds_withdrawn",
  "Initial_Checkout_Session": <Checkout Session Object>,
  "Subscription": <Subscription Object>,
  "Invoice": <Invoice Object>,
  "Charge": <Charge Object>,
  "Balance_Transaction": <Balance Transaction Object>,
  "Refund": {},
  "Dispute": <Dispute Object>
}

Описание полей

Поле

Тип

Описание

Event

string

Тип события

Initial_Checkout_Session

object

Объект сессии оплаты (может быть пустым null / {} для событий)

Subscription

object

Объект подписки (если диспут связан с подпиской)

Invoice

object

Инвойс оригинального платежа

Charge

object

Детали оригинального списания. disputed: true

Balance_Transaction

object

Транзакция диспута (списание или возврат средств)

Refund

object

Пустой объект {}

Dispute

object

Детали диспута — причина, статус, сроки, доказательства

Ключевые поля Dispute объекта

{
  "id": "du_1S7FL2LoVAqE08foJsFPwcD0",
  "amount": 1999,
  "reason": "fraudulent",
  "status": "needs_response",
  "evidence_details": {
    "due_by": 1759276799,
    "has_evidence": false,
    "past_due": false
  }
}

Поле

Описание

reason

Причина диспута: fraudulent, duplicate, product_not_received, subscription_canceled, и др.

status

Статус: needs_response, under_review, won, lost

evidence_details.due_by

Дедлайн для предоставления доказательств (timestamp)

Пример payload

Оценка риска платежа (risk score)

Stripe Radar оценивает каждый платёж: risk_score от 0 до 100 (чем выше — тем подозрительнее) и категория risk_level. Оценка приходит в объекте Charge, в поле outcome:

{
  "Charge": {
    "outcome": {
      "type": "authorized",
      "network_status": "approved_by_network",
      "risk_level": "normal",
      "risk_score": 12,
      "seller_message": "Payment complete."
    }
  }
}

Поле

Описание

outcome.risk_score

Оценка риска 0–100. Чем выше — тем подозрительнее платёж

outcome.risk_level

Категория риска: normal, elevated, highest

outcome.type

Итог проверки: authorized, issuer_declined, blocked, invalid

outcome.seller_message

Пояснение итога. Для неуспешных платежей: One of your rules blocked this payment — сработало правило Radar; Stripe blocked this payment as too risky — Stripe заблокировал платёж сам

Для неуспешных платежей (Stripe.payment_intent.payment_failed) поля risk_score, risk_level и seller_message дополнительно продублированы на верхнем уровне объекта Charge. Если платёж отклонён до создания списания (например, сработала защита от фрода), объект Charge приходит без id, и risk-поля могут быть null.

Как использовать risk score, чтобы защитить выдачу товара от оплат крадеными картами, — Защита от фрода: работа с risk score в Stripe.

Справочник объектов Stripe

FAQ

Как отличить one-time payment от подписки?

  • Если Subscription не пустой — это платёж по подписке

  • Если Invoice содержит billing_reason: "subscription_create" — это первый платёж подписки

  • Если Invoice содержит billing_reason: "subscription_cycle" — это повторный платёж подписки

Как получить мой client_reference_id?

Поле client_reference_id находится в объекте Initial_Checkout_Session:

{
  "Initial_Checkout_Session": {
    "client_reference_id": "your_order_id",
    ...
  }
}

Как получить комиссию Stripe?

Комиссия находится в объекте Balance_Transaction:

{
  "Balance_Transaction": {
    "fee": 2057,
    "fee_details": [
      {
        "amount": 2057,
        "description": "Stripe processing fees",
        "type": "stripe_fee"
      }
    ],
    "net": 67843
  }
}
  • fee — общая комиссия в центах

  • net — сумма к зачислению после вычета комиссии