Перейти к содержимому

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

Каждый запрос к API подписывается вашим ключом. Сессий и токенов нет: подпись проверяется в каждом запросе.

Заголовок Значение
X-Merchant-Key Ваш ключ, mka_test_… или mka_….
X-Timestamp Unix-время в секундах. Расходится с часами сервера не больше чем на ±5 минут.
X-Nonce 16–128 символов из A–Z a–z 0–9 _ -, новый для каждого запроса, включая повторы.
X-Body-SHA256 SHA-256 точных байтов тела в hex нижнего регистра. Без тела — от пустой строки.
X-Signature HMAC-SHA256 канонического запроса в hex нижнего регистра; ключ — ваш секрет.

Соедините пять строк через \n (без перевода строки в конце):

POST
/v1/payments
1792694400
0123456789abcdef0123456789abcdef
1981bce37ff314cc0a7187703d73a6d469440bebcef6aa05bd4319e20e0521d6
  1. Метод в верхнем регистре.
  2. Путь с query-строкой точно в том виде, в каком он отправлен, без хоста: /v1/payouts?limit=10.
  3. Время из X-Timestamp.
  4. Nonce из X-Nonce.
  5. Хеш тела из X-Body-SHA256.

Ключ HMAC — это секрет, декодированный из base64url, а не текст секрета. Секрет — 43 символа base64url без выравнивания, после декодирования — 32 байта.

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

examples/curl/signed-request.sh
# Signs and sends one request to the Merchant Payments API with curl and openssl.
# Source it from bash: . signed-request.sh
# MKA_KEY, MKA_SECRET and MKA_BASE_URL come from the environment, never from code.
: "${MKA_BASE_URL:=https://sandbox.processing.yolotech.cc}"
# The HMAC key as hex: the secret is base64url text, the key the bytes it encodes.
mka_hmac_key() {
local secret padded
secret=$(printf '%s' "$MKA_SECRET" | tr '_-' '/+')
padded="$secret$(printf '%*s' $(( (4 - ${#secret} % 4) % 4 )) '' | tr ' ' '=')"
printf '%s' "$padded" | openssl base64 -d -A | od -An -v -tx1 | tr -d ' \n'
}
# mka_request METHOD TARGET [BODY] [IDEMPOTENCY_KEY]
# TARGET is the path with its query. Prints the status line, then the body.
mka_request() {
local method=$1 target=$2 body=${3:-} key=${4:-}
local timestamp nonce body_hash signature
timestamp=$(date +%s)
nonce=$(openssl rand -hex 16) # new on every request, retries included
body_hash=$(printf '%s' "$body" | openssl dgst -sha256 -r | cut -d' ' -f1)
signature=$(printf '%s\n%s\n%s\n%s\n%s' "$method" "$target" "$timestamp" "$nonce" "$body_hash" |
openssl dgst -sha256 -mac HMAC -macopt "hexkey:$(mka_hmac_key)" -r | cut -d' ' -f1)
local headers=(-H "X-Merchant-Key: $MKA_KEY" -H "X-Timestamp: $timestamp" -H "X-Nonce: $nonce"
-H "X-Body-SHA256: $body_hash" -H "X-Signature: $signature")
[ -n "$body" ] && headers+=(-H "Content-Type: application/json" --data-binary "$body")
[ -n "$key" ] && headers+=(-H "Idempotency-Key: $key")
# MKA_DRY_RUN=1 prints the signed request instead of sending it.
if [ -n "${MKA_DRY_RUN:-}" ]; then
printf '{"method":"%s","target":"%s","body":%s,"headers":{"X-Merchant-Key":"%s","X-Timestamp":"%s","X-Nonce":"%s","X-Body-SHA256":"%s","X-Signature":"%s"}}\n' \
"$method" "$target" "$(printf '%s' "$body" | mka_json_string)" "$MKA_KEY" "$timestamp" "$nonce" "$body_hash" "$signature"
return
fi
local head answer status replayed=''
head=$(mktemp)
answer=$(curl -sS -X "$method" "${headers[@]}" -D "$head" -w '\n%{http_code}' "$MKA_BASE_URL$target") || { rm -f "$head"; return 1; }
grep -qi '^idempotent-replayed: *true' "$head" && replayed=' (replayed)'
rm -f "$head"
status=${answer##*$'\n'}
printf 'HTTP %s%s\n' "$status" "$replayed"
if [ -n "${answer%$'\n'*}" ]; then printf '%s\n' "${answer%$'\n'*}"; fi
}
# A JSON string of stdin, for the dry run.
mka_json_string() {
sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' | awk 'BEGIN{ORS=""; printf "\""} NR>1{printf "\\n"} {print} END{printf "\""}'
}
# A new Idempotency-Key. Keep it with the request you retry: a retry reuses it.
mka_new_key() { openssl rand -hex 16; }
# An input the example needs, from the environment.
mka_required() {
if [ -z "${!1:-}" ]; then echo "set $1" >&2; exit 1; fi
printf '%s' "${!1}"
}

Для этих входных данных ваш код должен давать эти подписи. Тесты самого сервера проверяют те же значения.

Вход Значение
Секрет MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDE
Время 1792694400
Nonce 0123456789abcdef0123456789abcdef
Запрос Тело Подпись
GET /v1/payments/019fccd2-3eef-4a33-80b8-cd750e2db99f пустое 56d354416d979093c536ba60b1f267ccec4fd0db1ad0490b26b911d238be6be5
POST /v1/payments {"merchant_order_id":"order-1001","amount":"2500.00","currency":"LKR"} ccee5a223a9d2047bd93fc1ad9144cec5a27de6c1b76724c24a2ae068776cc8a

У каждого примера есть пробный прогон: с MKA_DRY_RUN=1 он печатает подписанный запрос вместо отправки.

Любая ошибка аутентификации — один и тот же 401 с {"title":"Unauthorized","status":401,"detail":"authentication failed"}, и это сделано намеренно. Проверьте по порядку:

  • Ключ и установку. Ключи mka_test_ работают только в песочнице; отозванный ключ или ключ, заменённый больше 24 часов назад, перестаёт работать сразу.
  • Ключ HMAC. Декодируйте секрет из base64url; не используйте его текст.
  • Тело. Хешируйте и отправляйте одни и те же байты. Если сериализовать JSON дважды — для хеша и для отправки, — в некоторых языках байты разойдутся.
  • Путь. Подписывайте путь с query-строкой, экранированный так же, как при отправке, без хоста.
  • Часы. Синхронизируйте сервер по NTP; запрос с расхождением больше 5 минут отклоняется.
  • Nonce. Nonce используется один раз. Повтору нужны новые nonce и время — и тот же Idempotency-Key.