Integration

Key generation

Run these two commands in a terminal:

terminal
openssl genrsa -out finik_private.pem 2048
openssl rsa -in finik_private.pem -pubout > finik_public.pem
FilePurpose
finik_private.pemSign every request with this key. Keep it secret because anyone who obtains it can send requests on your behalf.
finik_public.pemSend to Finik via a secure channel (email Finik) so that the service can check your requests.

Signing requests

Each request requires a signature header - RSA-SHA256, Base64-encoded, generated with your private key. Finik provides ready-made packages for Node.js and Python.

Install @mancho.devs/authorizer and node-fetch, then:

create-payment.ts
import { Signer, RequestData } from '@mancho.devs/authorizer';
import fetch from 'node-fetch'; // or axios

// Choose the environment you call:
const baseUrl = 'https://api.acquiring.averspay.kg';      // prod
// const baseUrl = 'https://beta.api.acquiring.averspay.kg'; // beta

// IMPORTANT: Host header must match the URL host exactly.
const host = new URL(baseUrl).host;

// Your credentials
const apiKey = process.env.FINIK_API_KEY!;           // from Finik
const privateKey = process.env.FINIK_PRIVATE_PEM!;   // contents of finik_private.pem
const timestamp = Date.now().toString();             // UNIX ms

// Create Payment body per spec
const body = {
  Amount: 100,
  CardType: 'FINIK_QR',
  PaymentId: '00000000-0000-0000-0000-000000000000',  // use a real UUID
  RedirectUrl: 'https://example.com/success',
  Data: {
    accountId: 'your-account-id',
    name_en: 'your-qr-name',
  },
};

// Build the canonical input for signing
const requestData: RequestData = {
  httpMethod: 'POST',
  path: '/v1/payment',                 // absolute path only (no query)
  headers: {
    Host: host,                        // must match baseUrl host
    'x-api-key': apiKey,               // included in signature
    'x-api-timestamp': timestamp,      // UNIX ms; same value used in signature
    // You may send other headers, but only `host` and `x-api-*` are included in the signature.
  },
  queryStringParameters: undefined,    // or { ... } if you have query; lib sorts & encodes
  body,                                // plain JS object; lib will canonicalize/JSON-stringify
};

// Produce Base64 RSA-SHA256 signature
const signature = await new Signer(requestData).sign(privateKey);

// Send the actual HTTP request
const url = `${baseUrl}${requestData.path}`;
const res = await fetch(url, {
  method: requestData.httpMethod,
  headers: {
    'content-type': 'application/json',
    'x-api-key': apiKey,
    'x-api-timestamp': timestamp,
    signature,                         // <- attach signature header
  },
  body: JSON.stringify(body),
  // If your API currently returns 302 (HTML) on S2S calls, prevent auto-follow:
  redirect: 'manual', // remove once API returns 201 JSON by default
});

if (res.status === 302) {
  // Read Location when you opt into redirects
  console.log('Redirect to:', res.headers.get('location'));
} else {
  console.error(res.status, await res.text());
}

Only Host and headers starting with x-api- are included in the signature. Use the same timestamp value in both the signature and the request header.

Create a payment

POST signed request to:

API endpoints
POST https://api.acquiring.averspay.kg/v1/payment — Production
POST https://beta.api.acquiring.averspay.kg/v1/payment — Beta

Headers

HeaderDescription
signatureThe signature you generated.
x-api-keyYour API key from Finik.
x-api-timestampThe current time in UNIX milliseconds is the same value that was used in the signature.

Request body

FieldTypeRequiredDescription
AmountNumberOptionalFixed payment amount. If not specified, the client can pay any amount.
CardTypeStringRequiredAlways FINIK_QR.
PaymentIdStringRequiredUnique payment ID - prevents duplicate payments.
RedirectUrlStringRequiredWhere Finik sends the client after successful payment.
DataObjectRequiredPayment details - see below.

Data object

FieldTypeRequiredDescription
accountIdStringRequiredYour Finik account ID - where the funds are credited.
name_enStringRequiredThe name of the QR code displayed to the client.
webhookUrlStringRequiredYour server endpoint for payment status notifications.
descriptionStringOptionalDescription on the payment page.
startDateNumberOptionalStart of QR validity period (UNIX ms).
endDateNumberOptionalQR expiration date (UNIX ms).
additionalDataArrayOptionalIf the `value` field is not provided, the customer fills out the form before the payment methods are displayed. The maximum number of fields is 20.

additionalData items

FieldTypeRequiredDescription
fieldIdStringRequiredThe key used for this field in the webhook payload.
nameStringRequiredField label on the payment page.
isHiddenBooleanOptionalHide the field on the payment page.
valueStringOptionalPrefilled value.

Example request body

POST /v1/payment — body
{
  "Amount": 100,
  "CardType": "FINIK_QR",
  "PaymentId": "a3f1c2e4-7b9d-4e2a-8c1f-3d0e9b2a5f6c",
  "RedirectUrl": "https://example.com/success",
  "Data": {
    "accountId": "your-account-id",
    "name_en": "your-qr-name",
    "webhookUrl": "https://merchant.example.com/webhooks/finik",
    "description": "your-qr-description",
    "startDate": 1737369000000,
    "endDate": 1737455400000
  }
}

Response processing

A successful Create Payment returns a 302 redirect with the payment page URL in the Location header:

response
HTTP/1.1 302 Found
Location: https://qr.finik/<payment-path>
  1. Call POST /v1/payment from the backend with redirects disabled.
  2. Read Location - this is the URL of your payment page.
  3. Send this URL to the client - redirect the browser to it or return it to your SPA / mobile application to open in webview.
  4. The client completes the payment and is redirected to your RedirectUrl.
  5. Finik sends a webhook with the final status - consider it the source of truth.

Disable automatic redirects and read the Location header

fetch.js
const res = await fetch("https://api.acquiring.averspay.kg/v1/payment", {
  method: "POST",
  headers: { "content-type": "application/json", "x-api-key": apiKey, "x-api-timestamp": ts, signature },
  body: JSON.stringify(body),
  redirect: "manual", // don't auto-follow
});

if (res.status === 302) {
  const paymentUrl = res.headers.get("location"); // send this to the browser
}

The backend can redirect the browser (302 Location: <paymentUrl>) or return JSON to your SPA and set window.location = paymentUrl.

Want to try it without writing code? Use the interactive Playground to send a test Create Payment request and complete a test payment.