DSSDevelopers
Единая цифровая платформа ДСС

Партнёрский API продажи билетов

Руководство по интеграции для разработчиков Ticketon: как подписывать запросы, в каком порядке звать ручки, что означает каждый статус и код ошибки, и как пройти сертификацию на стенде.

версия контракта 0.2.0 дата 2 сентября 2026 статус контракт зафиксирован, стенд готовится источник partner-api.openapi.yaml

Этот документ и OpenAPI-файл описывают одно и то же. При расхождении по форме полей побеждает OpenAPI-файл; этот текст объясняет, почему поля устроены именно так и что с ними делать.

Как это работает

Ticketon продаёт билеты на мероприятия ДСС у себя. Мероприятия, зал и статусы мест остаются на стороне ДСС; Ticketon получает доступ к живому инвентарю и проводит оплату самостоятельно. Пять утверждений, на которых стоит всё остальное:

  1. Живой инвентарь, а не квота. Вы работаете с теми же местами, что и витрина ДСС. Место, проданное на витрине, исчезает у вас немедленно, и наоборот. Заранее выделенных пачек мест не существует; ДСС остаётся мастер-системой статусов.
  2. Доступ выдаётся на мероприятие. Вы видите и продаёте только то, на что ДСС выдала грант. Список меняется на стороне ДСС без правок вашего кода.
  3. Деньги у вас. Вы принимаете оплату и сами фискализируете чек. Платёжный шлюз ДСС в этом потоке не участвует; payment_info в подтверждении заказа это факт вашего платежа, который ДСС хранит для сверки. Взаиморасчёт между ДСС и партнёром периодический и идёт вне API.
  4. Заказ раньше денег, билеты после подтверждения. Заказ запирает места платёжным окном до списания. После списания вы зовёте confirm и получаете билеты: подписанный qr_token и pdf_url на каждый. Писем покупателю ДСС не отправляет, доставка на вашей стороне.
  5. Возврат денег делаете вы. ДСС гасит билеты, погашенный не проходит на вход. В первой фазе возврат оформляется обращением к персоналу ДСС, и место после него в продажу не возвращается; освобождение мест появится вместе с ручкой refund во второй фазе.
GET/events GET/seats POST/bookings POST/orders списание у партнёра POST/confirm билетыqr_token POST/cancel эквайер отказал бронь: 3 мин ttl_seconds окно оплаты: 5 мин expires_at списывать деньги только после 201 на POST /orders
Счастливый путь и единственная развилка. Бронь держит места на время выбора, заказ на платёжное окно; между ними только один запрет: не списывать деньги, пока заказ не создан. Цифры таймеров это умолчания, действующие значения приходят в ответах.
Правило, которое проверяет сертификация

Списывать деньги раньше ответа 201 на POST /orders запрещено. Заказ и есть гарантия места: до него бронь может истечь, и получится «деньги списаны, места нет». Сверка проверяет, что payment_info.paid_at не раньше created_at заказа.

Заказ, не получивший ни confirm, ни cancel, истекает сам и возвращает места в продажу. Подвисших билетов без денег не бывает по построению.

Если у вас на руках ТЗ СКУП ДСС

Контракт следует разделу 4.2.14 технического задания заказчика и отличается от него в шести местах, каждое ради покупателя или денег: места отдаются посекторно, а не всем залом одним ответом; успешный ответ обёрнут в {"data": …}; ключи идемпотентности partner_booking_id и partner_order_id обязательны; формула подписи зафиксирована проверочным вектором, а не словами; заказ создаётся до списания, а оплата подтверждается отдельным вызовом; гарантированный канал отмены это опрос, вебхуки подключаются позже как ускоритель.

Быстрый старт

Стенд
адрес и ключи pk_sandbox_… передаются техническому контакту (планируется api-sandbox.dss.ndsvs.kz; адрес подтверждается вместе с ключами)
Продуктив
https://api-dss.ndsvs.kz/api/v1/partners, ключи pk_live_… после сертификации
Транспорт
HTTPS, TLS 1.2 и выше; JSON в UTF-8; имена полей snake_case
Заголовки
X-Api-Key, X-Timestamp, X-Signature на каждом запросе; Content-Type: application/json на POST

Подпись запроса

Каждый запрос подписывается HMAC-SHA256. Строка для подписи собирается из четырёх частей, разделённых одним символом перевода строки (байт 0x0A):

