Webhooks
Handling webhook
After each completed payment, Finik sends a POST to your webhookUrl. Check it, then update your order.
Example payload
POST /webhooks/finik
{
"id": "transaction-id-15423_DEBIT", // Finik operation ID (the _DEBIT suffix means a debit)
"transactionId": "transaction-id-241234", // payment transaction ID — use it for idempotency (deduplication)
"status": "success", // payment status; may arrive in any case — check case-insensitively
"amount": 100, // payment amount
"fields": { // payment form fields and your additionalData
"amount": 100, // amount captured on the form
"paymentId": "payment-id-241234", // the PaymentId you passed to POST /v1/payment
"fieldId1": "value1", // value of your additionalData field with fieldId "fieldId1"
"fieldId2": "value2" // value of your additionalData field with fieldId "fieldId2"
},
"requestDate": 1737369012345, // request creation time (UNIX, ms)
"transactionDate": 1737369012345, // transaction processing time (UNIX, ms)
"service": { "id": "VISA" } // present only if card acceptance (Visa) is enabled for you
}What to do
- Verify the signature - Build a canonical string from the incoming request (method, path, host + all headers
x-api-*, query, body), then verify thesignatureheader against the Finik public key for your environment. - Check the timestamp - Make sure
x-api-timestampis within the valid window (eg ±5 minutes). - Ensure idempotency - Use
transactionId(orid) for deduplication. Finik can deliver one webhook several times (“at least once” delivery). - Respond quickly - Return
200 OKright away and do the heavy lifting asynchronously.
Delivery retries
Webhooks are delivered "at least once". If your server does not respond with 200 OK (or responds with an error), Finik retries the delivery.
- First 5 attempts — every 30 seconds.
- After that — the interval doubles (exponential backoff): it starts at 60 seconds and is capped at 24 hours (86,400 seconds).
| Attempt | Delay |
|---|---|
0 | 60 sec (1 min) |
1 | 120 sec (2 min) |
2 | 240 sec (4 min) |
3 | 480 sec (8 min) |
4 | 960 sec (16 min) |
5 | 1920 sec (32 min) |
6 | 3840 sec (1 h 4 min) |
7 | 7680 sec (2 h 8 min) |
8 | 15360 sec (4 h 16 min) |
9 | 30720 sec (8 h 32 min) |
10 | 61440 sec (17 h 4 min) |
11 | 122880 sec → capped to 86400 sec (24 h) |
Once the delay reaches 24 hours it stops growing — all further attempts are made once a day.
How to verify the signature
Below are examples of how to correctly verify the signature.
Install @mancho.devs/authorizer and node-fetch, then:
check-signature.ts
import { Signer } from "@mancho.devs/authorizer";
import * as dotenv from "dotenv";
dotenv.config({ path: ".env.development" });
const publicKey = process.env.FINIK_BETA_PUBLIC_KEY;
const requestData = {
body: {
fields: {
amount: 1,
webhook_url: "https://huntpro.kg/finik-callback",
paymentId: "141fc797-0366-480e-8ba7-2982632b2b2c",
success_redirect_url:
"https://api.acquiring.averspay.kg/v1/redirect?paymentId=141fc797-0366-480e-8ba7-2982632b2b2c&clientId=1295102413&status=succeeded",
name: "HuntPro",
qrTransactionId: "aaa76e79-f808-4ac5-b88e-76a9b4055832",
},
amount: 1,
transactionDate: 1781697937466,
clientId: "25c1aabe-fc4a-4968-85d9-90600466e9a5",
id: "320259584_aaa76e79-f808-4ac5-b88e-76a9b4055832_DEBIT",
status: "succeeded",
transactionId: "141fc797-0366-480e-8ba7-2982632b2b2c",
data: {
accountId: "8d4e1d11-e50c-4278-a5e2-4e55de196c6a",
description: "Order 2606",
webhookUrl: "https://huntpro.kg/finik-callback",
name_en: "HuntPro",
},
},
httpMethod: "POST",
headers: {
"Content-Type": "application/json",
Host: "huntpro.kg",
"x-api-timestamp": "1781763261255",
},
path: "/finik-callback",
queryStringParameters: null,
};
const signature = "jS1a8HW4BTIea8iCmoW/EF7FXUA0JSsHe/kSRBfCYdxFwvyrMe0SOfU9pAKzOOO0bIsa9FW+WqAzqxExHjcscmG8k/vwgB+dCMcM04K91vTL6eZAVOE/Hcx2iALNZnG6UVnP/jnUt8MjK+xwBhamwFktxdUovf+cVFZ2io10RAT1pOEIHtZceXbQeg7XlhcPuTAKmOuF1B4gGoksRtGglGwwyvYaa9ml8OPMP3uoCaauYqT0aUKLTNsJdFfO0Q3Dvsy9uyWvuVa5t6adbIm+SHlbz9VGSZ2xrwgETUBVj86dfA3oiAgERLKhQTBSMTxRYlUKhPVHGfR3kjAhD3g4JQ==";
async function checkSignature() {
const isValid = await new Signer(requestData as any).verify(
publicKey!,
signature,
);
console.log("Is valid signature:", isValid);
}
checkSignature();