Merchant API

version: 1.0.0

Для работы Merchant API необходим публичный адрес вашего бэкенда. Укажите адрес в поле Callback URL в консоли Yandex Split на странице разработчика.

Ваш бэкенд должен проверять подлинность запроса, прежде чем приступать к его обработке.

Важно

Не привязывайте логику к конкретным IP-адресам — они могут измениться. Для проверки подлинности используйте валидацию JWT-токена с публичными ключами.

Аутентификация запроса Яндекса

В теле запроса Яндекс присылает JWT-токен, подписанный по алгоритму ES256. Токен состоит из трех частей: заголовка (header), полезной нагрузки (payload) и подписи, конкатенированных через точку: <base64-encoded headers JSON>.<base64-encoded payload JSON>.<signature>. Раскодированный из base64 заголовок представляет собой JSON со следующей структурой:

{
    "alg": "ES256",         // алгоритм подписи
    "kid": "key-id",        // ID ключа, используется при определении публичного ключа
    "iat": "1639408318",    // UNIX время, когда токен был выпущен
    "exp": "1639429918",    // UNIX время, когда токен истекает, токен признается не валидным после наступления этого времени
    "typ": "JWT"            // тип токена
}

Полезная нагрузка также представляет собой JSON, конкретная структура полей которого определяется вызываемым методом. Подпись необходима для проверки валидности JWT-токена.

Важно

Перед десериализацией токена проверьте его валидность.

Алгоритм проверки валидности токена

Для проверки валидности JWT-токена удобнее воспользоваться одной из стандартных библиотек из списка: https://jwt.io/libraries. По возможности воздержитесь от ручной реализации данных проверок.

В общем случае алгоритм проверки JWT-токена с помощью библиотеки включает в себя:

  1. Проверку валидности подписи JWT-токена с помощью публичных JWK-ключей, размещенных по адресам: https://sandbox.pay.yandex.uz/api/jwks для тестового окружения и https://pay.yandex.uz/api/jwks для продакшн-окружения. Публичный ключ, используемый для валидации подписи конкретного JWT-токена, выбирается на основе требований alg и kid в заголовке самого токена.
  2. Проверку стандартных требований в заголовках JWT-токена: alg, typ, iat, exp и т.д.

Только в случае, если обе проверки прошли успешно, получатель может десериализовать JSON payload JWT-токена. После этого получатель должен дополнительно проверить, что merchantId в payload токена совпадает с ID продавца в Yandex Split. В противном случае токен не может быть использован.

Если любая из проверок завершилась неудачей, токен считается невалидным, а запрос — неаутентифицированным. Такой запрос должен быть отклонен. Код ошибки reasonCode зависит от причины:

  • TOKEN_EXPIRED — срок действия токена истек;
  • UNAUTHORIZED — не удалось проверить подпись токена;
  • OTHER — другие ошибки валидации.

Примеры ответов:

{
  "status": "fail",
  "reasonCode": "TOKEN_EXPIRED",
  "reason": "JWT token has expired"
}
{
  "status": "fail",
  "reasonCode": "UNAUTHORIZED",
  "reason": "Invalid token signature"
}
{
  "status": "fail",
  "reasonCode": "OTHER",
  "reason": "Invalid merchantId"
}

Кеширование публичных JWK-ключей

Допускается кеширование JWK-ключей, используемых для валидации JWT-токенов, на бэкенде магазина. Время жизни такого кеша необходимо устанавливать таким образом, чтобы это не приводило к ошибкам валидации токенов в случае ротации JWK-ключей на стороне Yandex Split. На практике некоторые библиотеки по умолчанию устанавливают время жизни такого кеша в 10 минут. В случае кеширования JWK-ключей, необходимо реализовать следующие сценарии проверок.

Сценарий А: Успешная валидация токена кешированными JWK-ключами.

  1. Приходит запрос с токеном, чей kid найден среди кешированных JWK-ключей, и время жизни кеша не истекло.
  2. Валидация подписи токена завершается успешно.
  3. Токен считается валидным.

Сценарий B: Неуспешная валидация токена кешированными JWK-ключами.

  1. Приходит запрос с токеном, чей kid найден среди кешированных JWK-ключей, и время жизни кеша не истекло.
  2. Валидация подписи токена завершается неудачей.
  3. Токен считается невалидным, запрос отклоняется.

Сценарий C: Валидация токена со сбросом кеша JWK-ключей.

  1. Приходит запрос с токеном, чей kid не найден среди кешированных JWK-ключей или время жизни кеша истекло.
  2. Продавец должен сбросить кеш JWK-ключей и запросить весь список активных JWK-ключей заново с соответствующего окружению адреса.
  3. Валидация продолжается по сценарию А или B.

Как и в случае с валидацией токена, рекомендуется использовать стандартные библиотеки и воздержаться от ручной реализации данных сценариев.

Пример кода валидации JWT-токена

Ниже приведен пример успешной валидации JWT-токена, выпущенного в тестовом окружении. Для валидации токенов в продакшн-окружении должны использоваться публичные ключи, доступные по адресу https://pay.yandex.uz/api/jwks.

import json
from urllib.request import urlopen

from jose import jwt

YANDEX_JWK_ENDPOINT = "https://sandbox.pay.yandex.uz/api/jwks"

JWT_TOKEN = (
"eyJhbGciOiJFUzI1NiIsImlhdCI6MTY1MDE5Njc1OCwia2lkIjoidGVzdC1rZXkiLCJ0eXAiOiJKV1QifQ.eyJjYXJ0Ijp7Iml0ZW1zIjpbeyJwcm9kdWN"
"0SWQiOiJwMSIsInF1YW50aXR5Ijp7ImNvdW50IjoiMSJ9fV19LCJjdXJyZW5jeUNvZGUiOiJSVUIiLCJtZXJjaGFudElkIjoiMjc2Y2YxZjEtZjhlZC00N"
"GZlLTg5ZTMtNWU0MTEzNDZkYThkIn0.YmQjHlh3ddLWgBexQ3QrwtbgAA3u1TVnBl1qnfMIvToBwinH3uH92KGB15m4NAQXdz5nhkjPZZu7RUStJt40PQ"
)

with urlopen(YANDEX_JWK_ENDPOINT) as response:
    public_jwks = json.load(response)

payload = jwt.decode(JWT_TOKEN, public_jwks, algorithms=["ES256"])
print(json.dumps(payload, indent=2))
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;

$sandboxJwk = 'https://sandbox.pay.yandex.uz/api/jwks';

$JWT_TOKEN =
"eyJhbGciOiJFUzI1NiIsImlhdCI6MTY1MDE5Njc1OCwia2lkIjoidGVzdC1rZXkiLCJ0eXAiOiJKV1QifQ.eyJjYXJ0Ijp7Iml0ZW1zIjpbeyJwcm9kdWN"
. "0SWQiOiJwMSIsInF1YW50aXR5Ijp7ImNvdW50IjoiMSJ9fV19LCJjdXJyZW5jeUNvZGUiOiJSVUIiLCJtZXJjaGFudElkIjoiMjc2Y2YxZjEtZjhlZC00N"
. "GZlLTg5ZTMtNWU0MTEzNDZkYThkIn0.YmQjHlh3ddLWgBexQ3QrwtbgAA3u1TVnBl1qnfMIvToBwinH3uH92KGB15m4NAQXdz5nhkjPZZu7RUStJt40PQ";