Каноническая строка
<timestamp>\n<METHOD>\n<uri>\n<body>
ЧастьЧто подставлять
timestampUnix-время в секундах, десять цифр, то же значение, что в X-Timestamp. Окно валидности пять минут в обе стороны.
METHODGET или POST, заглавными.
uriПолный путь с query-строкой ровно как он уходит на провод, включая префикс /api/v1/partners, без хоста. Параметры не пересортировываются.
bodyТело запроса байт в байт. Для GET пустая строка, но разделитель \n перед ней остаётся.

Подпись: HMAC-SHA256(key = secret_key, message = canonical), результат в нижнем регистре hex. Секрет это ключ HMAC, а не часть сообщения.

Пример на Pythonpython
import hmac, hashlib, json, time

def headers(api_key: str, secret: str, method: str, uri: str, body: bytes = b"") -> dict:
    ts = str(int(time.time()))                      # секунды, не миллисекунды
    canonical = f"{ts}\n{method.upper()}\n{uri}\n".encode() + body
    sig = hmac.new(secret.encode(), canonical, hashlib.sha256).hexdigest()
    return {"X-Api-Key": api_key, "X-Timestamp": ts, "X-Signature": sig}

# тело сериализуется ОДИН раз, и отправляются ровно эти байты
body = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode()
Три ошибки, которые встречаются чаще всего

X-Timestamp в миллисекундах. Тринадцать цифр окажутся вне пятиминутного окна, и ответ будет INVALID_SIGNATURE, хотя подпись посчитана верно.

Тело сериализовано дважды. Подписали одну строку, а HTTP-клиент пересобрал JSON с другими пробелами или порядком ключей. Подписывайте те же байты, что отправляете.

Литеральный \n. В примерах ниже перевод строки показан как \n ради читаемости. Скопированные два символа «обратный слэш и n» дадут другую подпись.

Отсутствующий X-Api-Key отвечает 401 INVALID_API_KEY; любая другая проблема аутентификации, включая timestamp вне окна, отвечает 401 INVALID_SIGNATURE. Отдельного nonce нет намеренно: каждый изменяющий вызов идемпотентен по вашим ключам (Идемпотентность), поэтому повтор запроса внутри окна воспроизводит тот же результат и нового эффекта не создаёт.

Проверочный вектор

Сойдитесь с ним до первого запроса на стенд: расхождение здесь стоит минуты, расхождение на стенде стоит дня переписки.

secret
sandbox_secret_do_not_use_in_prod
X-Timestamp
1790841600
POST, тело без лишних пробелов
1790841600\nPOST\n/api/v1/partners/bookings\n{"event_id":32,"partner_booking_id":"TKT-BKG-2026-0009871","seats":["seat-AA-A-03-005","seat-AA-A-03-006"],"customer":{"email":"buyer@ticketon.kz"}}

X-Signature: d2b089654f675c25a6f8e3dacce46f5850c7a5a6c62a8a697fcd92e8fc6c5527
GET, тело пустое
1790841600\nGET\n/api/v1/partners/events?date_from=2026-10-01&limit=50\n

X-Signature: d783761a531da8c84c1e6fba1c048f5ebc6cf8c9ac66fe43a7cf060dd95616ca

Чеклист перед первым запросом на стенд

  • Обе подписи проверочного вектора сошлись на вашей реализации.
  • X-Timestamp десятизначный, берётся из синхронизированных часов.
  • Тело сериализуется один раз; подписанные байты и отправленные байты это один буфер.
  • Клиент читает X-Request-Id из ответа и пишет его в журнал рядом с телом отказа.
  • Таймаут клиента около 10 секунд, повтор с нарастающей задержкой на 5xx и на сетевых ошибках.

Общие правила

Успех
{"data": …}; списки дополнительно несут {"meta": {"total", "limit", "offset"}}
Ошибка
{"error": {"code", "message", "details"}}; ветвиться по code, message писать в журнал, а не показывать покупателю
Деньги
целые тенге без копеек; currency всегда KZT
Время
ISO 8601 со смещением, например 2026-10-05T19:00:00+05:00; площадки в UTC+5
Идентификаторы
seat-AA-A-03-007, sector-AA-A, row-AA-A-03 это непрозрачные строки платформы: не разбирать и не собирать самостоятельно
Трассировка
заголовок X-Request-Id (UUID) принимается на любом запросе и возвращается тем же заголовком в каждом ответе, включая ошибки; по нему ДСС находит запрос у себя
Пагинация
limit до 100 (по умолчанию 50), offset; при опросе изменений с updated_since ответ полный и пагинация не применяется

