Пять методов — и касса готова Ключ выдаёт владелец точки Защита от двойных начислений
Для разработчиков POS

Подключите свою кассу к uGet

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

Интеграция не даёт доступа к кабинету мерчанта и не управляет его товарами. Ключ умеет ровно три операции — и ничего больше.

Экран кассира
Товары 50 000
Скидка по акции −5 000
Списать баллами −5 000
К оплате 40 000
Начислим клиенту 2 250 баллов

Эти четыре строки касса получает одним запросом до пробития чека.

Что получает касса

◎

Узнать клиента

По QR-коду или номеру телефона. В ответ — имя и баланс баллов у этого мерчанта.

▤

Показать итог до чека

Скидка по акциям, предел списания баллов и будущий бонус. Ничего не записывается.

✓

Провести продажу

Баллы списываются в оплату, кешбек начисляется, подарки выдаются. Повтор запроса безопасен.

↺

Вернуть чек

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

Три шага до первой продажи

Ключ выпускает владелец бизнеса в своём кабинете. Разработчику кассы он приходит готовым — выпустить его самому нельзя.

Шаг 1

Владелец выпускает ключ

Кабинет → Настройки → API-ключи внешних касс. Владелец выбирает точку продажи и сотрудника-автора чеков, обычно заведённого специально для интеграции.

Шаг 2

Вы сохраняете ключ

Ключ показывается ровно один раз. В базе uGet остаётся только его хеш — восстановить потерянный нельзя, владелец выпустит новый.

Шаг 3

Касса шлёт заголовок

В каждом запросе — заголовок с ключом. Точка и автор чека берутся из самого ключа, в теле запроса их нет.

ugetpos_a1b2c3_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
└──┬──┘ └─┬──┘ └────────────────┬─────────────────┘
  метка   префикс          секрет — только у вас

Одна точка — один ключ

Для второй точки владелец выпускает второй ключ. Предусмотрите в своей системе хранение ключа на уровне точки, а не одного общего на всю сеть.

Песочница: пробуйте прямо сейчас

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

curl -H "X-Api-Key: ugetpos_pCfYNh-U_wp7bworxMa7o9y-5a1TJqxdpZJJ7aDScnSvUVDxQnlI" \
  "https://sandbox.uget.uz/api/integration/pos/consumers/resolve?phone=%2B998900000001"

→ 200
{ "consumerId": 1, "phone": "+998900000001", "points": "0" }

Что уже заведено

Магазин с одной точкой, три товара и акция «кешбек 5%». Хватает, чтобы пройти весь цикл.

Покупатели

Номера +998900000001 … +998900000010. Код подтверждения всегда 112233, SMS не уходит.

Данные одноразовые

Песочница периодически пересоздаётся, ключ после этого меняется. Не завязывайте на него автотесты.

Пять методов

Боевой адрес — connect.uget.uz, песочница — sandbox.uget.uz. Обмен в JSON, UTF-8. Язык ошибок — заголовком Accept-Language: русский по умолчанию, узбекская латиница по запросу.

GET /api/integration/pos/consumers/resolve

Найти клиента

Обязателен один из двух параметров: идентификатор из QR-кода или номер телефона в международном формате. Клиента нет в системе — ответ 404, это нормальный случай: продайте чек как анонимный.

curl -H "X-Api-Key: $UGET_KEY" \
  "https://connect.uget.uz/api/integration/pos/consumers/resolve?phone=%2B998901234567"

→ 200
{ "consumerId": 1024, "name": "Дилшод", "phone": "+998901234567", "points": "45000.00" }
POST /api/integration/pos/sales/preview

Показать итог до пробития

Ничего не записывает — вызывайте при каждом изменении корзины. Поле idempotencyKey здесь не используется, но обязательно: передайте любую непустую строку.

curl -X POST -H "X-Api-Key: $UGET_KEY" -H "Content-Type: application/json" \
  "https://connect.uget.uz/api/integration/pos/sales/preview" -d '{
    "consumerId": 1024,
    "idempotencyKey": "preview",
    "redeemPoints": "5000.00",
    "items": [ { "productId": 512, "qty": "2", "price": "25000.00" } ]
  }'

→ 200
{
  "goods": "50000.00",          стоимость товаров
  "discount": "5000.00",        скидка по акциям
  "payable": "45000.00",        к оплате после скидок
  "giftsToIssue": 0,           сколько подарков выдать
  "pointsBalance": "45000.00",  баллов у клиента
  "maxRedeemPoints": "22500.00", предел списания в этом чеке
  "pointsToAccrue": "2250.00"   начислим клиенту
}