$client = new GuzzleHttp\Client();
$keysJson = $client->get($sandboxJwk)->getBody();
$keysData = json_decode($keysJson, JSON_OBJECT_AS_ARRAY);

$keys = JWK::parseKeySet($keysData);
$payload = JWT::decode($JWT_TOKEN, $keys);

print_r($payload);

Для работы примера необходимо установить зависимости:

composer require firebase/php-jwt
composer require guzzlehttp/guzzle

Решение проблем c вебхуками

Если нотификации не приходят, проверьте следующие моменты:

  • Неправильный адрес бэкенда

    Нотификации могут приходить не туда, куда ожидаете. Указывайте Callback URL без /v1/webhook — этот путь добавится автоматически, например:

    Callback URL

    Куда придет запрос

    https://example.merchant.uz

    https://example.merchant.uz/v1/webhook

    https://example.merchant.uz/v1/webhook

    https://example.merchant.uz/v1/webhook/v1/webhook

  • Обработка Content-Type

    Убедитесь, что бэкенд вашего магазина готов принимать сообщения с заголовком Content-Type: application/octet-stream.

  • SSL-сертификат

    Система не распознает самоподписанные SSL-сертификаты. Используйте сертификат от доверенного центра сертификации.

  • Настройки брандмауэра

    Проверьте, что брандмауэр не блокирует входящие запросы и не обрезает тело запроса.

Endpoints

Specification

