emehmon·CRM API Sandbox · подключение
Onboarding · пошаговое руководство

Подключениеза 5 минут

Это руководство проведёт вашу CRM/PMS от нуля до первого подтверждённого запроса в песочнице emehmon.uz: получить ключ, собрать HMAC-подпись, отправить запрос и убедиться, что всё работает — прямо здесь, в браузере.

Base URL http://sandbox-api.emehmon.uz Подпись HMAC-SHA256 Метка ответа X-Sandbox: 1
Чем отличается от прода

Контракт тот же, последствий — нет

X-Sandbox в ответах

Каждый ответ помечен X-Sandbox: 1 — вы всегда знаете, что в тестовом контуре.

Госуведомления off

МВД/ОВИР, ГНИ, ГТК и аналитика не вызываются — очередь заглушена. Реальные службы не затрагиваются.

Проверка гостей stub

Иностранцы всегда «подтверждены» — реальный PERSON_NOT_FOUND не воспроизводится.

Аутентификация 1:1 с продом

HMAC, окно времени, анти-replay, лимиты, идемпотентность и коды ошибок — идентичны боевым.

Пошагово

От ключа до первого запроса

Освоив эти шаги здесь, вы готовы к проду — контракт совпадает.

1

Получите тестовый ключ

Администратор заводит ключ командой crm:sandbox-seed и передаёт две строки:

X-User-Name — идентификатор, напр. hotel-0000001
secret — 64-символьный hex, хранится только у вас и по сети не передаётся.

2

Соберите подпись и заголовки

К каждому запросу — четыре заголовка. Подпись это HMAC-SHA256 от склейки name + ts + request_id вашим секретом, hex (нижний регистр).

ЗаголовокЗначение
X-User-NameВаш идентификатор обязателен
X-User-TsUnix-время в секундах. Окно ±300 c — синхронизируйте часы (NTP) обязателен
X-User-Request-IdUUID, уникальный на каждый запрос (анти-replay) обязателен
X-User-HashHMAC-SHA256(name + ts + request_id, secret) в hex обязателен
# bash — подпись и запрос
USERNAME="hotel-0000001"
SECRET="ваш-секрет"
TS=$(date +%s)
REQ_ID=$(uuidgen)
HASH=$(printf '%s' "${USERNAME}${TS}${REQ_ID}" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $NF}')

curl -s 'http://sandbox-api.emehmon.uz/sync/v1/crm/dictionaries/countries' \
  -H "X-User-Name: $USERNAME" \
  -H "X-User-Ts: $TS" \
  -H "X-User-Request-Id: $REQ_ID" \
  -H "X-User-Hash: $HASH"
# Python 3 — requests
import time, uuid, hmac, hashlib, requests

USERNAME = "hotel-0000001"
SECRET   = "ваш-секрет"
ts  = str(int(time.time()))
rid = str(uuid.uuid4())
sig = hmac.new(SECRET.encode(), (USERNAME+ts+rid).encode(), hashlib.sha256).hexdigest()

r = requests.get(
    "http://sandbox-api.emehmon.uz/sync/v1/crm/dictionaries/countries",
    headers={"X-User-Name":USERNAME,"X-User-Ts":ts,"X-User-Request-Id":rid,"X-User-Hash":sig})
print(r.status_code, r.headers.get("X-Sandbox"), r.json()["data"]["count"])
// Node.js 18+ (fetch + crypto встроены)
import crypto from 'node:crypto';

const USERNAME='hotel-0000001', SECRET='ваш-секрет';
const ts=Math.floor(Date.now()/1000).toString(), rid=crypto.randomUUID();
const sig=crypto.createHmac('sha256',SECRET).update(USERNAME+ts+rid).digest('hex');

const r=await fetch('http://sandbox-api.emehmon.uz/sync/v1/crm/dictionaries/countries',{
  headers:{'X-User-Name':USERNAME,'X-User-Ts':ts,'X-User-Request-Id':rid,'X-User-Hash':sig}});
console.log(r.status, r.headers.get('x-sandbox'));
// PHP 8 — cURL
$username='hotel-0000001'; $secret='ваш-секрет';
$ts=(string)time(); $rid=bin2hex(random_bytes(16));
$sig=hash_hmac('sha256',$username.$ts.$rid,$secret);

$ch=curl_init('http://sandbox-api.emehmon.uz/sync/v1/crm/dictionaries/countries');
curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_HTTPHEADER=>[
  "X-User-Name: $username","X-User-Ts: $ts","X-User-Request-Id: $rid","X-User-Hash: $sig"]]);
echo curl_exec($ch);
3

Сделайте первый (безопасный) запрос

Начните со справочника — это read-only. GET /sync/v1/crm/dictionaries/countries вернёт 200, заголовок X-Sandbox: 1 и список стран с настоящими кодами.

4

Проверьте, что всё работает

Введите свой ключ — страница посчитает HMAC в браузере (Web Crypto) и отправит живой запрос. Тот же расчёт, что и в коде выше.

Секрет не покидает браузер — подпись считается локально. Это тестовый sandbox-ключ.
5

Заселение, смена номера, выселение и оплата

Когда чтение работает — переходите к записи. Ниже готовые тела запросов; заголовки HMAC — как в шаге 2.

