{
  "envelope": {
    "description": "Все вебхуки используют единый формат. Подпись `HMAC-SHA256(webhook_secret, timestamp\\nraw_body)` в `X-TKE-Webhook-Signature` (hex в нижнем регистре). В течение 24 часов смены секрета также передаётся `X-TKE-Webhook-Signature-Old`. Доставка как минимум один раз; удаляйте дубли по `event_id`.",
    "fields": {
      "data": "Содержимое зависит от типа события (см. ниже).",
      "event_id": "Постоянный UUID, уникальный для события (защита от дублей).",
      "event_type": "Тип события.",
      "is_testmode": "true, если событие создано тестовым ключом.",
      "occurred_at": "ISO-8601 UTC."
    }
  },
  "health_check": {
    "data_fields": [
      "message"
    ],
    "event_type": "ping",
    "note": "Нельзя подписаться или отписаться через `events` (значение `ping` даёт `internal.validation_failed`); событие получает каждый обработчик. Формат и подпись как у реальных событий (`X-TKE-Webhook-Signature`, без `-Old`), используйте для проверки подписи. На неизвестный `event_type` обработчик должен отвечать 2xx: при 4xx создание/изменение завершается `webhook.health_check_failed` (422), ничего не сохраняется. `ping` не входит в `GET /v1/webhooks/deliveries`.",
    "when": "Каждый `POST /v1/webhooks` и `PUT /v1/webhooks/{id}` проверяет доступность до сохранения обработчика"
  },
  "items": [
    {
      "data_fields": [
        "order_id",
        "external_ref",
        "items[] (order_item_id, iccid)"
      ],
      "event_type": "order.completed",
      "note": "",
      "when": "Заказ оформлен, eSIM готовы"
    },
    {
      "data_fields": [
        "order_id",
        "external_ref",
        "reason"
      ],
      "event_type": "order.failed",
      "note": "",
      "when": "Заказ не завершён (ошибка выдачи и т. п.)"
    },
    {
      "data_fields": [
        "order_item_id",
        "order_id",
        "external_ref",
        "iccid",
        "lpa"
      ],
      "event_type": "esim.delivered",
      "note": "Связь с заказом по `order_id` и `external_ref`. `lpa` расшифровывается непосредственно перед POST и не хранится открытым текстом в доставках. URL изображения QR не передаётся: создайте QR из `lpa` или получите `qr_code_base64` через `GET /v1/esims/{id}`.",
      "when": "Выдана eSIM с данными установки"
    },
    {
      "data_fields": [
        "order_item_id",
        "order_id",
        "external_ref",
        "iccid",
        "activated_at"
      ],
      "event_type": "esim.activated",
      "note": "Используйте `order_id` и `external_ref` (ваша ссылка, переданная при покупке) для связи eSIM с вашим заказом.",
      "when": "eSIM установлена и активирована (smdp enabled/in_use)"
    },
    {
      "data_fields": [
        "order_item_id",
        "order_id",
        "external_ref",
        "threshold (75/90)",
        "used_bytes",
        "data_bytes"
      ],
      "event_type": "esim.usage_threshold",
      "note": "Свяжите eSIM с заказом через `order_id` и `external_ref`. Передаётся только наибольший новый пройденный порог: рост 0→95% вызывает только 90. `threshold` означает, что превышен как минимум этот уровень.",
      "when": "Расход достиг порога 75% / 90%"
    },
    {
      "data_fields": [
        "order_item_id",
        "order_id",
        "external_ref",
        "days_remaining (3/1)",
        "expires_at"
      ],
      "event_type": "esim.expiring",
      "note": "Используйте `order_id` и `external_ref` (ваша ссылка, переданная при покупке) для связи eSIM с вашим заказом.",
      "when": "За 3 дня / 1 день до истечения срока"
    },
    {
      "data_fields": [
        "threshold (25/10)",
        "balance",
        "currency"
      ],
      "event_type": "wallet.low_balance",
      "note": "`threshold` — процент (25/10), передаётся только при известном значении; иначе поле отсутствует (сравнивайте `balance` самостоятельно). Сумма VND никогда не передаётся в `threshold`.",
      "when": "Баланс кошелька достиг порога предупреждения 25% / 10%"
    },
    {
      "data_fields": [
        "changes[] (package_code, change: price|availability|package, occurred_at)",
        "change_count"
      ],
      "event_type": "catalog.updated",
      "note": "Цены не передаются, поскольку они зависят от организации. Получите ваши цены через `GET /v1/catalog/packages`. Изменения за короткое окно (по умолчанию 10 минут) объединяются в один вебхук `changes[]`; каждый тариф появляется один раз. Синхронизация сотен тарифов не создаёт сотни вебхуков. Действует в тестовом и рабочем режимах.",
      "when": "Изменение каталога: тариф добавлен, изменён, скрыт, изменена базовая цена или закончился запас"
    }
  ]
}
