AploDelivery Partner API — портал разработчика · v2 (2026-10-16)

Аутентификация и подпись запроса

Работает

Каждый партнёрский запрос к AploDelivery подписывается HMAC-SHA256 четырьмя заголовками. Реализация сервера — internal/auth/hmac.go (Authenticator.MiddlewareWithLimit); эталонная клиентская реализация — pkg/ingressclient (функция signature).

Заголовки

ЗаголовокЗначение
X-API-Keyвыданный вам API-ключ (≥ 8 символов)
X-TimestampUnix time в секундах, время отправки запроса
X-Request-IDновый UUID (формат xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, 36 символов) на каждый запрос
X-Signaturehex-кодированный HMAC-SHA256 от canonical-строки, ключ — ваш HMAC-секрет (≥ 32 символов)

Canonical-строка (base string)

timestamp + "\n" + requestId + "\n" + HTTP_METHOD + "\n" + escapedPath + "\n" + sha256hex(body)

Построчно:

  1. timestamp — то же значение, что в заголовке X-Timestamp, как строка (не число).
  2. requestId — то же значение, что в заголовке X-Request-ID, как есть (в нижнем или как отправлено — сервер сравнивает подпись побайтово с тем, что вы прислали в X-Signature, а сам requestId в canonical-строке подставляется без изменения регистра).
  3. HTTP_METHOD — заглавными буквами, GET или POST.
  4. escapedPath — путь без query-строки, экранированный так же, как (*url.URL).EscapedPath() в Go (для путей ASCII без спецсимволов это обычный путь как есть, например /v1/points или /v1/orders/3fae2b7e-…). Query-параметры не входят в подпись, даже если они есть в запросе.
  5. sha256hex(body) — hex SHA-256 тела запроса. Для GET тело всегда пустое, поэтому это hex SHA-256 пустой последовательности байт (e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 — да, это действительно так выглядит SHA-256 от "", проверьте сами: printf '' | sha256sum). Для POST — SHA-256 ровно тех байт тела, которые вы отправляете; лишний пробел или другой порядок ключей JSON даст другую подпись, хотя сам JSON и будет валиден.

Строки соединяются символом \n (LF, 0x0A), без завершающего перевода строки.

GET с query-параметрами — пример

Для GET /v1/orders?externalOrderId=web-42 в подпись идёт только /v1/orders; query-параметр externalOrderId=web-42 добавляется в URL запроса, но не участвует в canonical-строке.

Допуски и одноразовость

Тестовый вектор для проверки собственной реализации

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

ПараметрЗначение
Секрет (APLO_API_SECRET)test-secret-0123456789abcdef0123456789
X-Timestamp1758610800
X-Request-ID11111111-1111-4111-8111-111111111111
МетодPOST
Путь (без query)/v1/orders
Тело запроса (ровно эти байты, без добавленного перевода строки в конце)см. ниже

Тело запроса (валидный CanonicalOrder):

{"schemaVersion":1,"source":"website","externalOrderId":"site-1042","externalPointId":"3","createdAt":"2026-09-23T10:15:00+03:00","deliverAt":"2026-09-23T11:00:00+03:00","guest":{"externalClientId":"c-9001","name":"Ivan","phone":"+79261234567"},"address":{"street":"Lenina","house":"5"},"items":[{"externalProductId":"321","quantity":"2","price":{"amountMinor":42000,"currency":"RUB"}}],"total":{"amountMinor":84000,"currency":"RUB"}}

Промежуточные и итоговое значения:

ВеличинаЗначение
sha256hex(body)fa8462ece95103e1d8e7771d8bbe5b803ffda8881429cff7978b07289c2ff15a
Canonical-строка1758610800\n11111111-1111-4111-8111-111111111111\nPOST\n/v1/orders\nfa8462ece95103e1d8e7771d8bbe5b803ffda8881429cff7978b07289c2ff15a
X-Signature (hex HMAC-SHA256)f341b5e12580ecf285360538a21c41a663d5a28c1e7dc8f817270861614b68dc

