Authentication
sk_test / sk_live API keys, request format, HMAC signing and idempotency. A Bearer key is mandatory; the signature is optional but recommended.
Every protected request needs an Authorization: Bearer <api_key> header. Keys are issued per shop in the Integration tab.
sk_test_…- test key. Hits the simulator; no real bank call. Always available.
sk_live_…- live key. Returns 403 until the merchant is activated for live mode.
Request format
Request and response bodies are JSON in UTF-8. Dates and times use RFC 3339 in UTC. Money amounts are integers in the currency's minor units: kopeks for RUB, cents for USD. Identifiers are UUIDv4.
curl -X POST https://api.nerezpay.ru/v1/public/payments \ -H 'Authorization: Bearer sk_test_x9k…' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: 7f2a-pay-1042'
Dashboard settings and HMAC signing
Everything below is configured in the dashboard: /cabinet/integration → shop card. These settings can\u2019t be changed via the public API: they\u2019re meant for a human, not a server.
Per shop
- API keys.
sk_test_…hits the simulator,sk_live_…hits the real bank. You can issue multiple keys per shop, each revocable in one click. The full secret is shown only once — save it immediately. - IP allow-list. List of IPv4, IPv6, CIDR that\u2019re allowed to use this shop\u2019s keys. Requests from other addresses get
403 ip_not_allowed. An empty list means no restriction. - Shop HMAC secret (
thm_…). Used to sign your requests to our API. If the shop has "Require signature" enabled, requests without anX-PSP-Signature: sha256=<hex>header get 401. A second factor on top of the Bearer key: even if the key leaks via a log or proxy, without the secret no one can forge requests.
Request signing (optional)
When "Require signature" is enabled on the shop, every API request must be signed with the shop secret (thm_…) and placed in the header X-PSP-Signature: sha256=<hex>. Algorithm: HMAC-SHA256 over the raw body — the exact bytes that go over the wire, before any transforms. For GET requests with an empty body sign the empty string.
import crypto from 'node:crypto'
const body = JSON.stringify({
amount: 150000, currency: 'RUB', method: 'sbp', order_id: 'ORDER-1042',
})
const sig = 'sha256=' + crypto
.createHmac('sha256', process.env.PSP_TERMINAL_SECRET) // thm_…
.update(body)
.digest('hex')
await fetch('https://api.nerezpay.ru/v1/public/payments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PSP_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '7f2a-pay-1042',
'X-PSP-Signature': sig,
},
body, // IMPORTANT: exactly the same body you signed — JSON.stringify once
})Webhook endpoints
You can configure several endpoints per shop. Each has its own URL, event set and secret (whs_…). We use that secret to sign webhooks we send you. See Webhooks for details.
thm_…) signs your requests to us. The webhook endpoint secret (whs_…) signs the webhooks we send to you. They\u2019re independent and rotated separately.Idempotency
On resource-creating POSTs (payment, payout) include an Idempotency-Key header of up to 64 characters. A repeated request with the same key returns the previously created object and does not create a duplicate. Keys are unique per merchant; use your own order_id or an application-side UUID.
Repeat with the same key and the same body — 200 OK with the existing object and idempotent: true. Repeat with the same key and a different body — 409 idempotent_conflict.