Это оценка, а не обязательство. Акция может закончиться, а баланс — измениться на другой кассе в ту же секунду. Итог фиксирует только регистрация продажи, и он может отличаться. Не сверяйте эти числа программно и не отказывайте в продаже из-за расхождения.

POST /api/integration/pos/sales

Провести продажу

Списывает баллы, начисляет кешбек, выдаёт подарки. Сумма к оплате деньгами — поле cashPaid.

curl -X POST -H "X-Api-Key: $UGET_KEY" -H "Content-Type: application/json" \
  "https://connect.uget.uz/api/integration/pos/sales" -d '{
    "consumerId": 1024,
    "idempotencyKey": "3b0c1e2a-6f1e-4a71-9c1e-7d3a2f5b8c41",
    "redeemPoints": "5000.00",
    "certificateCode": "WELCOME2026",
    "items": [ { "productId": 512, "qty": "2", "price": "25000.00" } ]
  }'

→ 201
{
  "saleId": 90210,
  "goodsAmount": "50000.00",
  "discountAmount": "5000.00",
  "giftsIssued": 0,
  "redeemedPoints": "5000.00",
  "cashPaid": "40000.00",        взять деньгами
  "accrualOperationId": 77431
}
Поле Обязательно Что это
idempotencyKey да Ключ чека, до 80 символов. Главное поле контракта — о нём ниже.
items да Позиции чека. Количество и цена — положительные, товар из каталога необязателен.
consumerId нет Без него чек анонимный: пройдёт, но без баллов и подарков.
redeemPoints нет Сколько баллов списать. Не больше предела из предпросмотра.
certificateCode нет Код сертификата или купона, если клиент его предъявил.
POST /api/integration/pos/sales/{saleId}/refund

Вернуть чек

Начисленный кешбек снимается, списанные баллы возвращаются клиенту, оплата картой уходит обратно. Вернуть можно только чек своей точки: ключ выпущен на одну точку, чужой чек отвечает 404. Повторный возврат — 422.

curl -X POST -H "X-Api-Key: $UGET_KEY" -H "Content-Type: application/json"   "https://connect.uget.uz/api/integration/pos/sales/90210/refund" -d '{ "reason": "Возврат товара" }'

→ 200
{
  "saleId": 90210,
  "refundedAmount": "45000.00",
  "pointsWithdrawn": "1350.00",   снятый кешбек
  "pointsReturned": "5000.00",    возвращённые баллы
  "cardRefunded": true
}
GET /api/integration/pos/sales/{saleId}

Состояние чека

Для сверки, когда связь оборвалась на ответе и неизвестно, прошёл чек или нет. Спросить дешевле, чем пробивать повторно.

curl -H "X-Api-Key: $UGET_KEY"   "https://connect.uget.uz/api/integration/pos/sales/90210"

→ 200
{
  "saleId": 90210,
  "clientRef": "3b0c1e2a-6f1e-4a71-9c1e-7d3a2f5b8c41",
  "consumerId": 1024,
  "totalAmount": "45000.00",
  "refunded": false,
  "occurredAt": "2026-09-05T14:22:11Z"
}
Самое важное

Повтор запроса не должен начислить дважды

Связь рвётся, и касса повторяет запрос. Правило простое: один чек — один ключ, созданный в момент, когда кассир нажал «Оплатить», и сохранённый до получения ответа. При каждом повторе отправляйте тот же ключ. Новый ключ на повторе создаст второй чек.

Повтор с уже использованным ключом вернёт код 409 и номер ранее созданной продажи. Это успех, а не ошибка — первый запрос дошёл. Закройте продажу как успешную. Покажете кассиру ошибку — он проведёт чек второй раз вручную.

→ 409
{
  "status": 409,
  "title": "Повторная операция",
  "existingSaleId": 90210,
  "type": "https://docs.uget.uz/errors/conflict"
}

Ключ уникален в пределах мерчанта и живёт вечно:
переиспользовать его для другого чека нельзя
даже через год.

uGet сам сообщает о событиях

Владелец задаёт HTTPS-адрес в кабинете и получает секрет. uGet шлёт на этот адрес POST при каждой продаже и возврате — включая те, что прошли мимо вашего API: на кассе uGet, в мобильном приложении, через QR.

Событие Когда приходит
sale.registered Чек проведён. В теле — суммы, списанные баллы и что взять деньгами.
sale.refunded Чек возвращён. В теле — снятый кешбек, возвращённые баллы и деньги.
ping Проверка адреса из кабинета. Тело пустое.

Что приходит

POST /uget/events
X-Uget-Event: sale.registered
X-Uget-Timestamp: 1788623531
X-Uget-Signature: 9f2b…c41