Ограничение частоты

По умолчанию 100 запросов в минуту на партнёра; боевое значение договорное и согласуется под онсейл заранее, как и срок брони с платёжным окном. Каждый ответ несёт X-RateLimit-Limit, X-RateLimit-Remaining и X-RateLimit-Reset (Unix-секунды). При превышении 429 RATE_LIMIT_EXCEEDED.

Рекомендуемая каденция чтений: сводку секторов не чаще раза в 10 секунд на мероприятие; места сектора запрашивать, когда пользователь открывает сектор, а не фоном по всем секторам. Доступность в чтениях справочная, истина только за успешной бронью, поэтому частый опрос точности не добавляет.

Внешний щит

Перед приложением стоит общая защита инфраструктуры. Её 429 может прийти без тела нашего формата и без заголовков X-RateLimit-*. То же касается 5xx: тело может быть вне формата {"error": …}. Ветвитесь по HTTP-статусу, повторяйте с нарастающей задержкой; все изменяющие вызовы идемпотентны, повтор безопасен.

Справочник ручек

Девять операций. Восемь доступны в первой фазе, refund появляется во второй. Все пути относительно префикса /api/v1/partners. У любой операции возможны 401 (аутентификация) и 429 (лимит), в таблицах ниже они не повторяются; 403 (ACCESS_DENIED, PARTNER_DISABLED) перечислен там, где он возможен.

ОперацияЗачем
GET /eventsчто можно продавать; с updated_since это фид изменений
GET /events/{event_id}карточка одного мероприятия
GET /events/{event_id}/seatsсводка по секторам, затем места сектора
POST /bookingsудержать места на время выбора
POST /ordersзаказ: места заперты платёжным окном
POST /orders/{order_id}/confirmоплата прошла, билеты в ответе
POST /orders/{order_id}/cancelэквайер отказал, места свободны сразу
GET /orders?partner_order_id=восстановление после потери ответа
POST /orders/{order_id}/refundотметка возврата, вторая фаза
GET/events

Список мероприятий и фид изменений

Возвращает мероприятия, на которые у вас есть грант. Без updated_since в выдаче sales_scheduled, sales_open и sales_paused. С updated_since ручка становится фидом изменений: приходят все мероприятия с грантом, изменившиеся после курсора, в том числе cancelled, sales_closed и отозванные (access: "revoked"). Подробно об опросе в разделе Изменения мероприятий.

Параметры запроса

ПараметрЗначение
updated_sincemeta.cursor из предыдущего ответа. Не своё время: курсор серверный и непрозрачный. Первый запрос без параметра.
date_from, date_toпериод по дате начала, YYYY-MM-DD, конец включительно
venue_idобъект, например almaty-arena
event_typeтип из словаря
limit, offsetпагинация; с updated_since игнорируются

Поля, на которые стоит смотреть: title гарантированно на трёх языках; description и hall_name гарантируют только ru, остальные языки могут отсутствовать, показывайте с откатом на русский. max_seats_per_booking это действующий потолок мест в одной брони именно для этого мероприятия (по умолчанию 8). price_from_tenge может быть null, если цены ещё не заданы.

Ошибки

КодКогда
401INVALID_API_KEY, INVALID_SIGNATUREаутентификация
422INVALID_PARAMETERSневерная дата, неизвестный тип, limit вне диапазона
429RATE_LIMIT_EXCEEDEDпревышена частота
GET/events/{event_id}

Карточка мероприятия

Текущее состояние одного мероприятия с грантом: обновление вашей страницы между опросами фида. Тело такое же, как элемент списка. Отменённое мероприятие с грантом читается.

Ошибки

КодКогда
403ACCESS_DENIEDмероприятие существует, гранта нет
403PARTNER_DISABLEDдоступ партнёра приостановлен ДСС целиком; отличайте от проблемы ключа
404EVENT_NOT_FOUNDтакого event_id нет
GET/events/{event_id}/seats

Доступность мест