POST /sync/v1/crm/listok  ·  гражданин UZ/БГ — обязателен pinfl (14 цифр)

Запрос
{
  "surname": "Иванов", "firstname": "Иван",
  "id_citizen": 173, "passportNumber": "AD1234567",
  "datebirth": "1990-05-15", "datePassport": "2020-01-01",
  "datevisiton": "2026-05-25T14:00:00Z", "wdays": 2,
  "sex": "M", "id_visittype": 2, "id_guest": 1,
  "pinfl": "32505901234567", "propiska": "101"
}
Ответ · 201
{ "code":201, "data":{ "listok_id":8826912, "regNum":"…-26", "registered_via":2, "foreign_duplicates":[] } }

POST /sync/v1/crm/listok  ·  иностранец — обязателен id_person (строка!), pinfl не нужен

Запрос
{
  "surname": "Smith", "firstname": "John",
  "id_citizen": 2, "id_country": 2, "id_countryFrom": 2,
  "id_passporttype": 1, "passportNumber": "FR9876543",
  "datebirth": "1985-03-20", "datePassport": "2021-06-01",
  "datevisiton": "2026-05-25T14:00:00Z", "wdays": 2,
  "sex": "M", "id_visittype": 2, "id_guest": 1,
  "id_person": "987654321"
}
Ответ · 201
{ "code":201, "data":{ "listok_id":8826913, "regNum":"…-26", "registered_via":2 } }

PATCH /sync/v1/crm/listok/{id}  ·  только в течение 5 мин после создания (grace)

Запрос — присылайте только меняющиеся поля
{ "propiska": "202" }
Ответ · 200
{ "code":200, "data":{ "listok_id":8826912, "updated":true } }

После grace-периода — 409 LISTOK_GRACE_PERIOD_EXPIRED. Дети правятся только при создании (POST).

POST /sync/v1/crm/listok/{id}/checkout  ·  выселение + оплата (расчёт турсбора на сервере)

Запрос — payment_type_id из справочника payment-types
{ "sum": 300000, "payment_type_id": 1 }
Ответ · 200
{ "code":200, "data":{ "listok_id":8826912, "checked_out":true,
  "dateVisitOff":"2026-05-25T17:00:00+05:00" } }
6

Финансовые сводки: турсбор, абонплата, регистрации

Read-only GET-запросы, тело не нужно — только query-параметры year (обязателен) и month (опционален). Турсбор считается по дате выезда, регистрации и абонплата — по дате заезда.

GET /sync/v1/crm/finance/tourist-tax?year=2026&month=7  ·  без month — все 12 месяцев + итог

Ответ · 200 (gt — иностранцы, mt — местные, st — самостоятельные)
{ "code":200, "data":{ "basis":"checkout", "months":[ { "month":7, "listok_qty":412, "self_qty":8,
  "gt_sum":17800000, "mt_sum":420000, "st_sum":230000, "total":18450000 } ],
  "total":{ "total":18450000 } } }

GET /sync/v1/crm/finance/tourist-tax/details?year=2026&month=7&page=1&per_page=50  ·  month обязателен

Ответ · 200 — построчно по гостям, выехавшим в этом месяце
{ "code":200, "data":{ "total":420, "items":[ { "source":"listok", "listok_id":8826912,
  "surname":"SMITH", "citizenship":"Франция", "days":4, "prc":5, "mzrp":412000, "tax_sum":82400 } ] } }

GET /sync/v1/crm/finance/subscription?year=2026&month=7

Ответ · 200 — начисление появляется после закрытия месяца; пока его нет, accruals пуст
{ "code":200, "data":{ "accruals":[ { "period":"2026-07", "tariff":500000, "discount_percent":0,
  "total":500000, "registrations":42 } ], "payments_total":250000, "current_deposit":1500000 } }

GET /sync/v1/crm/finance/registrations?year=2026&month=7  ·  за месяц — по дням, без month — по месяцам

Ответ · 200 — итог месяца сходится с registrations в абонплате
{ "code":200, "data":{ "basis":"checkin", "items":[ { "day":1, "listok_qty":14, "self_qty":1, "total":15 } ],
  "total":{ "total":420 } } }
Состояние песочницы

Что сейчас в тестовом контуре

Живые данные тест-отеля и последние листовки, заведённые через API. Обновляется без перезагрузки.

Тест-отель
Если что-то не так

Разбор частых ошибок

Почти все проблемы на старте сводятся к подписи или времени.

ОтветПричина и что делать
401 · MISSING_AUTH_HEADERSОтправлены не все 4 заголовка. Проверьте X-User-Name/Ts/Request-Id/Hash.
401 · INVALID_TIMESTAMPЧасы клиента разошлись больше чем на 5 минут. Синхронизируйте время; X-User-Ts — в секундах.
401 · подписьХеш не сошёлся. Порядок склейки строго name + ts + request_id; hex в нижнем регистре; тот же секрет.
422 · VALIDATION_ERRORСмотрите meta.fields. Напр. id_person у иностранца должен быть строкой.
429 · TOO_MANY_REQUESTSЛимит 120 запросов/мин на ключ. Учитывайте Retry-After.
502 · INTERNAL_GATEWAY_ERRORОшибка сервиса. В песочнице причина дублируется в meta.upstream_message.