{
  "saleId": 90210,
  "pointId": 7,
  "consumerId": 1024,
  "goodsAmount": "50000.00",
  "discount": "5000.00",
  "redeemedPoints": "5000.00",
  "cashPaid": "40000.00",
  "occurredAt": "2026-09-05T14:22:11Z"
}

Как проверить подпись

# HMAC-SHA256 от «timestamp.тело» вашим секретом
signature = hmac_sha256(secret, timestamp + "." + raw_body)
if signature != header["X-Uget-Signature"]: reject()
if now - timestamp > 300: reject()   защита от повтора

Проверять обязательно. Без подписи любой, кто узнал адрес, смог бы прислать вам выдуманный чек. Тело сверяйте в исходном виде, до разбора JSON: пересобранный объект даст другую строку и другую подпись.

Повторы и порядок

Ответ 2xx — событие принято. Любой другой ответ или таймаут (10 секунд) — uGet повторит: до пяти попыток с задержкой 1, 2, 4, 8 и 16 минут. Из-за повторов одно событие может прийти дважды — считайте ключом saleId вместе с типом события. Порядок доставки не гарантирован: возврат может прийти раньше продажи, если первая попытка не прошла.

Ошибки и повторы

Формат ответа — стандарт Problem Details. Текст ошибки готов к показу кассиру, разбирать его программно не нужно: ориентируйтесь на код ответа.

Код Когда Что делать кассе
400 Пустой чек, отрицательная цена, не указан ни клиент, ни телефон Ошибка в вашем запросе. Показать кассиру, не повторять
401 Ключ не предъявлен, неверен или отозван; мерчант заблокирован; сотрудник-автор уволен Не повторять. Сообщить владельцу, что нужен новый ключ
404 Клиент не найден Продать как анонимный чек
409 Повтор по ключу идемпотентности Считать успехом и взять номер прежней продажи
422 Нарушено бизнес-правило: например, списать больше допустимого Показать текст ошибки кассиру
429 Превышен лимит частоты запросов Повторить через указанное в ответе число секунд
5xx Сбой на стороне uGet Повторить с тем же ключом идемпотентности, с нарастающей паузой

Код 401 намеренно не различает «ключа нет» и «ключ отозван»: иначе перебором можно было бы выяснить, какие ключи существуют. Разумная схема повторов — три попытки с паузами в одну, три и десять секунд, дальше предложить кассиру провести чек без лояльности.

Порядок работы кассы

01

Кассир собрал корзину.

02

Клиент предъявил QR или назвал телефон — ищем его. Не нашли, продолжаем без клиента.

03

Запрашиваем предпросмотр и показываем скидку, доступные баллы и будущий бонус.

04

Клиент выбрал, сколько баллов списать — не больше предела из предпросмотра.

05

Нажали «Оплатить» — создаём ключ идемпотентности и сохраняем его рядом с чеком.

06

Проводим продажу, берём сумму к оплате деньгами, печатаем свой чек. Оборвалась связь — повторяем с тем же ключом.

Если uGet недоступен совсем — проводите продажу как обычно, без лояльности. Начислить баллы задним числом нельзя, зато клиент не останется у кассы без чека.

Границы контракта

Не пробелы, а осознанные решения — учтите их в проектировании.

Смена остаётся у вас

uGet не ведёт вашу смену и не просит её номер: две системы учёта смен рассинхронизируются на первом же сбое связи. Отчёт по точке строится по продажам за период.

Ключ работает на одну точку

Точка берётся из ключа, а не из тела запроса, и чужой чек не прочитать и не вернуть. Сеть из пяти точек — пять ключей.

Частичный возврат

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

Лимиты частоты

Могут появиться без изменения контракта. Держите разумный темп запросов и обрабатывайте код 429.

Чек-лист перед запуском

Пройдите его в песочнице до переключения на боевой адрес.

  • ✓Ключ лежит в защищённом конфиге, а не в коде и не в мобильном клиенте
  • ✓Ключ не попадает в логи, в том числе в логи HTTP-запросов
  • ✓В сети касс каждая точка использует свой ключ
  • ✓Ключ идемпотентности создаётся один раз на чек и хранится до ответа
  • ✓Ответ 409 с номером прежней продажи обрабатывается как успех
  • ✓Ответ 401 не приводит к бесконечным повторам
  • ✓Кассир не может списать больше предела из предпросмотра
  • ✓Недоступность uGet не блокирует продажу
  • ✓Числа предпросмотра не сверяются программно с итогом продажи

Готовы подключить кассу?

Напишите нам — поможем с тестовым доступом, разберём спорные места контракта и подскажем, как объяснить владельцу точки выпуск ключа.

Если вы не разработчик, а владелец бизнеса — просто передайте эту страницу тем, кто обслуживает вашу кассу.