Места отдаются посекторно: зал первого объекта это 960 мест в шести секторах. Ручка работает в двух режимах. Без параметра sector приходит сводка по секторам с минимальной ценой и числом свободных мест; с параметром sector приходят места этого сектора. Схему зала стройте одним запросом сводки плюс по запросу на сектор, который открывает пользователь.

Параметры запроса

ПараметрЗначение
sectorидентификатор сектора из сводки, например sector-AA-A
categoryфильтр по категории билета: standard, vip, fan, invitation, service

status живой: held значит, что место прямо сейчас удерживает кто-то, в том числе покупатель на витрине ДСС. Продавать можно только места с is_sellable: true, это поле учитывает и статус, и цену. Геометрию для отрисовки (SVG-план зала, в котором id элементов совпадают с space_id) можно запросить у технического контакта ДСС.

Ошибки

КодКогда
403ACCESS_DENIED, PARTNER_DISABLEDнет гранта, доступ приостановлен
404EVENT_NOT_FOUNDмероприятия нет
422INVALID_PARAMETERSнеизвестный sectordetails.sector) или категория
POST/bookings

Временное бронирование

Удерживает места на срок брони. Захват атомарный: либо удержаны все перечисленные места, либо ни одно. Срок настраивается на партнёра, по умолчанию 3 минуты; действующее значение приходит в expires_at и ttl_seconds, не зашивайте его константой. Бронь не является продажей и ничего не резервирует за покупателем: по истечении срока места освобождаются сами.

Цена фиксируется в момент брони. price_tenge каждого места и total_tenge из ответа наследуются заказом как есть; до брони цены могут меняться. Итог покупателю показывайте из ответа брони, а не из закэшированных чтений. Льготных цен в первой фазе нет: каждое место бронируется по полной цене своей категории.

Тело запроса

ПолеЗначение
event_idмероприятие с грантом
partner_booking_idобязателен: ключ идемпотентности, уникальный в пределах партнёра, до 64 символов
seatsот 1 до max_seats_per_booking мероприятия; абсолютный потолок схемы 20 действует в первой фазе, групповые продажи сверх него обсуждаются отдельно
customerнеобязательный: name, email, phone

Ошибки

КодКогдаЧто делать
409SEATS_NOT_AVAILABLEхотя бы одно место занято, ни одно не удержано; список в details.seatsобновить сектор, предложить другие места
409SALES_PAUSEDпродажи по мероприятию приостановленыпоказать паузу, читать можно
409SALES_NOT_OPENпродажи не открыты, закрыты или мероприятие отмененоснять с продажи до изменения статуса
409BOOKING_LIMIT_EXCEEDEDмест больше действующего предела; details: {limit, requested}ограничить корзину по max_seats_per_booking
409IDEMPOTENCY_CONFLICTваш partner_booking_id уже использован с другим теломновый идентификатор для новой брони
422SEATS_UNKNOWNместо не существует или принадлежит другому мероприятию; список в details.seatsошибка на вашей стороне, проверить источник идентификаторов
422INVALID_PARAMETERSтело не прошло проверкуразбор по полям в details
403404ACCESS_DENIED, PARTNER_DISABLED, EVENT_NOT_FOUNDкак у чтений
POST/orders

Заказ до списания денег

Превращает бронь в заказ со статусом pending. Места остаются за заказом до expires_at: платёжное окно партнёра, по умолчанию 5 минут. Дальше вы списываете деньги и зовёте confirm; если эквайер отказал, зовёте cancel. Если не случилось ни того ни другого, заказ истекает сам. Билеты в этом ответе не приходят, они выпускаются на confirm.

Тело запроса

ПолеЗначение
booking_idиз ответа брони
partner_order_idобязателен, до 64 символов, уникален в пределах партнёра; должен существовать до списания: по нему заказ восстанавливается
customer.emailобязателен: по нему заказ находит поддержка ДСС; если настоящий адрес не передаётся, согласуем уникальный на заказ псевдоним. Данные покупателя ДСС хранит как персональные и не использует для рассылки по партнёрскому каналу
consentобязан быть true: согласие покупателя на обработку данных собираете вы

total_tenge в ответе это ровно та сумма, которую вы списываете за билеты. Ваш сервисный сбор с покупателя сверх неё ДСС не видит, не сверяет и не ограничивает.

Ошибки

