Подключите свою кассу к uGet
Касса магазина остаётся вашей: свой чек, своя смена, своя фискализация. uGet добавляет к ней программу лояльности — кассир находит клиента, видит скидку и бонус до оплаты и проводит продажу.
Интеграция не даёт доступа к кабинету мерчанта и не управляет его товарами. Ключ умеет ровно три операции — и ничего больше.
Эти четыре строки касса получает одним запросом до пробития чека.
Что получает касса
Узнать клиента
По QR-коду или номеру телефона. В ответ — имя и баланс баллов у этого мерчанта.
Показать итог до чека
Скидка по акциям, предел списания баллов и будущий бонус. Ничего не записывается.
Провести продажу
Баллы списываются в оплату, кешбек начисляется, подарки выдаются. Повтор запроса безопасен.
Вернуть чек
Кешбек снимается, списанные баллы возвращаются клиенту, оплата картой уходит обратно.
Три шага до первой продажи
Ключ выпускает владелец бизнеса в своём кабинете. Разработчику кассы он приходит готовым — выпустить его самому нельзя.
Владелец выпускает ключ
Кабинет → Настройки → API-ключи внешних касс. Владелец выбирает точку продажи и сотрудника-автора чеков, обычно заведённого специально для интеграции.
Вы сохраняете ключ
Ключ показывается ровно один раз. В базе uGet остаётся только его хеш — восстановить потерянный нельзя, владелец выпустит новый.
Касса шлёт заголовок
В каждом запросе — заголовок с ключом. Точка и автор чека берутся из самого ключа, в теле запроса их нет.
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: русский по умолчанию, узбекская латиница по запросу.
/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" }
/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" начислим клиенту
}
Это оценка, а не обязательство. Акция может закончиться, а баланс — измениться на другой кассе в ту же секунду. Итог фиксирует только регистрация продажи, и он может отличаться. Не сверяйте эти числа программно и не отказывайте в продаже из-за расхождения.
/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 | нет | Код сертификата или купона, если клиент его предъявил. |
/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
}
/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 намеренно не различает «ключа нет» и «ключ отозван»: иначе перебором можно было бы выяснить, какие ключи существуют. Разумная схема повторов — три попытки с паузами в одну, три и десять секунд, дальше предложить кассиру провести чек без лояльности.
Порядок работы кассы
Кассир собрал корзину.
Клиент предъявил QR или назвал телефон — ищем его. Не нашли, продолжаем без клиента.
Запрашиваем предпросмотр и показываем скидку, доступные баллы и будущий бонус.
Клиент выбрал, сколько баллов списать — не больше предела из предпросмотра.
Нажали «Оплатить» — создаём ключ идемпотентности и сохраняем его рядом с чеком.
Проводим продажу, берём сумму к оплате деньгами, печатаем свой чек. Оборвалась связь — повторяем с тем же ключом.
Если uGet недоступен совсем — проводите продажу как обычно, без лояльности. Начислить баллы задним числом нельзя, зато клиент не останется у кассы без чека.
Границы контракта
Не пробелы, а осознанные решения — учтите их в проектировании.
Смена остаётся у вас
uGet не ведёт вашу смену и не просит её номер: две системы учёта смен рассинхронизируются на первом же сбое связи. Отчёт по точке строится по продажам за период.
Ключ работает на одну точку
Точка берётся из ключа, а не из тела запроса, и чужой чек не прочитать и не вернуть. Сеть из пяти точек — пять ключей.
Частичный возврат
Возврат идёт чеком целиком. Вернуть две позиции из пяти нельзя: пересчёт акций и подарков на части чека дал бы результат, который не сойдётся с выданным клиенту.
Лимиты частоты
Могут появиться без изменения контракта. Держите разумный темп запросов и обрабатывайте код 429.
Чек-лист перед запуском
Пройдите его в песочнице до переключения на боевой адрес.
- ✓Ключ лежит в защищённом конфиге, а не в коде и не в мобильном клиенте
- ✓Ключ не попадает в логи, в том числе в логи HTTP-запросов
- ✓В сети касс каждая точка использует свой ключ
- ✓Ключ идемпотентности создаётся один раз на чек и хранится до ответа
- ✓Ответ 409 с номером прежней продажи обрабатывается как успех
- ✓Ответ 401 не приводит к бесконечным повторам
- ✓Кассир не может списать больше предела из предпросмотра
- ✓Недоступность uGet не блокирует продажу
- ✓Числа предпросмотра не сверяются программно с итогом продажи
Готовы подключить кассу?
Напишите нам — поможем с тестовым доступом, разберём спорные места контракта и подскажем, как объяснить владельцу точки выпуск ключа.
Если вы не разработчик, а владелец бизнеса — просто передайте эту страницу тем, кто обслуживает вашу кассу.