# Webhook EasyPay: структура данных платежей T-Bank

> **Этот документ описывает webhook, который EasyPay отправляет на ваш сервер при успешной оплате через T-Bank** (карта или СБП). Аналогичный документ для Stripe-платежей — [Webhook EasyPay: структура данных платежей Stripe](https://docs.thenextgen.store/s/635a43c1-37fa-463b-bc7e-7ede7884fce2/doc/webhook-easypay-struktura-dannyh-platezhej-stripe-2aK80hyqzo).

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

EasyPay получает уведомление об оплате от T-Bank, нормализует его и отправляет вам **очищенный объект** — без внутренних технических полей платёжной системы. В отличие от Stripe, где событий несколько (подписки, возвраты, диспуты), для T-Bank сейчас передаётся **одно событие — успешный платёж**.

T-Bank работает с рублёвыми платежами. Сумма передаётся в **копейках** (минорных единицах): `1000` = 10.00 ₽.

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

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

| Параметр | Значение |
|----------|----------|
| **Метод** | `POST`   |
| **Content-Type** | `application/json` |
| **Timeout** | 10 секунд на ответ |
| **Успешный ответ** | любой `2xx` |
| **Повторы** | до 6 попыток с нарастающим интервалом: \~1 → 3 → 9 → 27 → 30 минут (всего \~70 минут) |

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

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

* ответ `5xx`, обрыв соединения или отсутствие ответа за 10 секунд;
* ответ `429` (слишком много запросов), а также `408`, `409`, `425`.

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

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

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

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

Ключ идемпотентности — `Payment.Data.ep_payment_uuid`: он уникален для платежа и одинаков во всех повторах. Если такой платёж уже обработан, просто верните `2xx`.

У платежей, созданных **публичной ссылкой на оплату** (`pay.appload.tech/<slug>`), `ep_payment_uuid` приходит `null` — там нет `idempotency_key`, его передаёт только тот, кто создаёт платёж через API. Для таких платежей ключом идемпотентности служит `Payment.OrderId`: он уникален для каждой попытки оплаты и не меняется между повторами.

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

Payload содержит поле `Event`, которое указывает тип события.

| Event | Описание |
|-------|----------|
| `TBank.payment.succeeded` | Успешная оплата (разовый платёж картой или через СБП) |

## Структура payload

```json
{
  "Event": "TBank.payment.succeeded",
  "Payment": {
    "Status": "CONFIRMED",
    "Success": true,
    "ErrorCode": "0",
    "Amount": 1000,
    "OrderId": "<string>",
    "Pan": "<masked>",
    "Data": {
      "Email": "<string>",
      "Phone": "<string|null>",
      "ep_pid": "<string>",
      "ep_product_id": "<string>",
      "ep_payment_uuid": "<string|null>",
      "ep_order": "<string|null>"
    }
  }
}
```

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

| Поле | Тип | Описание |
|------|-----|----------|
| `Event` | string | Тип события. Всегда `TBank.payment.succeeded`. |
| `Payment` | object | Объект платежа. |
| `Payment.Status` | string | Статус платежа в T-Bank. Для успешной оплаты — `CONFIRMED`. |
| `Payment.Success` | boolean | Признак успешности — `true`. |
| `Payment.ErrorCode` | string | Код ошибки T-Bank. `"0"` — без ошибок. |
| `Payment.Amount` | number | Сумма в копейках (минорные единицы). `1000` = 10.00 ₽. |
| `Payment.OrderId` | string | Идентификатор заказа на стороне T-Bank (генерируется EasyPay). |
| `Payment.Pan` | string | Маскированный номер карты. Для оплаты через СБП — маскированный телефон. |
| `Payment.Data` | object | Дополнительные данные платежа. |
| `Payment.Data.Email` | string | Email клиента. |
| `Payment.Data.Phone` | string | null     |
| `Payment.Data.ep_pid` | string | Ваш идентификатор партнёра в EasyPay. |
| `Payment.Data.ep_product_id` | string | Идентификатор оплаченного продукта. |
| `Payment.Data.ep_payment_uuid` | string \| null | Уникальный идентификатор платежа. Совпадает с `idempotency_key`, который вы передали при создании платежа через API. `null` — если платёж пришёл по публичной ссылке на оплату (там `idempotency_key` передавать негде); тогда используйте `Payment.OrderId`. |
| `Payment.Data.ep_order` | string \| null | **Ваш** идентификатор заказа — тот, что вы передали при создании платежа: в параметре `?d=` публичной ссылки (`order_id`) или в поле `external_order_id` ручки `create_partner_tbank_payment`. Возвращается ровно в том виде, в котором вы его прислали. `null` — если идентификатор не передавали. |

### Пример payload

```json
{
  "Event": "TBank.payment.succeeded",
  "Payment": {
    "Status": "CONFIRMED",
    "Success": true,
    "ErrorCode": "0",
    "Amount": 1000,
    "OrderId": "a1b2c3d4e5f64a7b8c9d0e1f",
    "Pan": "220070******0000",
    "Data": {
      "Email": "customer@example.com",
      "Phone": null,
      "ep_pid": "your_partner_name",
      "ep_product_id": "42",
      "ep_payment_uuid": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "ep_order": "order-2026-0042"
    }
  }
}
```

## FAQ

### Как сопоставить платёж с моим заказом?

Передавайте свой идентификатор заказа при создании платежа — он вернётся в `Payment.Data.ep_order` ровно таким, каким вы его прислали:

* публичная ссылка на оплату — поле `order_id` в параметре `?d=` (см. «Checkout: Публичная T-Bank ссылка — параметры через Base64»);
* `create_partner_tbank_payment` — поле `external_order_id`.

Если идентификатор не передавали, у платежей через API остаётся `Payment.Data.ep_payment_uuid` (= ваш `idempotency_key`), а у платежей по публичной ссылке — только `Payment.OrderId` (наш внутренний идентификатор попытки; он стабилен, но о вашем заказе ничего не знает).

### Какой продукт оплачен?

Идентификатор продукта — в `Payment.Data.ep_product_id`.

### Какая сумма и валюта?

`Payment.Amount` — сумма в копейках (например, `1000` = 10.00 ₽). T-Bank работает в рублях (RUB).

### Чем платёж картой отличается от СБП?

Событие одно — `TBank.payment.succeeded`. Способ оплаты виден по полю `Payment.Pan`: для карты это маскированный номер карты, для СБП — маскированный телефон.