КодКогдаЧто делать
409BOOKING_EXPIREDсрок брони истёкновая бронь, потом новый заказ
409BOOKING_NOT_FOUNDтакой брони нет или она чужаяпроверить booking_id
409BOOKING_ALREADY_CONFIRMEDпо этой брони уже создан заказ с другим partner_order_idнайти его через GET /orders
409IDEMPOTENCY_CONFLICTваш partner_order_id уже использован с другой броньюновый идентификатор
403ACCESS_DENIED, PARTNER_DISABLEDгрант на мероприятие отозван или доступ приостановлен после брониостановить продажу по мероприятию
429PENDING_LIMIT_EXCEEDEDслишком много незакрытых pending-заказовповторить после Retry-After; закрывать заказы confirm или cancel, не бросать
422INVALID_PARAMETERSнапример consent: false или пустой emailразбор по полям в details
POST/orders/{order_id}/confirm

Оплата подтверждена, выпуск билетов

Зовётся сразу после успешного списания, не из отложенной очереди. payment_info описывает ваш платёж и сохраняется у ДСС для сверки. В ответе билеты: qr_token для турникета и pdf_url для скачивания. Повтор по уже подтверждённому заказу возвращает 200 с теми же билетами; первый принятый payment_info при повторе не перезаписывается.

Тело запроса

ПолеЗначение
payment_info.amount_tengeобязан совпадать с total_tenge заказа; при расхождении 422 с обеими суммами в details, заказ остаётся pending, окно идёт, повтор с верной суммой допустим до expires_at
payment_info.paid_atмомент списания у вас, ISO 8601
payment_info.external_payment_idидентификатор платежа в вашей системе или у эквайера
payment_info.currency, methodKZT; способ оплаты в свободной форме, необязателен

Ошибки

КодКогдаЧто делать
409ORDER_EXPIREDплатёжное окно истекло до confirm, места уже вернулись в продажунемедленно вернуть покупателю списанное; каждый такой случай считается дефектом интеграции и попадает в сверку
409ORDER_CANCELLEDзаказ уже отменён через cancel (поздний успех эквайера)вернуть деньги, оформить новый заказ при желании покупателя
409EVENT_CANCELLEDмероприятие отменено между заказом и оплатойвернуть деньги
422INVALID_PARAMETERSсумма не совпала или тело неполноеисправить и повторить в окне
404ORDER_NOT_FOUNDзаказа нет или он чужойпроверить order_id
POST/orders/{order_id}/cancel

Списание не удалось

Зовётся при отказе эквайера. Отменяет pending-заказ и возвращает места в продажу сразу, не дожидаясь конца платёжного окна: покупатель, который тут же пробует другую карту, не должен ждать собственные места. Тела запроса нет. Повтор по уже отменённому или истёкшему заказу возвращает 200.

Ошибки

КодКогда
409ORDER_NOT_CANCELLABLEзаказ уже подтверждён; текущий статус в details. Для оплаченного заказа существует только возврат
404ORDER_NOT_FOUNDзаказа нет или он чужой
GET/orders?partner_order_id=…

Чтение заказа по вашему идентификатору

Ручка восстановления и разбора. Сценарий, ради которого она существует: ответ на POST /orders потерян (таймаут, падение процесса), order_id неизвестен, тело для повтора не сохранилось. Этот запрос отдаёт заказ по единственному, что у вас точно есть: вашему partner_order_id. Дальше confirm или cancel по полученному order_id. Тело такое же, как у создания заказа; после подтверждения в нём есть билеты и те же ссылки на PDF.

Статус expired вычисляется в момент чтения по expires_at: заказ с прошедшим окном читается как expired сразу, независимо от внутренней уборки.

Ошибки

КодКогда
404ORDER_NOT_FOUNDзаказа с таким partner_order_id у вас нет
422INVALID_PARAMETERSпараметр отсутствует или длиннее 64 символов
POST/orders/{order_id}/refundвторая фаза

Отметка возврата

Недоступна в первой фазе. До неё возврат оформляется обращением в ДСС (контакт и срок обработки фиксируются договором): персонал ДСС гасит билет вручную, деньги покупателю в любом случае возвращаете вы. Во второй фазе ручка гасит перечисленные билеты и освобождает их места; операция идемпотентна по билету. Причина возврата в свободной форме попадает в журнал ДСС.

Ошибки