Значение подписи проверено двумя независимыми способами — воспроизведите любой из них и сверьте результат:

Python:

import hashlib, hmac

secret = b"test-secret-0123456789abcdef0123456789"
timestamp = "1758610800"
request_id = "11111111-1111-4111-8111-111111111111"
method = "POST"
path = "/v1/orders"
body = b'{"schemaVersion":1,"source":"website","externalOrderId":"site-1042","externalPointId":"3","createdAt":"2026-09-23T10:15:00+03:00","deliverAt":"2026-09-23T11:00:00+03:00","guest":{"externalClientId":"c-9001","name":"Ivan","phone":"+79261234567"},"address":{"street":"Lenina","house":"5"},"items":[{"externalProductId":"321","quantity":"2","price":{"amountMinor":42000,"currency":"RUB"}}],"total":{"amountMinor":84000,"currency":"RUB"}}'

body_hash = hashlib.sha256(body).hexdigest()
canonical = f"{timestamp}\n{request_id}\n{method}\n{path}\n{body_hash}"
signature = hmac.new(secret, canonical.encode(), hashlib.sha256).hexdigest()
print(signature)  # f341b5e12580ecf285360538a21c41a663d5a28c1e7dc8f817270861614b68dc

curl + openssl (shell):

BODY='{"schemaVersion":1,"source":"website","externalOrderId":"site-1042","externalPointId":"3","createdAt":"2026-09-23T10:15:00+03:00","deliverAt":"2026-09-23T11:00:00+03:00","guest":{"externalClientId":"c-9001","name":"Ivan","phone":"+79261234567"},"address":{"street":"Lenina","house":"5"},"items":[{"externalProductId":"321","quantity":"2","price":{"amountMinor":42000,"currency":"RUB"}}],"total":{"amountMinor":84000,"currency":"RUB"}}'
BODY_HASH="$(printf '%s' "$BODY" | sha256sum | awk '{print $1}')"
TIMESTAMP='1758610800'
REQUEST_ID='11111111-1111-4111-8111-111111111111'
CANONICAL="$(printf '%s\n%s\n%s\n%s\n%s' "$TIMESTAMP" "$REQUEST_ID" "POST" "/v1/orders" "$BODY_HASH")"
SIGNATURE="$(printf '%s' "$CANONICAL" | openssl dgst -sha256 -hmac 'test-secret-0123456789abcdef0123456789' -hex | sed 's/^.* //')"
echo "$SIGNATURE"  # f341b5e12580ecf285360538a21c41a663d5a28c1e7dc8f817270861614b68dc

Если ваша реализация даёт другое значение — самая частая причина: тело сериализовано с другим порядком ключей/пробелами, чем байты выше, либо \n заменён на \r\n, либо путь содержит query-строку.

Второй вектор — GET без тела

Для проверки ветки с пустым телом (пригодится для GET /v1/points, GET /v1/orders/{id}):

ПараметрЗначение
Секретtest-secret-0123456789abcdef0123456789
X-Timestamp1758610800
X-Request-ID22222222-2222-4222-8222-222222222222
МетодGET
Путь/v1/points
sha256hex("")e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
X-Signature7f0aba7fa0f278a2dae872fe05324ccd74475d0e6f0c61755e7c74ef9fa92146

Готовые реализации

Ошибки аутентификации

HTTPerrorПричина
401unauthorizedнеизвестный X-API-Key, отсутствует/невалиден один из заголовков, X-Timestamp вне допуска 60 c, X-Request-ID не является UUID, подпись не совпала, либо X-Request-ID уже был использован этим ключом раньше
503authentication_unavailableсервис не смог проверить одноразовость X-Request-ID (временная недоступность зависимости) — безопасно повторить запрос позже с тем же X-Request-ID, так как он ещё не был принят

Подробный справочник по всем кодам ошибок — Справочник ошибок.