Open API
{
    "components": {
        "schemas": {
            "MerchantErrorResponse": {
                "properties": {
                    "reason": {
                        "description": "Описание причины ошибки.",
                        "type": "string"
                    },
                    "reasonCode": {
                        "description": "Код ошибки:\n\n- `FORBIDDEN` — заказ существует, но был оплачен не через Yandex Split;\n- `ORDER_NOT_FOUND` — заказ не найден в системе продавца;\n- `ORDER_AMOUNT_MISMATCH` — сумма заказа не совпадает с суммой в системе продавца;\n- `ORDER_DETAILS_MISMATCH` — детали заказа отличаются от данных в системе продавца;\n- `OTHER` — общая ошибка;\n- `UNAUTHORIZED` — не удалось проверить подпись JWT-токена;\n- `TOKEN_EXPIRED` — срок действия JWT-токена истек;\n- `CONFLICT` — данные в нотификации расходятся с состоянием заказа в системе продавца. Например, пришла нотификация об оплате для отмененного заказа.",
                        "enum": [
                            "FORBIDDEN",
                            "ITEM_NOT_FOUND",
                            "ORDER_NOT_FOUND",
                            "ORDER_AMOUNT_MISMATCH",
                            "ORDER_DETAILS_MISMATCH",
                            "OUT_OF_INVENTORY",
                            "PICKUP_POINT_NOT_FOUND",
                            "SHIPPING_DETAILS_MISMATCH",
                            "OTHER",
                            "UNAUTHORIZED",
                            "TOKEN_EXPIRED",
                            "CONFLICT"
                        ],
                        "type": "string"
                    },
                    "status": {
                        "default": "fail",
                        "type": "string"
                    }
                },
                "required": [
                    "reasonCode"
                ],
                "type": "object"
            },
            "MerchantSuccessResponse": {
                "properties": {
                    "status": {
                        "default": "success",
                        "type": "string"
                    }
                },
                "type": "object"
            },
            "MerchantWebhookV1Request": {
                "properties": {
                    "event": {
                        "description": "Тип события:\n- `ORDER_STATUS_UPDATED` — обновление статуса заказа;\n- `OPERATION_STATUS_UPDATED` — обновление статуса операций списания, возврата или отмены платежа.",
                        "enum": [
                            "TRANSACTION_STATUS_UPDATE",
                            "ORDER_STATUS_UPDATED",
                            "OPERATION_STATUS_UPDATED",
                            "SUBSCRIPTION_STATUS_UPDATED"
                        ],
                        "type": "string"
                    },
                    "eventTime": {
                        "description": "Время события в формате `RFC 3339`: `YYYY-MM-DDThh:mm:ssTZD`.",
                        "example": "2025-05-26T21:00:36.08847+00:00",
                        "format": "date-time",
                        "type": "string"
                    },
                    "merchantId": {
                        "description": "ID (идентификатор) продавца.",
                        "format": "uuid",
                        "type": "string"
                    },
                    "operation": {
                        "allOf": [
                            {
                                "properties": {
                                    "externalOperationId": {
                                        "description": "Идентификатор операции в системе продавца. Должен быть уникальным.\n\nПередайте этот параметр, чтобы отслеживать конкретную операцию через метод [v1/operations/{external_operation_id}](../yandex-pay-api/operation/merchant_v1_operations-get).",
                                        "type": "string"
                                    },
                                    "operationId": {
                                        "description": "Идентификатор операции.",
                                        "example": "5d32f295-8723-457d-81f9-ab13f17b7bd6",
                                        "format": "uuid",
                                        "type": "string"
                                    },
                                    "operationType": {
                                        "description": "Тип операции. Подробнее о типах операций читайте в разделе [Статусы операций](../../../payments/statuses).",
                                        "enum": [
                                            "AUTHORIZE",
                                            "BIND_CARD",
                                            "REFUND",
                                            "CAPTURE",
                                            "VOID",
                                            "RECURRING",
                                            "PREPAYMENT",
                                            "SUBMIT"
                                        ],
                                        "type": "string"
                                    },
                                    "orderId": {
                                        "description": "ID заказа, переданный в [/v1/orders](../yandex-pay-api/order/merchant_v1_orders-post.md) при создании заказа.",
                                        "type": "string"
                                    },
                                    "status": {
                                        "description": "Статус операции. Подробнее о статусах операций читайте в разделе [Статусы операций](../../../payments/statuses).",
                                        "enum": [
                                            "PENDING",
                                            "SUCCESS",
                                            "FAIL"
                                        ],
                                        "type": "string"
                                    }
                                },
                                "required": [
                                    "operationId",
                                    "operationType",
                                    "orderId",
                                    "status"
                                ],
                                "type": "object"
                            }
                        ],
                        "description": "Информация по операции. Приходит с событием `OPERATION_STATUS_UPDATED`"
                    },
                    "order": {
                        "allOf": [
                            {
                                "properties": {
                                    "cartUpdated": {
                                        "description": "Была ли обновлена корзина. Возвращается при оплате баллами.Если флаг имеет значение `true`, получите актуальную корзину.",
                                        "type": "boolean"
                                    },
                                    "orderId": {
                                        "description": "ID заказа, переданный в [/v1/orders](../yandex-pay-api/order/merchant_v1_orders-post.md) при создании заказа.",
                                        "type": "string"
                                    },
                                    "paymentStatus": {
                                        "description": "Статус заказа. Подробнее читайте в разделе [Статус заказа](../../../payments/statuses.md).",
                                        "enum": [
                                            "PENDING",
                                            "AUTHORIZED",
                                            "CAPTURED",
                                            "VOIDED",
                                            "REFUNDED",
                                            "CONFIRMED",
                                            "PARTIALLY_REFUNDED",
                                            "FAILED"
                                        ],
                                        "type": "string",
                                        "x-enumDescriptions": {
                                            "AUTHORIZED": "Платеж за заказ авторизован. Средства заблокированы на счету плательщика",
                                            "CAPTURED": "Заказ успешно оплачен. Средства списаны со счета плательщика",
                                            "CONFIRMED": "Заказ успешно оформлен",
                                            "FAILED": "Заказ не был успешно оплачен",
                                            "PARTIALLY_REFUNDED": "Совершён частичный возврат средств за заказ",
                                            "PENDING": "Ожидается оплата",
                                            "REFUNDED": "Совершён возврат средств за заказ",
                                            "VOIDED": "Оплата отменена (voided). Списание средств не производилось"
                                        }
                                    }
                                },
                                "required": [
                                    "orderId",
                                    "paymentStatus"
                                ],
                                "type": "object"
                            }
                        ],
                        "description": "Информация по заказу. Приходит с событием `ORDER_STATUS_UPDATED`"
                    },
                    "subscription": {
                        "allOf": [
                            {
                                "properties": {
                                    "customerSubscriptionId": {
                                        "description": "ID подписки. Возвращается из SDK при успешном создании подписки. Также можно сохранить подписку при получении первой нотификации по ней. Дальнейшие обновления по этой подписке будут приходить с таким же значением этого поля.",
                                        "format": "uuid",
                                        "type": "string"
                                    },
                                    "nextWriteOff": {
                                        "description": "Дата следующей попытки списания денег по подписке",
                                        "format": "date-time",
                                        "type": "string"
                                    },
                                    "status": {
                                        "description": "Статус подписки",
                                        "enum": [
                                            "NEW",
                                            "ACTIVE",
                                            "CANCELLED",
                                            "EXPIRED"
                                        ],
                                        "type": "string"
                                    },
                                    "subscriptionPlanId": {
                                        "description": "ID плана подписки, созданного в личном кабинете или через API.",
                                        "format": "uuid",
                                        "type": "string"
                                    }
                                },
                                "required": [
                                    "customerSubscriptionId",
                                    "status",
                                    "subscriptionPlanId"
                                ],
                                "type": "object"
                            }
                        ],
                        "description": "Состояние подписки."
                    }
                },
                "required": [
                    "event",
                    "eventTime",
                    "merchantId"
                ],
                "type": "object"
            },
            "OperationWebhookData": {
                "properties": {
                    "externalOperationId": {
                        "description": "Идентификатор операции в системе продавца. Должен быть уникальным.\n\nПередайте этот параметр, чтобы отслеживать конкретную операцию через метод [v1/operations/{external_operation_id}](../yandex-pay-api/operation/merchant_v1_operations-get).",
                        "type": "string"
                    },
                    "operationId": {
                        "description": "Идентификатор операции.",
                        "example": "5d32f295-8723-457d-81f9-ab13f17b7bd6",
                        "format": "uuid",
                        "type": "string"
                    },
                    "operationType": {
                        "description": "Тип операции. Подробнее о типах операций читайте в разделе [Статусы операций](../../../payments/statuses).",
                        "enum": [
                            "AUTHORIZE",
                            "BIND_CARD",
                            "REFUND",
                            "CAPTURE",
                            "VOID",
                            "RECURRING",
                            "PREPAYMENT",
                            "SUBMIT"
                        ],
                        "type": "string"
                    },
                    "orderId": {
                        "description": "ID заказа, переданный в [/v1/orders](../yandex-pay-api/order/merchant_v1_orders-post.md) при создании заказа.",
                        "type": "string"
                    },
                    "status": {
                        "description": "Статус операции. Подробнее о статусах операций читайте в разделе [Статусы операций](../../../payments/statuses).",
                        "enum": [
                            "PENDING",
                            "SUCCESS",
                            "FAIL"
                        ],
                        "type": "string"
                    }
                },
                "required": [
                    "operationId",
                    "operationType",
                    "orderId",
                    "status"
                ],
                "type": "object"
            },
            "OrderWebhookData": {
                "properties": {
                    "cartUpdated": {
                        "description": "Была ли обновлена корзина. Возвращается при оплате баллами.Если флаг имеет значение `true`, получите актуальную корзину.",
                        "type": "boolean"
                    },
                    "orderId": {
                        "description": "ID заказа, переданный в [/v1/orders](../yandex-pay-api/order/merchant_v1_orders-post.md) при создании заказа.",
                        "type": "string"
                    },
                    "paymentStatus": {
                        "description": "Статус заказа. Подробнее читайте в разделе [Статус заказа](../../../payments/statuses.md).",
                        "enum": [
                            "PENDING",
                            "AUTHORIZED",
                            "CAPTURED",
                            "VOIDED",
                            "REFUNDED",
                            "CONFIRMED",
                            "PARTIALLY_REFUNDED",
                            "FAILED"
                        ],
                        "type": "string",
                        "x-enumDescriptions": {
                            "AUTHORIZED": "Платеж за заказ авторизован. Средства заблокированы на счету плательщика",
                            "CAPTURED": "Заказ успешно оплачен. Средства списаны со счета плательщика",
                            "CONFIRMED": "Заказ успешно оформлен",
                            "FAILED": "Заказ не был успешно оплачен",
                            "PARTIALLY_REFUNDED": "Совершён частичный возврат средств за заказ",
                            "PENDING": "Ожидается оплата",
                            "REFUNDED": "Совершён возврат средств за заказ",
                            "VOIDED": "Оплата отменена (voided). Списание средств не производилось"
                        }
                    }
                },
                "required": [
                    "orderId",
                    "paymentStatus"
                ],
                "type": "object"
            },
            "SubscriptionWebhookData": {
                "properties": {
                    "customerSubscriptionId": {
                        "description": "ID подписки. Возвращается из SDK при успешном создании подписки. Также можно сохранить подписку при получении первой нотификации по ней. Дальнейшие обновления по этой подписке будут приходить с таким же значением этого поля.",
                        "format": "uuid",
                        "type": "string"
                    },
                    "nextWriteOff": {
                        "description": "Дата следующей попытки списания денег по подписке",
                        "format": "date-time",
                        "type": "string"
                    },
                    "status": {
                        "description": "Статус подписки",
                        "enum": [
                            "NEW",
                            "ACTIVE",
                            "CANCELLED",
                            "EXPIRED"
                        ],
                        "type": "string"
                    },
                    "subscriptionPlanId": {
                        "description": "ID плана подписки, созданного в личном кабинете или через API.",
                        "format": "uuid",
                        "type": "string"
                    }
                },
                "required": [
                    "customerSubscriptionId",
                    "status",
                    "subscriptionPlanId"
                ],
                "type": "object"
            }
        }
    },
    "info": {
        "description": "Для работы Merchant API необходим публичный адрес вашего бэкенда. [Укажите адрес](../../../console/settings-pay-split.md#callback-settings) в поле **Callback URL** в консоли Yandex Split на [странице разработчика](https://console.pay.yandex.uz/settings).\n\nВаш бэкенд должен проверять подлинность запроса, прежде чем приступать к его обработке.\n\n{% note warning %}\n\nНе привязывайте логику к конкретным IP-адресам — они могут измениться. Для проверки подлинности используйте [валидацию JWT-токена](#validate) с публичными ключами.\n\n{% endnote %}\n\n## Аутентификация запроса Яндекса {#auth}\n\nВ теле запроса Яндекс присылает JWT-токен, подписанный по алгоритму ES256.\nТокен состоит из трех частей: заголовка (header), полезной нагрузки (payload) и подписи,\nконкатенированных через точку: `<base64-encoded headers JSON>.<base64-encoded payload JSON>.<signature>`.\nРаскодированный из base64 заголовок представляет собой JSON со следующей структурой:\n\n```json\n{\n    \"alg\": \"ES256\",         // алгоритм подписи\n    \"kid\": \"key-id\",        // ID ключа, используется при определении публичного ключа\n    \"iat\": \"1639408318\",    // UNIX время, когда токен был выпущен\n    \"exp\": \"1639429918\",    // UNIX время, когда токен истекает, токен признается не валидным после наступления этого времени\n    \"typ\": \"JWT\"            // тип токена\n}\n```\n\nПолезная нагрузка также представляет собой JSON, конкретная структура полей которого определяется\nвызываемым методом. Подпись необходима для проверки валидности JWT-токена.\n\n{% note warning %}\n\nПеред десериализацией токена проверьте его валидность.\n\n{% endnote %}\n\n### Алгоритм проверки валидности токена {#validate}\n\nДля проверки валидности JWT-токена удобнее воспользоваться одной из стандартных библиотек\nиз списка: <https://jwt.io/libraries>. По возможности воздержитесь от ручной реализации данных проверок.\n\nВ общем случае алгоритм проверки JWT-токена с помощью библиотеки включает в себя:\n\n1. Проверку валидности подписи JWT-токена с помощью публичных JWK-ключей, размещенных по адресам:\n<https://sandbox.pay.yandex.uz/api/jwks> для тестового окружения и <https://pay.yandex.uz/api/jwks>\nдля продакшн-окружения. Публичный ключ, используемый для валидации подписи конкретного JWT-токена,\nвыбирается на основе требований `alg` и `kid` в заголовке самого токена.\n1. Проверку стандартных требований в заголовках JWT-токена: `alg`, `typ`, `iat`, `exp` и т.д.\n\nТолько в случае, если обе проверки прошли успешно, получатель может десериализовать JSON payload JWT-токена. После этого получатель должен дополнительно проверить, что `merchantId` в payload токена совпадает с ID продавца в Yandex Split. В противном случае токен не может быть использован.\n\nЕсли любая из проверок завершилась неудачей, токен считается невалидным, а запрос — неаутентифицированным. Такой запрос должен быть отклонен. Код ошибки `reasonCode` зависит от причины:\n\n- `TOKEN_EXPIRED` — срок действия токена истек;\n- `UNAUTHORIZED` — не удалось проверить подпись токена;\n- `OTHER` — другие ошибки валидации.\n\nПримеры ответов:\n\n```json\n{\n  \"status\": \"fail\",\n  \"reasonCode\": \"TOKEN_EXPIRED\",\n  \"reason\": \"JWT token has expired\"\n}\n```\n\n```json\n{\n  \"status\": \"fail\",\n  \"reasonCode\": \"UNAUTHORIZED\",\n  \"reason\": \"Invalid token signature\"\n}\n```\n\n```json\n{\n  \"status\": \"fail\",\n  \"reasonCode\": \"OTHER\",\n  \"reason\": \"Invalid merchantId\"\n}\n```\n\n#### Кеширование публичных JWK-ключей {#jwk-cache}\n\nДопускается кеширование JWK-ключей, используемых для валидации JWT-токенов, на бэкенде магазина.\nВремя жизни такого кеша необходимо устанавливать таким образом, чтобы это не приводило к ошибкам валидации\nтокенов в случае ротации JWK-ключей на стороне Yandex Split. На практике\n[некоторые библиотеки](https://github.com/auth0/node-jwks-rsa#caching) по умолчанию устанавливают\nвремя жизни такого кеша в 10 минут. В случае кеширования JWK-ключей, необходимо реализовать следующие\nсценарии проверок.\n\n**Сценарий А:** Успешная валидация токена кешированными JWK-ключами.\n\n1. Приходит запрос с токеном, чей `kid` найден среди кешированных JWK-ключей, и время жизни кеша не истекло.\n1. Валидация подписи токена завершается успешно.\n1. Токен считается валидным.\n\n**Сценарий B:** Неуспешная валидация токена кешированными JWK-ключами.\n\n1. Приходит запрос с токеном, чей `kid` найден среди кешированных JWK-ключей, и время жизни кеша не истекло.\n1. Валидация подписи токена завершается неудачей.\n1. Токен считается невалидным, запрос отклоняется.\n\n**Сценарий C:** Валидация токена со сбросом кеша JWK-ключей.\n\n1. Приходит запрос с токеном, чей `kid` не найден среди кешированных JWK-ключей или время жизни кеша истекло.\n1. Продавец должен сбросить кеш JWK-ключей и запросить весь список активных JWK-ключей заново с соответствующего окружению адреса.\n1. Валидация продолжается по сценарию А или B.\n\nКак и в случае с валидацией токена, рекомендуется использовать стандартные библиотеки и воздержаться от\nручной реализации данных сценариев.\n\n### Пример кода валидации JWT-токена {#jwk-verify-example}\n\nНиже приведен пример успешной валидации JWT-токена, выпущенного в тестовом окружении.\nДля валидации токенов в продакшн-окружении должны использоваться публичные ключи, доступные по адресу `https://pay.yandex.uz/api/jwks`.\n\n{% list tabs %}\n\n- Python\n\n    ```python\n    import json\n    from urllib.request import urlopen\n\n    from jose import jwt\n\n    YANDEX_JWK_ENDPOINT = \"https://sandbox.pay.yandex.uz/api/jwks\"\n\n    JWT_TOKEN = (\n    \"eyJhbGciOiJFUzI1NiIsImlhdCI6MTY1MDE5Njc1OCwia2lkIjoidGVzdC1rZXkiLCJ0eXAiOiJKV1QifQ.eyJjYXJ0Ijp7Iml0ZW1zIjpbeyJwcm9kdWN\"\n    \"0SWQiOiJwMSIsInF1YW50aXR5Ijp7ImNvdW50IjoiMSJ9fV19LCJjdXJyZW5jeUNvZGUiOiJSVUIiLCJtZXJjaGFudElkIjoiMjc2Y2YxZjEtZjhlZC00N\"\n    \"GZlLTg5ZTMtNWU0MTEzNDZkYThkIn0.YmQjHlh3ddLWgBexQ3QrwtbgAA3u1TVnBl1qnfMIvToBwinH3uH92KGB15m4NAQXdz5nhkjPZZu7RUStJt40PQ\"\n    )\n\n    with urlopen(YANDEX_JWK_ENDPOINT) as response:\n        public_jwks = json.load(response)\n\n    payload = jwt.decode(JWT_TOKEN, public_jwks, algorithms=[\"ES256\"])\n    print(json.dumps(payload, indent=2))\n    ```\n\n- PHP\n\n    ```php\n    use Firebase\\JWT\\JWT;\n    use Firebase\\JWT\\JWK;\n\n    $sandboxJwk = 'https://sandbox.pay.yandex.uz/api/jwks';\n\n    $JWT_TOKEN =\n    \"eyJhbGciOiJFUzI1NiIsImlhdCI6MTY1MDE5Njc1OCwia2lkIjoidGVzdC1rZXkiLCJ0eXAiOiJKV1QifQ.eyJjYXJ0Ijp7Iml0ZW1zIjpbeyJwcm9kdWN\"\n    . \"0SWQiOiJwMSIsInF1YW50aXR5Ijp7ImNvdW50IjoiMSJ9fV19LCJjdXJyZW5jeUNvZGUiOiJSVUIiLCJtZXJjaGFudElkIjoiMjc2Y2YxZjEtZjhlZC00N\"\n    . \"GZlLTg5ZTMtNWU0MTEzNDZkYThkIn0.YmQjHlh3ddLWgBexQ3QrwtbgAA3u1TVnBl1qnfMIvToBwinH3uH92KGB15m4NAQXdz5nhkjPZZu7RUStJt40PQ\";\n\n    $client = new GuzzleHttp\\Client();\n    $keysJson = $client->get($sandboxJwk)->getBody();\n    $keysData = json_decode($keysJson, JSON_OBJECT_AS_ARRAY);\n\n    $keys = JWK::parseKeySet($keysData);\n    $payload = JWT::decode($JWT_TOKEN, $keys);\n\n    print_r($payload);\n    ```\n\n    Для работы примера необходимо установить зависимости:\n\n    ```bash\n    composer require firebase/php-jwt\n    composer require guzzlehttp/guzzle\n    ```\n\n{% endlist %}\n\n## Решение проблем c вебхуками {#troubleshooting}\n\nЕсли нотификации не приходят, проверьте следующие моменты:\n\n- Неправильный адрес бэкенда\n\n    Нотификации могут приходить не туда, куда ожидаете. [Указывайте Callback URL](../../../console/settings-pay-split.md#callback-settings) без `/v1/webhook` — этот путь добавится автоматически, например:\n\n    #|\n    || **Callback URL** | **Куда придет запрос** ||\n    || `https://example.merchant.uz` | `https://example.merchant.uz/v1/webhook` ||\n    || `https://example.merchant.uz/v1/webhook` | `https://example.merchant.uz/v1/webhook/v1/webhook` ||\n    |#\n\n- Обработка `Content-Type`\n\n    Убедитесь, что бэкенд вашего магазина готов принимать сообщения с заголовком `Content-Type: application/octet-stream`.\n\n- SSL-сертификат\n\n    Система не распознает самоподписанные SSL-сертификаты. Используйте сертификат от доверенного центра сертификации.\n\n- Настройки брандмауэра\n\n    Проверьте, что брандмауэр не блокирует входящие запросы и не обрезает тело запроса.",
        "title": "Merchant API",
        "version": "1.0.0"
    },
    "openapi": "3.0.3",
    "paths": {
        "/v1/webhook": {
            "post": {
                "description": "Нотификации об изменении статуса.\n\nЗапрос отправляется при изменении статуса заказа или операции по заказу.\n\nПоддерживаемые события:\n\n- `ORDER_STATUS_UPDATED` — обновление статуса заказа;\n- `OPERATION_STATUS_UPDATED` — обновление статуса операций списания, возврата или отмены платежа.\n<!-- - `SUBSCRIPTION_STATUS_UPDATED` — сейчас этот статус не отдаем? -->\n\n## Формат запроса {#webhook-format}\n\nЗапрос приходит в формате `application/octet-stream` в виде JWT-токена, подписанного по алгоритму ES256. Перед обработкой запроса проверьте его подлинность. Как это сделать, читайте в разделе [Аутентификация](../merchant-api/index.md).\n\nPayload проверенного и декодированного JWT-токена содержит JSON с данными события. Посмотрите [примеры событий](#webhook-examples).\n\n{% note warning \"Если в токене нет тела запроса\" %}\n\n- Убедитесь, что бэкенд вашего магазина готов принимать сообщение с заголовком `Content-Type: application/octet-stream`.\n- Проверьте, что брандмауэр не блокирует входящие запросы и не обрезает тело запроса.\n\nДругие ошибки смотрите в разделе [Решение проблем c вебхуками](../merchant-api/index.md#troubleshooting).\n\n{% endnote %}\n\n## Идемпотентность операций {#idempotency}\n\nПри действиях с заказом, например, при возврате средств методом [/v2/orders/{order_id}/refund](../yandex-pay-api/order/merchant_v2_refund-post.md), передавайте уникальный идентификатор операции `externalOperationId`.\n\nС помощью него вы сможете:\n- понять, по какой операции пришла нотификация;\n- проверить состояние операции через метод [v1/operations/{external_operation_id}](../yandex-pay-api/operation/merchant_v1_operations-get.md);\n- защититься от дублирования.\n\nЕсли отправите запрос повторно с тем же `externalOperationId`, то получите:\n- информацию о текущей операции, если она в процессе;\n- ошибку c `reasonCode: \"DUPLICATE_EXTERNAL_OPERATION_ID\"`, если операция завершена.\n\n## Примеры событий {#webhook-examples}\n\n### Оплата заказа\n\n{% list tabs %}\n\n- Успех\n\n  ```json\n  {\n    \"merchantId\": \"xxxxxxxxx-xxx-5xxx-xxxxx-xxxxxxxx\",\n    \"event\": \"ORDER_STATUS_UPDATED\",\n    \"eventTime\": \"2023-11-26T08:11:09.359370+00:00\",\n    \"order\": {\n      \"orderId\": \"700aa3f04df64b3b8712d6b51f752e8b\",\n      \"paymentStatus\": \"CAPTURED\"\n    }\n  }\n  ```\n\n  Посмотрите пример JWT-токена на [jwt.io](https://jwt.io/#debugger-io?token=eyJhbGciOiJFUzI1NiIsImV4cCI6MTcwMDk4NzYwMCwiaWF0IjoxNzAwOTg3MzAwLCJraWQiOiIxLW1lcmNoYW50LWFwaSIsInR5cCI6IkpXVCJ9.eyJtZXJjaGFudElkIjoieHh4eHh4eHh4LXh4eC01eHh4LXh4eHh4LXh4eHh4eHh4IiwiZXZlbnQiOiJPUkRFUl9TVEFUVVNfVVBEQVRFRCIsImV2ZW50VGltZSI6IjIwMjMtMTEtMjZUMDg6MTE6MDkuMzU5MzcwKzAwOjAwIiwib3JkZXIiOnsib3JkZXJJZCI6IjcwMGFhM2YwNGRmNjRiM2I4NzEyZDZiNTFmNzUyZThiIiwicGF5bWVudFN0YXR1cyI6IkNBUFRVUkVEIn19.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx).\n\n  **Пример запроса от Яндекса в бэкенд магазина:**\n\n  ```(bash)\n    curl -X POST https://test.uz/some/prefix/v1/webhook \\\n      --header 'User-Agent: YandexPay/1.0' \\\n      --header 'Accept: \\*/\\*' \\\n      --header 'Content-Type: application/octet-stream' \\\n      --header 'X-Request-Id: ff2a54885c4e45309853d2e33af1d63b\\\\_3a70f3062db640fcb2f3c34de1a27bd5' \\\n      --header 'X-Request-Timeout: 13970' \\\n      --compressed \\\n      -d eyJhbGciOiJFUzI1NiIsImV4cCI6MTcwMDk4NzYwMCwiaWF0IjoxNzAwOTg3MzAwLCJraWQiOiIxLW1lcmNoYW50LWFwaSIsInR5cCI6IkpXVCJ9.eyJtZXJjaGFudElkIjoieHh4eHh4eHh4LXh4eC01eHh4LXh4eHh4LXh4eHh4eHh4IiwiZXZlbnQiOiJPUkRFUl9TVEFUVVNfVVBEQVRFRCIsImV2ZW50VGltZSI6IjIwMjMtMTEtMjZUMDg6MTE6MDkuMzU5MzcwKzAwOjAwIiwib3JkZXIiOnsib3JkZXJJZCI6IjcwMGFhM2YwNGRmNjRiM2I4NzEyZDZiNTFmNzUyZThiIiwicGF5bWVudFN0YXR1cyI6IkNBUFRVUkVEIn19.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n  ```\n\n- Неудача\n\n  ```json\n  {\n    \"merchantId\": \"xxxxxxxxx-xxx-5xxx-xxxxx-xxxxxxxx\",\n    \"event\": \"ORDER_STATUS_UPDATED\",\n    \"eventTime\": \"2024-04-25T07:56:29.974810+00:00\",\n    \"order\": {\n      \"orderId\": \"253222_1714029088\",\n      \"paymentStatus\": \"FAILED\"\n    }\n  }\n  ```\n\n  Посмотрите пример JWT-токена на [jwt.io](https://jwt.io/#debugger-io?token=eyJhbGciOiJFUzI1NiIsImV4cCI6MTcxNDAzMjIyMSwiaWF0IjoxNzE0MDMxOTIxLCJraWQiOiIxLW1lcmNoYW50LWFwaSIsInR5cCI6IkpXVCJ9.eyJtZXJjaGFudElkIjoieHh4eHh4eHh4LXh4eC01eHh4LXh4eHh4LXh4eHh4eHh4IiwiZXZlbnQiOiJPUkRFUl9TVEFUVVNfVVBEQVRFRCIsImV2ZW50VGltZSI6IjIwMjQtMDQtMjVUMDc6NTY6MjkuOTc0ODEwKzAwOjAwIiwib3JkZXIiOnsib3JkZXJJZCI6IjI1MzIyMl8xNzE0MDI5MDg4IiwicGF5bWVudFN0YXR1cyI6IkZBSUxFRCJ9fQ.v9dw_cR3_b4R5v0D8WRisrSPABxhegSSpEq4kz9s10fr5cUK150yWnwJREYCGQCm5BZK1Yydsquh-WE6OyRR2APOST).\n\n{% endlist %}\n\n### Возврат\n\n{% note tip %}\n\nСначала изучите, как работают возвраты, в разделе [/v2/orders/{order_id}/refund](../yandex-pay-api/order/merchant_v2_refund-post.md).\n\n{% endnote %}\n\n#### Полный возврат\n\nНезависимо от того, меняется ли статус заказа, отправляется 2 нотификации: по операции и по заказу.\n\n{% list tabs %}\n\n- Успех\n\n  1. `OPERATION_STATUS_UPDATED` — операция возврата завершилась успешно:\n\n      ```json\n      {\n        \"merchantId\": \"xxxxxxxxx-xxx-5xxx-xxxxx-xxxxxxxx\",\n        \"event\": \"OPERATION_STATUS_UPDATED\",\n        \"eventTime\": \"2024-04-19T10:27:53.323878+00:00\",\n        \"operation\": {\n          \"operationId\": \"73dec2cd-db5c-4386-be6d-10c5b5a2ee09\",\n          \"orderId\": \"86283\",\n          \"status\": \"SUCCESS\",\n          \"operationType\": \"REFUND\"\n        }\n      }\n      ```\n\n  2. `ORDER_STATUS_UPDATED` — заказ в перешел в терминальный статус `REFUNDED`. Больше нельзя вызывать возвраты.\n\n      ```json\n      {\n        \"merchantId\": \"xxxxxxxxx-xxx-5xxx-xxxxx-xxxxxxxx\",\n        \"event\": \"ORDER_STATUS_UPDATED\",\n        \"eventTime\": \"2024-04-19T12:16:28.766392+00:00\",\n        \"order\": {\n          \"orderId\": \"86283\",\n          \"paymentStatus\": \"REFUNDED\"\n        }\n      }\n      ```\n\n      Посмотрите пример JWT-токена на [jwt.io](https://jwt.io/#debugger-io?token=eyJhbGciOiJFUzI1NiIsImV4cCI6MTcxMzUyOTI4OSwiaWF0IjoxNzEzNTI4OTg5LCJraWQiOiIxLW1lcmNoYW50LWFwaSIsInR5cCI6IkpXVCJ9.eyJtZXJjaGFudElkIjoieHh4eHh4eHh4LXh4eC01eHh4LXh4eHh4LXh4eHh4eHh4IiwiZXZlbnQiOiJPUkRFUl9TVEFUVVNfVVBEQVRFRCIsImV2ZW50VGltZSI6IjIwMjQtMDQtMTlUMTI6MTY6MjguNzY2MzkyKzAwOjAwIiwib3JkZXIiOnsib3JkZXJJZCI6Ijg2MjgzIiwicGF5bWVudFN0YXR1cyI6IlJFRlVOREVEIn19.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx).\n\n- Неудача\n\n  1. `OPERATION_STATUS_UPDATED` — операция возврата завершилась неуспешно:\n\n      ```json\n      {\n        \"merchantId\": \"xxxxxxxxx-xxx-5xxx-xxxxx-xxxxxxxx\",\n        \"event\": \"OPERATION_STATUS_UPDATED\",\n        \"eventTime\": \"2024-06-13T22:27:53.323878+00:00\",\n        \"operation\": {\n          \"operationId\": \"73dec2cd-db5c-4386-be6d-10c5b5a2ee08\",\n          \"orderId\": \"9c8aed6d-a8e5-4c6a-acd8-645538173f66\",\n          \"status\": \"FAIL\",\n          \"operationType\": \"REFUND\"\n        }\n      }\n      ```\n\n      Посмотрите пример JWT-токена на [jwt.io](https://jwt.io/#debugger-io?token=eyJhbGciOiJFUzI1NiIsImV4cCI6MTcxODMxNzk3NCwiaWF0IjoxNzE4MzE3Njc0LCJraWQiOiIxLW1lcmNoYW50LWFwaSIsInR5cCI6IkpXVCJ9.eyJtZXJjaGFudElkIjoieHh4eHh4eHh4LXh4eC01eHh4LXh4eHh4LXh4eHh4eHh4IiwiZXZlbnQiOiJPUEVSQVRJT05fU1RBVFVTX1VQREFURUQiLCJldmVudFRpbWUiOiIyMDI0LTA2LTEzVDIyOjI3OjUzLjMyMzg3OCswMDowMCIsIm9wZXJhdGlvbiI6eyJvcGVyYXRpb25JZCI6IjczZGVjMmNkLWRiNWMtNDM4Ni1iZTZkLTEwYzViNWEyZWUwOCIsIm9yZGVySWQiOiI5YzhhZWQ2ZC1hOGU1LTRjNmEtYWNkOC02NDU1MzgxNzNmNjYiLCJzdGF0dXMiOiJGQUlMIiwib3BlcmF0aW9uVHlwZSI6IlJFRlVORCJ9fQ.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx).\n\n  2. `ORDER_STATUS_UPDATED` — заказ остался в предыдущем статусе `CAPTURED`:\n\n      ```json\n      {\n        \"merchantId\": \"xxxxxxxxx-xxx-5xxx-xxxxx-xxxxxxxx\",\n        \"event\": \"ORDER_STATUS_UPDATED\",\n        \"eventTime\": \"2024-06-13T22:27:54.323878+00:00\",\n        \"order\": {\n          \"orderId\": \"9c8aed6d-a8e5-4c6a-acd8-645538173f66\",\n          \"paymentStatus\": \"CAPTURED\"\n        }\n      }\n      ```\n\n{% endlist %}\n\n#### Частичный возврат\n\nВы можете вернуть всю сумму заказа несколькими частичными возвратами. Когда сумма всех возвратов достигнет полной стоимости, заказ перейдет в терминальный статус `REFUNDED`. После этого нельзя вызывать возвраты.\n\nНезависимо от того, меняется ли статус заказа, отправляется 2 нотификации: по операции и по заказу.\n\nРассмотрим на примере заказа с тремя пачками сока.\n\n1. Совершили частичный возврат одного сока. Вам придет 2 нотификации:\n\n    1. `OPERATION_STATUS_UPDATED` — операция возврата завершилась успешно:\n\n        ```json\n        {\n          \"merchantId\": \"xxxxxxxxx-xxx-5xxx-xxxxx-xxxxxxxx\",\n          \"event\": \"OPERATION_STATUS_UPDATED\",\n          \"eventTime\": \"2024-04-19T10:27:53.323878+00:00\",\n          \"operation\": {\n            \"operationId\": \"73dec3cs-sd5t-4356-ne6d-10c79b5d2ee09\",\n            \"externalOperationId\": \"123-partial-refund-1\",\n            \"orderId\": \"123\",\n            \"status\": \"SUCCESS\",\n            \"operationType\": \"REFUND\"\n          }\n        }\n        ```\n\n    2. `ORDER_STATUS_UPDATED` — заказ в перешел в статус `PARTIALLY_REFUNDED`:\n\n        ```json\n        {\n          \"merchantId\": \"xxxxxxxxx-xxx-5xxx-xxxxx-xxxxxxxx\",\n          \"event\": \"ORDER_STATUS_UPDATED\",\n          \"eventTime\": \"2024-04-19T12:16:28.766392+00:00\",\n          \"order\": {\n            \"orderId\": \"123\",\n            \"paymentStatus\": \"PARTIALLY_REFUNDED\"\n          }\n        }\n        ```\n\n2. Совершили второй частичный возврат одного сока. Вам придет 2 нотификации:\n\n    1. `OPERATION_STATUS_UPDATED` — операция возврата завершилась успешно:\n\n        ```json\n        {\n          \"merchantId\": \"xxxxxxxxx-xxx-5xxx-xxxxx-xxxxxxxx\",\n          \"event\": \"OPERATION_STATUS_UPDATED\",\n          \"eventTime\": \"2024-04-19T13:27:53.323878+00:00\",\n          \"operation\": {\n            \"operationId\": \"28fba9ds-kl2m-7891-qw3r-45e82c7f1bb12\",\n            \"externalOperationId\": \"123-partial-refund-2\",\n            \"orderId\": \"123\",\n            \"status\": \"SUCCESS\",\n            \"operationType\": \"REFUND\"\n          }\n        }\n        ```\n\n    2. `ORDER_STATUS_UPDATED` — заказ остался в статусе `PARTIALLY_REFUNDED`:\n\n        ```json\n        {\n          \"merchantId\": \"xxxxxxxxx-xxx-5xxx-xxxxx-xxxxxxxx\",\n          \"event\": \"ORDER_STATUS_UPDATED\",\n          \"eventTime\": \"2024-04-19T13:27:54.321878+00:00\",\n          \"order\": {\n            \"orderId\": \"123\",\n            \"paymentStatus\": \"PARTIALLY_REFUNDED\"\n          }\n        }\n        ```\n\n3. Совершили третий частичный возврат. Сумма всех возвратов достигла полной стоимости. Вам придет 2 нотификации:\n\n    1. `OPERATION_STATUS_UPDATED` — операция возврата завершилась успешно:\n\n        ```json\n        {\n          \"merchantId\": \"xxxxxxxxx-xxx-5xxx-xxxxx-xxxxxxxx\",\n          \"event\": \"OPERATION_STATUS_UPDATED\",\n          \"eventTime\": \"2024-04-19T13:40:51.323878+00:00\",\n          \"operation\": {\n            \"operationId\": \"64abc2ts-rj4y-3187-mf5g-56b71e9a4dd67\",\n            \"externalOperationId\": \"123-partial-refund-3\",\n            \"orderId\": \"123\",\n            \"status\": \"SUCCESS\",\n            \"operationType\": \"REFUND\"\n          }\n        }\n        ```\n\n    2. `ORDER_STATUS_UPDATED` — заказ в перешел в терминальный статус `REFUNDED`. Больше нельзя вызывать возвраты.\n\n        ```json\n        {\n          \"merchantId\": \"xxxxxxxxx-xxx-5xxx-xxxxx-xxxxxxxx\",\n          \"event\": \"ORDER_STATUS_UPDATED\",\n          \"eventTime\": \"2024-04-19T14:16:28.766392+00:00\",\n          \"order\": {\n            \"orderId\": \"123\",\n            \"paymentStatus\": \"REFUNDED\"\n          }\n        }\n        ```",
                "operationId": "webhook",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "properties": {
                                    "event": {
                                        "description": "Тип события:\n- `ORDER_STATUS_UPDATED` — обновление статуса заказа;\n- `OPERATION_STATUS_UPDATED` — обновление статуса операций списания, возврата или отмены платежа.",
                                        "enum": [
                                            "TRANSACTION_STATUS_UPDATE",
                                            "ORDER_STATUS_UPDATED",
                                            "OPERATION_STATUS_UPDATED",
                                            "SUBSCRIPTION_STATUS_UPDATED"
                                        ],
                                        "type": "string"
                                    },
                                    "eventTime": {
                                        "description": "Время события в формате `RFC 3339`: `YYYY-MM-DDThh:mm:ssTZD`.",
                                        "example": "2025-05-26T21:00:36.08847+00:00",
                                        "format": "date-time",
                                        "type": "string"
                                    },
                                    "merchantId": {
                                        "description": "ID (идентификатор) продавца.",
                                        "format": "uuid",
                                        "type": "string"
                                    },
                                    "operation": {
                                        "allOf": [
                                            {
                                                "properties": {
                                                    "externalOperationId": {
                                                        "description": "Идентификатор операции в системе продавца. Должен быть уникальным.\n\nПередайте этот параметр, чтобы отслеживать конкретную операцию через метод [v1/operations/{external_operation_id}](../yandex-pay-api/operation/merchant_v1_operations-get).",
                                                        "type": "string"
                                                    },
                                                    "operationId": {
                                                        "description": "Идентификатор операции.",
                                                        "example": "5d32f295-8723-457d-81f9-ab13f17b7bd6",
                                                        "format": "uuid",
                                                        "type": "string"
                                                    },
                                                    "operationType": {
                                                        "description": "Тип операции. Подробнее о типах операций читайте в разделе [Статусы операций](../../../payments/statuses).",
                                                        "enum": [
                                                            "AUTHORIZE",
                                                            "BIND_CARD",
                                                            "REFUND",
                                                            "CAPTURE",
                                                            "VOID",
                                                            "RECURRING",
                                                            "PREPAYMENT",
                                                            "SUBMIT"
                                                        ],
                                                        "type": "string"
                                                    },
                                                    "orderId": {
                                                        "description": "ID заказа, переданный в [/v1/orders](../yandex-pay-api/order/merchant_v1_orders-post.md) при создании заказа.",
                                                        "type": "string"
                                                    },
                                                    "status": {
                                                        "description": "Статус операции. Подробнее о статусах операций читайте в разделе [Статусы операций](../../../payments/statuses).",
                                                        "enum": [
                                                            "PENDING",
                                                            "SUCCESS",
                                                            "FAIL"
                                                        ],
                                                        "type": "string"
                                                    }
                                                },
                                                "required": [
                                                    "operationId",
                                                    "operationType",
                                                    "orderId",
                                                    "status"
                                                ],
                                                "type": "object"
                                            }
                                        ],
                                        "description": "Информация по операции. Приходит с событием `OPERATION_STATUS_UPDATED`"
                                    },
                                    "order": {
                                        "allOf": [
                                            {
                                                "properties": {
                                                    "cartUpdated": {
                                                        "description": "Была ли обновлена корзина. Возвращается при оплате баллами.Если флаг имеет значение `true`, получите актуальную корзину.",
                                                        "type": "boolean"
                                                    },
                                                    "orderId": {
                                                        "description": "ID заказа, переданный в [/v1/orders](../yandex-pay-api/order/merchant_v1_orders-post.md) при создании заказа.",
                                                        "type": "string"
                                                    },
                                                    "paymentStatus": {
                                                        "description": "Статус заказа. Подробнее читайте в разделе [Статус заказа](../../../payments/statuses.md).",
                                                        "enum": [
                                                            "PENDING",
                                                            "AUTHORIZED",
                                                            "CAPTURED",
                                                            "VOIDED",
                                                            "REFUNDED",
                                                            "CONFIRMED",
                                                            "PARTIALLY_REFUNDED",
                                                            "FAILED"
                                                        ],
                                                        "type": "string",
                                                        "x-enumDescriptions": {
                                                            "AUTHORIZED": "Платеж за заказ авторизован. Средства заблокированы на счету плательщика",
                                                            "CAPTURED": "Заказ успешно оплачен. Средства списаны со счета плательщика",
                                                            "CONFIRMED": "Заказ успешно оформлен",
                                                            "FAILED": "Заказ не был успешно оплачен",
                                                            "PARTIALLY_REFUNDED": "Совершён частичный возврат средств за заказ",
                                                            "PENDING": "Ожидается оплата",
                                                            "REFUNDED": "Совершён возврат средств за заказ",
                                                            "VOIDED": "Оплата отменена (voided). Списание средств не производилось"
                                                        }
                                                    }
                                                },
                                                "required": [
                                                    "orderId",
                                                    "paymentStatus"
                                                ],
                                                "type": "object"
                                            }
                                        ],
                                        "description": "Информация по заказу. Приходит с событием `ORDER_STATUS_UPDATED`"
                                    },
                                    "subscription": {
                                        "allOf": [
                                            {
                                                "properties": {
                                                    "customerSubscriptionId": {
                                                        "description": "ID подписки. Возвращается из SDK при успешном создании подписки. Также можно сохранить подписку при получении первой нотификации по ней. Дальнейшие обновления по этой подписке будут приходить с таким же значением этого поля.",
                                                        "format": "uuid",
                                                        "type": "string"
                                                    },
                                                    "nextWriteOff": {
                                                        "description": "Дата следующей попытки списания денег по подписке",
                                                        "format": "date-time",
                                                        "type": "string"
                                                    },
                                                    "status": {
                                                        "description": "Статус подписки",
                                                        "enum": [
                                                            "NEW",
                                                            "ACTIVE",
                                                            "CANCELLED",
                                                            "EXPIRED"
                                                        ],
                                                        "type": "string"
                                                    },
                                                    "subscriptionPlanId": {
                                                        "description": "ID плана подписки, созданного в личном кабинете или через API.",
                                                        "format": "uuid",
                                                        "type": "string"
                                                    }
                                                },
                                                "required": [
                                                    "customerSubscriptionId",
                                                    "status",
                                                    "subscriptionPlanId"
                                                ],
                                                "type": "object"
                                            }
                                        ],
                                        "description": "Состояние подписки."
                                    }
                                },
                                "required": [
                                    "event",
                                    "eventTime",
                                    "merchantId"
                                ],
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "status": {
                                            "default": "success",
                                            "type": "string"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        },
                        "description": "Вебхук успешно получен и обработан.\nТело ответа может быть любым, рекомендуем отправить `{\"status\": \"success\"}`.\nПри получении `200` Яндекс прекращает отправку повторных вебхуков."
                    },
                    "400": {
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "reason": {
                                            "description": "Описание причины ошибки.",
                                            "type": "string"
                                        },
                                        "reasonCode": {
                                            "description": "Код ошибки:\n\n- `FORBIDDEN` — заказ существует, но был оплачен не через Yandex Split;\n- `ORDER_NOT_FOUND` — заказ не найден в системе продавца;\n- `ORDER_AMOUNT_MISMATCH` — сумма заказа не совпадает с суммой в системе продавца;\n- `ORDER_DETAILS_MISMATCH` — детали заказа отличаются от данных в системе продавца;\n- `OTHER` — общая ошибка;\n- `UNAUTHORIZED` — не удалось проверить подпись JWT-токена;\n- `TOKEN_EXPIRED` — срок действия JWT-токена истек;\n- `CONFLICT` — данные в нотификации расходятся с состоянием заказа в системе продавца. Например, пришла нотификация об оплате для отмененного заказа.",
                                            "enum": [
                                                "FORBIDDEN",
                                                "ITEM_NOT_FOUND",
                                                "ORDER_NOT_FOUND",
                                                "ORDER_AMOUNT_MISMATCH",
                                                "ORDER_DETAILS_MISMATCH",
                                                "OUT_OF_INVENTORY",
                                                "PICKUP_POINT_NOT_FOUND",
                                                "SHIPPING_DETAILS_MISMATCH",
                                                "OTHER",
                                                "UNAUTHORIZED",
                                                "TOKEN_EXPIRED",
                                                "CONFLICT"
                                            ],
                                            "type": "string"
                                        },
                                        "status": {
                                            "default": "fail",
                                            "type": "string"
                                        }
                                    },
                                    "required": [
                                        "reasonCode"
                                    ],
                                    "type": "object"
                                }
                            }
                        },
                        "description": "Ошибка обработки вебхука.\nПри отсутствии ответа или любом статусе кроме `200` Яндекс генерирует новый JWT-токен и повторяет отправку вебхука:\n- первые 10 раз через 5 мс;\n- далее с экспоненциально возрастающим интервалом до 15 минут;\n- затем каждые 15 минут в течение 24 часов.\nОбщее время повторных отправок — 24 часа. После этого вебхук считается недоставленным."
                    }
                },
                "summary": "/v1/webhook"
            }
        }
    },
    "servers": [
        {
            "description": "Production",
            "url": "https://example.merchant.uz"
        },
        {
            "description": "Sandbox",
            "url": "https://sandbox.example.merchant.uz"
        }
    ]
}
Следующая