КодКогда
409TICKET_NOT_REFUNDABLEбилет уже использован на входе, погашен по другой причине или относится к другому заказу; текущий статус в details
404ORDER_NOT_FOUNDзаказа нет или он чужой
422INVALID_PARAMETERSпустой список билетов или причина вне длины 1–500 символов

Состояния брони и заказа

Бронь Заказ active converted expired pending paid cancelled expired refunded POST /orders ttl_seconds прошёл confirm cancel expires_at прошёл refund, фаза 2
Оба автомата без возвратных рёбер: истёкшую бронь не оживить, отменённый или истёкший заказ не подтвердить. Повтор любого перехода по уже достигнутому состоянию отвечает 200, а не ошибкой.
ОбъектСтатусЗначение
броньactiveместа удержаны до expires_at
expiredсрок вышел, места свободны; повтор POST /bookings с тем же ключом вернёт её в этом состоянии и мест заново не удержит
convertedпо брони создан заказ, его номер в order_id
заказpendingместа заперты до expires_at, ждём confirm или cancel
paidоплата подтверждена, билеты выпущены, expires_at становится null
cancelledотменён вами через cancel
expiredокно прошло без confirm; вычисляется по expires_at при чтении
refundedвторая фаза: все билеты возвращены
partially_refundedвторая фаза: часть билетов возвращена

Идемпотентность и повторы

Каждый изменяющий вызов безопасен к повтору, и ключи это ваши же идентификаторы. Оба идентификатора обязаны существовать до вызова и не меняться при повторах.

ВызовКлючПовтор возвращает
POST /bookingspartner_booking_id200 и ту же бронь в её текущем состоянии; второй набор мест не удерживается
POST /orderspartner_order_id200 и тот же заказ в его текущем статусе
confirm, cancelorder_id200 с тем же результатом
refundticket_idуспех по уже погашенному билету с той же причиной

Повтор ключа с другим телом это не повтор, а конфликт. Тот же partner_booking_id с другими местами или тот же partner_order_id с другой бронью отвечает 409 IDEMPOTENCY_CONFLICT, в details лежат сохранённое и присланное значения поля.

Восстановление после потери ответа

Если ответ на POST /orders потерян и order_id неизвестен, есть два равноценных пути: повторить POST /orders с тем же partner_order_id (вернётся тот же заказ) или прочитать его через GET /orders?partner_order_id=…. Дальше confirm или cancel по полученному order_id. Терять заказы навсегда в этой схеме негде.

Повторы на ошибках сети и 5xx

Повторяйте с нарастающей задержкой; рекомендуемый таймаут клиента 10 секунд. Поскольку каждый изменяющий вызов идемпотентен, повтор после таймаута не создаёт второй брони и второго заказа. Пишите X-Request-Id из ответа рядом с телом отказа: по нему ДСС находит запрос в своих журналах.

Изменения мероприятий

Гарантированный канал с первого дня: опрос. Опрашивайте GET /events?updated_since=… не реже раза в 5 минут. Именно так доезжают отмена, перенос, закрытие продаж и отзыв доступа, и именно на этом канале лежит ваша обязанность остановить продажу и начать возвраты по отменённому мероприятию.

  1. Первый запрос делается без updated_since. Из ответа берётся meta.cursor.
  2. Каждый следующий запрос передаёт этот курсор в updated_since и берёт новый из ответа. Курсор серверный и непрозрачный; собственное время сюда не подставляется, часы двух систем расходятся.
  3. С updated_since ответ полный: limit и offset игнорируются. Опрос ведётся без фильтров date_from, venue_id, event_type, иначе перенос мероприятия за пределы фильтра пропал бы молча.
  4. Изменившееся мероприятие остаётся в выдаче со своим новым состоянием. Исчезновение из списка событием не является.
Что пришлоЧто это значитЧто делать
status: cancelledмероприятие отменено; билеты ДСС гасит, на вход они не проходятостановить продажу, начать возвраты покупателям
новая starts_atпереносуведомить покупателей, обновить страницу
status: sales_closedпродажи закрыты (в том числе мероприятие уже идёт или прошло)снять с продажи
status: sales_pausedпауза продаж, бронь отвергаетсяпоказать паузу, не бронировать
access: revokedДСС отозвала ваш доступ к мероприятию; новые брони ответят 403остановить продажу; проданные билеты остаются действительными, а confirm и cancel по уже созданным pending-заказам работают до конца окна
status: sales_scheduledгрант есть, продажи ещё не открыты, дата в sales_start_atсобрать страницу заранее, бронь до открытия отвечает SALES_NOT_OPEN

