Integration
Key generation
Run these two commands in a terminal:
openssl genrsa -out finik_private.pem 2048
openssl rsa -in finik_private.pem -pubout > finik_public.pem| File | Purpose |
|---|---|
finik_private.pem | Sign every request with this key. Keep it secret because anyone who obtains it can send requests on your behalf. |
finik_public.pem | Send 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:
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:
POST https://api.acquiring.averspay.kg/v1/payment — Production
POST https://beta.api.acquiring.averspay.kg/v1/payment — BetaHeaders
| Header | Description |
|---|---|
signature | The signature you generated. |
x-api-key | Your API key from Finik. |
x-api-timestamp | The current time in UNIX milliseconds is the same value that was used in the signature. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
Amount | Number | Optional | Fixed payment amount. If not specified, the client can pay any amount. |
CardType | String | Required | Always FINIK_QR. |
PaymentId | String | Required | Unique payment ID - prevents duplicate payments. |
RedirectUrl | String | Required | Where Finik sends the client after successful payment. |
Data | Object | Required | Payment details - see below. |
Data object
| Field | Type | Required | Description |
|---|---|---|---|
accountId | String | Required | Your Finik account ID - where the funds are credited. |
name_en | String | Required | The name of the QR code displayed to the client. |
webhookUrl | String | Required | Your server endpoint for payment status notifications. |
description | String | Optional | Description on the payment page. |
startDate | Number | Optional | Start of QR validity period (UNIX ms). |
endDate | Number | Optional | QR expiration date (UNIX ms). |
additionalData | Array | Optional | If 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
| Field | Type | Required | Description |
|---|---|---|---|
fieldId | String | Required | The key used for this field in the webhook payload. |
name | String | Required | Field label on the payment page. |
isHidden | Boolean | Optional | Hide the field on the payment page. |
value | String | Optional | Prefilled value. |
Example request 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:
HTTP/1.1 302 Found
Location: https://qr.finik/<payment-path>- Call POST /v1/payment from the backend with redirects disabled.
- Read
Location- this is the URL of your payment page. - Send this URL to the client - redirect the browser to it or return it to your SPA / mobile application to open in webview.
- The client completes the payment and is redirected to your
RedirectUrl. - Finik sends a webhook with the final status - consider it the source of truth.
Disable automatic redirects and read the Location header
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.