Authentication

Every merchant API request carries the x-merchant-id header (your public merchant ID, shown on the merchant page in the cabinet) plus one credential header:

  • x-api-key - the default way to authenticate
  • x-sign - optional high-security mode: a per-request RSA-SHA256 signature

If both credential headers are present, only x-api-key is verified. Every authentication failure returns 401 (MERCHANT_NOT_FOUND for a bad merchant id, INVALID_CREDENTIALS for a bad key or signature).

API key - the default

Generate the key on the merchant page in the cabinet. It is shown exactly once - only a hash is stored, so copy it right away. Send it on every request:

curl -X POST https://api.cryptunnel.io/v1/payments/widget \
  -H 'content-type: application/json' \
  -H 'x-merchant-id: <your merchant id>' \
  -H 'x-api-key: ct_live_...' \
  -d '{"amount": 100, "currency": "USD", "external_id": "order-1"}'

That is all a no-code or bot-constructor integration needs - two plain headers, no signing.

Request signing - optional high-security mode

Instead of a static API key, sign every request with your RSA private key. The server stores only your public key (upload it on the merchant page in the cabinet), so a leaked server-side database can never replay your requests.

The x-sign header is an RSA-SHA256 signature in hex, computed over the concatenation of:

  1. The HTTP method, e.g. POST
  2. The full request path including /v1, e.g. /v1/payments/widget
  3. The query parameters as a JSON object, e.g. {"limit":"10"} - only when the request has any
  4. The raw request body bytes - only when the request has a body

Node.js example:

const crypto = require('node:crypto')

function signRequest(privateKeyPem, method, path, query, body) {
  const sign = crypto.createSign('RSA-SHA256')
  sign.update(method)
  sign.update(path)
  if (query && Object.keys(query).length > 0) {
    sign.update(JSON.stringify(query))
  }
  if (body) {
    sign.update(body)
  }
  return sign.sign(privateKeyPem, 'hex')
}

const body = JSON.stringify({ amount: 100, currency: 'USD', external_id: 'order-1' })
const signature = signRequest(privateKey, 'POST', '/v1/payments/widget', undefined, body)
// send headers: x-merchant-id, x-sign: signature

Next steps


Did this page help you?