Вебхуки, вторая фаза

Вебхуки booking_expired, event_updated и event_cancelled подключаются во второй фазе как ускоритель и обязательный опрос не отменяют. ДСС вызывает ваш URL и подписывает запрос той же схемой HMAC с теми же тремя заголовками; uri в канонической строке это полный путь вашего URL без хоста. Доставка не менее одного раза: любой ответ кроме 2xx или таймаут означает повтор с нарастающей задержкой, поэтому обработчик обязан быть идемпотентным по booking_id или event_id. URL для них понадобится позже.

ВебхукТело
booking_expiredbooking_id, expired_at; дополнительно event_id, seats
event_updatedevent_id, change_type из rescheduled, updated, sales_paused, sales_resumed; new_date только при rescheduled
event_cancelledevent_id, cancellation_reason, cancelled_at; билеты ДСС гасит сама, вы начинаете возвраты

Билеты, QR и PDF

QR кодируется из qr_token, а не из ссылки

Токен подписан ключом мероприятия, и сканер на входе разбирает именно его. Если закодировать в QR ссылку на страницу билета, контролёр получит отказ QR_INVALID при полностью исправном билете. Это уже случалось на витрине ДСС, поэтому предупреждаем заранее.

qr_token
JWT в ASCII, примерно 530–560 символов. Кодировать в байтовом режиме QR с уровнем коррекции M (получится примерно 20-я версия символа); не усекать и не перекодировать. Между чтениями токен стабилен. Значение null означает, что билет погашен и QR рисовать не нужно.
pdf_url
подписанная ссылка без состояния, действует до окончания мероприятия плюс 30 дней. GET /orders возвращает те же ссылки. Отдавайте её покупателю как ссылку, а не скачивайте PDF пачками со своего сервера: маршрут защищён отдельным лимитом на адрес.
number
номер билета вида DSS-TKT-000061, для поддержки и сверки; номер заказа вида DSS-2026-001042
status
active действителен · used гость прошёл · void погашен (возврат, отмена, инициатива ДСС) · expired мероприятие прошло

Письма покупателю по партнёрскому каналу ДСС не отправляет: доставка билета, QR и PDF на вашей стороне. Покупатель партнёра находится поддержкой ДСС по номеру заказа и по customer.email, поэтому адрес обязателен.

Ошибки

Единый формат: {"error": {"code", "message", "details"}}. Ветвитесь по code, message пишите в журнал: это текст для оператора, а не для покупателя. Неизвестный код обрабатывайте по HTTP-статусу, а не падайте.

HTTPКодКогда
401INVALID_API_KEYключ неизвестен или заголовок отсутствует
401INVALID_SIGNATUREподпись не сошлась, X-Timestamp вне окна или не число
403ACCESS_DENIEDна мероприятие нет гранта
403PARTNER_DISABLEDдоступ партнёра приостановлен ДСС целиком; существующие заказы живут до конца окна
404EVENT_NOT_FOUNDмероприятия нет
404ORDER_NOT_FOUNDзаказа нет или он чужой
409SEATS_NOT_AVAILABLEхотя бы одно место занято, ни одно не удержано; details.seats
409SALES_PAUSEDбронь на мероприятие с приостановленными продажами
409SALES_NOT_OPENбронь на мероприятие, продажи которого не открыты, закрыты или отменены
409BOOKING_LIMIT_EXCEEDEDмест больше действующего предела; details: {limit, requested}
409IDEMPOTENCY_CONFLICTваш ключ с другим телом; details: {field, stored, received}
409BOOKING_EXPIREDбронь истекла
409BOOKING_NOT_FOUNDброни не существует
409BOOKING_ALREADY_CONFIRMEDпо броне уже создан другой заказ
409ORDER_EXPIREDплатёжное окно истекло до confirm; немедленно вернуть списанное
409ORDER_CANCELLEDconfirm по заказу, уже отменённому через cancel
409EVENT_CANCELLEDconfirm, когда мероприятие отменено между заказом и оплатой
409ORDER_NOT_CANCELLABLEзаказ уже подтверждён, отмене не подлежит
409TICKET_NOT_REFUNDABLEбилет использован на входе или уже погашен
422INVALID_PARAMETERSтело или параметры не прошли проверку; разбор по полям в details
422SEATS_UNKNOWNместо не существует или принадлежит другому мероприятию; details.seats
429RATE_LIMIT_EXCEEDEDпревышена частота или конкурентность; заголовки X-RateLimit-*
429PENDING_LIMIT_EXCEEDEDслишком много незакрытых pending-заказов; повторить после Retry-After

Словари

ПолеЗначения
Event.statussales_scheduled, sales_open, sales_paused, sales_closed, cancelled. Словарь закрыт: внутренние состояния платформы сворачиваются в эти пять, новые появятся только с новой версией контракта
Event.accessactive, revoked
Event.event_typeconcert, sport, theatre, exhibition, conference, festival, kids, other; может быть null
Event.age_rating0+, 6+, 12+, 16+, 18+, 21+; может быть null
Seat.statusfree, held, sold, blocked
category_codestandard, vip, fan, invitation, service
Booking.statusactive, expired, converted
Order.statuspending, paid, cancelled, expired; во второй фазе refunded, partially_refunded
Ticket.statusactive, used, void, expired
payment_info.currencyKZT
локализованный текстобъект {"ru", "kk", "en"}; у title все три языка, у description, hall_name и category_name гарантирован только ru

Песочница и сертификация

ДСС предоставляет отдельный стенд (планируемая доступность с 15 сентября 2026; адрес и ключи pk_sandbox_… передаются техническому контакту), этот документ с OpenAPI-файлом, коллекцию Postman, сгенерированную из него, и тестовое мероприятие с небольшим залом, на котором проходятся все сценарии, включая возврат и повторный проход. Нагрузочные прогоны по стенду только по предварительному согласованию: стенд разделяет машину с другими системами.

Что нужно от Ticketon

  1. Подтверждение проверочного вектора, до всего остального.
  2. Технический контакт: кто у вас ведёт интеграцию.
  3. Две даты: когда ваша разработка готова и когда вы готовы сертифицироваться.
  4. Диапазон исходящих адресов, если хотите ограничение по адресу.
  5. URL для вебхуков понадобится позже, ко второй фазе.

Сценарии сертификации

Продуктивные ключи выдаются после того, как эти сценарии пройдены на стенде вашим клиентом. Список можно использовать как собственный чеклист до сертификации.

  1. Проверочный вектор HMAC сходится.
  2. Счастливый путь: eventsseatsbookingsordersconfirm, билеты в ответе.
  3. Повтор брони с тем же partner_booking_id возвращает ту же бронь, места не удвоены.
  4. Повтор заказа с тем же partner_order_id возвращает тот же заказ.
  5. Повтор confirm отвечает 200 с теми же билетами.
  6. Бронь истекла: BOOKING_EXPIRED, места свободны.
  7. Заказ истёк до confirm: ORDER_EXPIRED.
  8. Отказ эквайера: cancel, места свободны немедленно.
  9. amount_tenge не равен сумме заказа: 422 с обеими суммами.
  10. Мероприятие без гранта: 403, в списке отсутствует.
  11. Превышение лимита частоты: 429 с заголовками.
  12. Отмена мероприятия видна опросом с updated_since.
  13. QR из qr_token проходит валидатор на демо-точке, повторный скан отвергается.
  14. Потеря ответа: повтор POST /orders и GET /orders?partner_order_id= возвращают тот же заказ, второй не создаётся.
  15. Отзыв гранта виден опросом (access: "revoked"), бронь по нему отвечает 403, confirm по живому pending проходит.
  16. Бронь сверх предела: BOOKING_LIMIT_EXCEEDED; бронь на sales_paused: SALES_PAUSED.
  17. Повтор ключа с другим телом: IDEMPOTENCY_CONFLICT; повтор брони после истечения возвращает её со status: expired и мест не удерживает.

Версии

Добавление полей и кодов ошибок происходит без предупреждения: клиент обязан переживать неизвестные поля и ветвиться по HTTP-статусу на неизвестных кодах. Удаление и переименование только новой версией контракта с уведомлением и окном параллельной работы.

ВерсияДатаЧто изменилось
0.2.002.09.2026Первая редакция контракта, выданная партнёрам
0.1.001.09.2026Внутренняя редакция, партнёрам не выдавалась