CentaPay Developer Documentation
Everything you need to integrate cross-border payment acceptance and disbursements for Central Asia.
What is CentaPay?#
CentaPay is an AIFC-incorporated, AFSA-regulated Money Services Provider built to connect international businesses and payment service providers with consumers in Central Asia. We provide the acquiring, processing, and settlement infrastructure — you integrate once and gain access to the full regional network as coverage expands.
All payment flows are cross-border by design. CentaPay does not serve domestic merchants or retail consumers. Our clients are international enterprises and licensed PSPs classified as Professional Clients under AFSA's conduct of business rules.
💳 Accept Payments
Card acquiring (Visa, Mastercard, UZCARD, HUMO), Apple Pay, Google Pay, recurring billing.
💸 Send Payouts
Disburse funds to cards. Virtual account and bank transfer payouts coming soon.
🏢 Direct Merchants
International businesses integrating directly. Dedicated MID, full reporting, settlement in preferred currency.
🔗 PSPs & Platforms
Licensed PSPs adding Central Asian acquiring. Per-merchant MID visibility. Aggregated settlement with sub-merchant reporting.
Coverage#
| Market | Currency | Payment Methods | Payouts | Status |
|---|---|---|---|---|
| 🇰🇿 Kazakhstan | KZT |
Visa, Mastercard, Apple Pay, Google Pay | Visa, Mastercard | Sandbox |
| 🇺🇿 Uzbekistan | UZS |
UZCARD, HUMO, Visa, Mastercard, Apple Pay, Google Pay | Visa, Mastercard | Sandbox |
| 🇰🇬 Kyrgyzstan | KGS |
Visa, Mastercard | — | Planned |
One integration today gives access to the full regional network as additional markets go live. No re-integration required.
Integration Model#
CentaPay uses a Server-to-Server (S2S) API only. Your server submits payment requests directly to our platform. Results are delivered both synchronously (JSON response) and asynchronously (callback to your webhook). Hosted Payment Pages (HPP) are available on request — contact info@centapay.com if HPP better suits your architecture.
99.95% uptime SLO
Production availability target.
T+1 settlement
Next-business-day settlement cycle.
English Common Law
All contracts governed via the AIFC framework.
How to Get Started#
Contact CentaPay
Email info@centapay.com or visit centapay.com/contact.
Sign client agreement
Professional Client classification under AFSA rules.
Receive sandbox credentials
CLIENT_KEY, PASSWORD, and PAYMENT_URL for testing.
Build & test integration
Use this documentation and the sandbox to build your integration.
Complete KYB / PCI review
Provide compliance documentation for production approval.
Go live
Receive production credentials and start processing.
Credentials & Environments#
Before you get an account, you must provide the following data to CentaPay:
| Data | Description |
|---|---|
| IP list | IP addresses from which your server will send requests. Requests from un-whitelisted IPs are rejected silently. |
| Callback URL | URL that will receive transaction result notifications (webhooks). Maximum 255 characters. Mandatory if your account supports 3D Secure. |
| Contact email | Email of the person who will monitor transactions, conduct refunds, and handle operational queries. |
You will receive the following credentials:
| Credential | Description | Where Used |
|---|---|---|
CLIENT_KEY |
Unique key identifying your account (UUID format). Corresponds to the Merchant key field in the admin panel. | Sent as a POST parameter on every request |
PASSWORD |
Secret used only to generate the hash signature. Corresponds to the Password field in the admin panel. |
Hash calculation only — never sent over the wire |
PAYMENT_URL |
Base endpoint URL for your account (different for sandbox and production). | All API requests |
Protocol Mapping#
CentaPay maps specific protocol types to each merchant account. You cannot process payments until the relevant protocol has been mapped by CentaPay's operations team during onboarding. Confirm with your account manager before testing.
| Protocol | Used For | Endpoint |
|---|---|---|
| S2S CARD | Card payments (Visa, MC, UZCARD, HUMO), Apple Pay, Google Pay, CREDIT2CARD payouts | https://{PAYMENT_URL}/post |
Content-Type: application/x-www-form-urlencoded. Responses are JSON-encoded.IP Whitelisting & Callbacks#
Requests from un-whitelisted IP addresses are rejected without a response. If you are testing from a development machine with a dynamic IP, use a tunnelling tool like ngrok for callbacks and let your account manager know your IPs need frequent updating during sandbox testing.
Authentication#
CentaPay uses MD5-based request signing. Every API request includes a hash parameter — a signature computed from specific request fields and your account password. There are no Bearer tokens or API key headers.
How It Works#
The hash is computed by reversing specific field values, concatenating them with your PASSWORD, converting to uppercase, then taking the MD5 digest. The exact formula varies by action — see the table below.
PASSWORD never leaves your server. Only the resulting hash is transmitted. If a formula references optional parameters that you do not send in the request, omit them from the hash calculation.Formula Reference#
In all formulas below, strrev() means reverse the string, strtoupper() means convert to uppercase, and md5() means compute the MD5 hex digest.
Formula 1 — SALE, RETRY, RECURRING_SALE
md5(strtoupper( strrev(email) . PASSWORD . strrev(substr(card_number, 0, 6) . substr(card_number, -4)) ))
function hashFormula1(string $email, string $password, string $cardNumber): string
{
$cardPart = substr($cardNumber, 0, 6) . substr($cardNumber, -4);
$raw = strrev($email) . $password . strrev($cardPart);
return md5(strtoupper($raw));
}import hashlib
def hash_formula_1(email: str, password: str, card_number: str) -> str:
card_part = card_number[:6] + card_number[-4:]
raw = email[::-1] + password + card_part[::-1]
return hashlib.md5(raw.upper().encode()).hexdigest()const crypto = require('crypto');
function hashFormula1(email, password, cardNumber) {
const cardPart = cardNumber.slice(0, 6) + cardNumber.slice(-4);
const raw = strrev(email) + password + strrev(cardPart);
return crypto.createHash('md5').update(raw.toUpperCase()).digest('hex');
}
function strrev(s) { return s.split('').reverse().join(''); }When card_token is provided instead of card data:
md5(strtoupper( strrev(email) . PASSWORD . strrev(card_token) ))
When digital_wallet is used (Apple Pay / Google Pay) — Formula 8:
md5(strtoupper( strrev(email) . PASSWORD ))
Formula 2 — CAPTURE, CREDITVOID, VOID, GET_TRANS_STATUS, Callback verification
md5(strtoupper( strrev(email) . PASSWORD . trans_id . strrev(substr(card_number, 0, 6) . substr(card_number, -4)) ))
function hashFormula2(string $email, string $password, string $transId, string $cardNumber): string
{
$cardPart = substr($cardNumber, 0, 6) . substr($cardNumber, -4);
$raw = strrev($email) . $password . $transId . strrev($cardPart);
return md5(strtoupper($raw));
}
// For callback verification, use the card mask from the callback
// (first 6 + last 4 digits visible in the masked PAN)def hash_formula_2(email: str, password: str, trans_id: str, card_number: str) -> str:
card_part = card_number[:6] + card_number[-4:]
raw = email[::-1] + password + trans_id + card_part[::-1]
return hashlib.md5(raw.upper().encode()).hexdigest()function hashFormula2(email, password, transId, cardNumber) {
const cardPart = cardNumber.slice(0, 6) + cardNumber.slice(-4);
const raw = strrev(email) + password + transId + strrev(cardPart);
return crypto.createHash('md5').update(raw.toUpperCase()).digest('hex');
}Formula 3 — CREATE_SCHEDULE
md5(strtoupper(strrev(PASSWORD)))
Formula 4 — PAUSE / RUN / DELETE / SCHEDULE_INFO / DESCHEDULE
md5(strtoupper(strrev(schedule_id + PASSWORD)))
Formula 5 — CREDIT2CARD
md5(strtoupper( PASSWORD . strrev(substr(card_number, 0, 6) . substr(card_number, -4)) )) // With card_token instead: md5(strtoupper(PASSWORD . strrev(card_token)))
Formula 6 — CREDIT2CARD callbacks & GET_TRANS_STATUS (for CREDIT2CARD)
md5(strtoupper( PASSWORD . trans_id . strrev(substr(card_number, 0, 6) . substr(card_number, -4)) ))
Formula 7 — GET_TRANS_STATUS_BY_ORDER
md5(strtoupper( strrev(email) . PASSWORD . order_id . strrev(substr(card_number, 0, 6) . substr(card_number, -4)) ))
Formula Quick Reference#
| Action | Formula | Key Inputs |
|---|---|---|
| SALE (card) | Formula 1 | email + PASSWORD + card first6/last4 |
| SALE (card_token) | Formula 1 variant | email + PASSWORD + card_token |
| SALE (digital wallet) | Formula 8 | email + PASSWORD only |
| CAPTURE | Formula 2 | email + PASSWORD + trans_id + card |
| CREDITVOID | Formula 2 | email + PASSWORD + trans_id + card |
| VOID | Formula 2 | email + PASSWORD + trans_id + card |
| RECURRING_SALE | Formula 1 | email + PASSWORD + card first6/last4 |
| RETRY | Formula 1 | email + PASSWORD + card first6/last4 |
| CREDIT2CARD | Formula 5 | PASSWORD + card first6/last4 |
| CREDIT2CARD callback | Formula 6 | PASSWORD + trans_id + card |
| GET_TRANS_STATUS | Formula 2 (or 6 for CREDIT2CARD) | See formula |
| GET_TRANS_DETAILS | Formula 2 (or 6 for CREDIT2CARD) | See formula |
| GET_TRANS_STATUS_BY_ORDER | Formula 7 (or 6 for CREDIT2CARD) | email + PASSWORD + order_id + card |
| CREATE_SCHEDULE | Formula 3 | PASSWORD only |
| Other schedule ops | Formula 4 | schedule_id + PASSWORD |
| Callback (all except CREDIT2CARD, VOID) | Formula 2 | email + PASSWORD + trans_id + card |
| Callback (CREDIT2CARD) | Formula 6 | PASSWORD + trans_id + card |
| Callback (VOID) | TBC | Source references undefined "Void signature" - verify with Akurateco |
Quickstart#
Process your first card payment through CentaPay in under 30 minutes. This guide assumes you have received sandbox credentials from your account manager.
What You Need#
| Credential | Description |
|---|---|
CLIENT_KEY | Your unique account identifier. Sent as a POST parameter on every request. |
PASSWORD | Used only to generate the hash signature. Never sent directly over the wire. |
PAYMENT_URL | The endpoint URL for your account (sandbox or production). |
You also need to provide CentaPay with your server IP address(es) and a callback (webhook) URL. Ensure the S2S CARD protocol has been mapped to your account.
Your First SALE Request#
All requests are HTTPS POST to {PAYMENT_URL}/post. The body must be URL-encoded form data — not JSON.
https://{PAYMENT_URL}/post
curl -X POST https://{PAYMENT_URL}/post \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "action=SALE" \
-d "client_key={CLIENT_KEY}" \
-d "order_id=ORD-001" \
-d "order_amount=5000" \
-d "order_currency=KZT" \
-d "order_description=Product+purchase" \
-d "card_number=4111111111111111" \
-d "card_exp_month=01" \
-d "card_exp_year=2038" \
-d "card_cvv2=000" \
-d "payer_first_name=John" \
-d "payer_last_name=Smith" \
-d "payer_email=john@example.com" \
-d "payer_phone=77001234567" \
-d "payer_country=KZ" \
-d "payer_city=Astana" \
-d "payer_address=10+Kunayeva+St" \
-d "payer_zip=010000" \
-d "payer_ip=192.168.1.1" \
-d "term_url_3ds=https://yoursite.com/3ds-return" \
-d "hash={CALCULATED_HASH}"<?php
function calcHash(string $email, string $password, string $cardNumber): string
{
$cardPart = substr($cardNumber, 0, 6) . substr($cardNumber, -4);
$raw = strrev($email) . $password . strrev($cardPart);
return md5(strtoupper($raw));
}
$cardNumber = '4111111111111111';
$params = [
'action' => 'SALE',
'client_key' => CLIENT_KEY,
'order_id' => 'ORD-001',
'order_amount' => '5000',
'order_currency' => 'KZT',
'order_description'=> 'Product purchase',
'card_number' => $cardNumber,
'card_exp_month' => '01',
'card_exp_year' => '2038',
'card_cvv2' => '000',
'payer_first_name' => 'John',
'payer_last_name' => 'Smith',
'payer_email' => 'john@example.com',
'payer_phone' => '77001234567',
'payer_country' => 'KZ',
'payer_city' => 'Astana',
'payer_address' => '10 Kunayeva St',
'payer_zip' => '010000',
'payer_ip' => $_SERVER['REMOTE_ADDR'],
'term_url_3ds' => 'https://yoursite.com/3ds-return',
];
$params['hash'] = calcHash($params['payer_email'], PASSWORD, $cardNumber);
$ch = curl_init(PAYMENT_URL . '/post');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($params),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => true,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);import requests, hashlib
def calc_hash(email: str, password: str, card_number: str) -> str:
card_part = card_number[:6] + card_number[-4:]
raw = email[::-1] + password + card_part[::-1]
return hashlib.md5(raw.upper().encode()).hexdigest()
card_number = '4111111111111111'
params = {
'action': 'SALE',
'client_key': CLIENT_KEY,
'order_id': 'ORD-001',
'order_amount': '5000',
'order_currency': 'KZT',
'order_description': 'Product purchase',
'card_number': card_number,
'card_exp_month': '01',
'card_exp_year': '2038',
'card_cvv2': '000',
'payer_first_name': 'John',
'payer_last_name': 'Smith',
'payer_email': 'john@example.com',
'payer_phone': '77001234567',
'payer_country': 'KZ',
'payer_city': 'Astana',
'payer_address': '10 Kunayeva St',
'payer_zip': '010000',
'payer_ip': '192.168.1.1',
'term_url_3ds': 'https://yoursite.com/3ds-return',
}
params['hash'] = calc_hash(params['payer_email'], PASSWORD, card_number)
resp = requests.post(f'{PAYMENT_URL}/post', data=params)
result = resp.json()const axios = require('axios');
const qs = require('qs');
const crypto = require('crypto');
const strrev = s => s.split('').reverse().join('');
function calcHash(email, password, cardNumber) {
const cardPart = cardNumber.slice(0, 6) + cardNumber.slice(-4);
const raw = strrev(email) + password + strrev(cardPart);
return crypto.createHash('md5').update(raw.toUpperCase()).digest('hex');
}
const cardNumber = '4111111111111111';
const params = {
action: 'SALE',
client_key: CLIENT_KEY,
order_id: 'ORD-001',
order_amount: '5000',
order_currency: 'KZT',
order_description: 'Product purchase',
card_number: cardNumber,
card_exp_month: '01',
card_exp_year: '2038',
card_cvv2: '000',
payer_first_name: 'John',
payer_last_name: 'Smith',
payer_email: 'john@example.com',
payer_phone: '77001234567',
payer_country: 'KZ',
payer_city: 'Astana',
payer_address: '10 Kunayeva St',
payer_zip: '010000',
payer_ip: req.ip,
term_url_3ds: 'https://yoursite.com/3ds-return',
};
params.hash = calcHash(params.payer_email, PASSWORD, cardNumber);
const { data } = await axios.post(
`${PAYMENT_URL}/post`,
qs.stringify(params),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);4111111111111111 with expiry 01/2038 returns a successful SALE. See Test Cards for all simulation scenarios.Handling the Response#
result | Meaning | Your Next Step |
|---|---|---|
| SUCCESS | Transaction authorised or settled | Store trans_id. Await callback to confirm final settlement status. |
| REDIRECT | 3DS authentication required | Redirect cardholder to redirect_url. See 3D Secure. |
| DECLINED | Transaction rejected | Display decline_reason to user. Do not retry immediately. |
| UNDEFINED | Status unknown — processing | Do not fulfil or cancel. Await callback for final result. |
| ERROR | Request failed validation | Check parameters and hash. Fix before retrying. See Error Codes. |
Next Steps#
→ Authentication
Understand all hash formulas and verify your implementation.
→ 3D Secure
Most KZ/UZ transactions trigger 3DS. Implement the redirect handler before going to production.
→ Callbacks
Set up your webhook endpoint and verify callback hash signatures.
→ Test Cards
Use test cards to simulate all transaction outcomes.
Cards#
CentaPay supports Visa, Mastercard, UZCARD, and HUMO for card-present-not-present (CNP) transactions across Kazakhstan and Uzbekistan.
SALE — Single Message (SMS)#
Authorise and capture in one step. This is the standard flow for immediate payments.
https://{PAYMENT_URL}/post
curl -X POST https://{PAYMENT_URL}/post \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "action=SALE" \
-d "client_key={CLIENT_KEY}" \
-d "order_id=ORD-001" \
-d "order_amount=5000" \
-d "order_currency=KZT" \
-d "order_description=Product+purchase" \
-d "card_number=4111111111111111" \
-d "card_exp_month=01" \
-d "card_exp_year=2038" \
-d "card_cvv2=000" \
-d "payer_first_name=John" \
-d "payer_last_name=Smith" \
-d "payer_email=john@example.com" \
-d "payer_phone=77001234567" \
-d "payer_country=KZ" \
-d "payer_city=Astana" \
-d "payer_address=10+Kunayeva+St" \
-d "payer_zip=010000" \
-d "payer_ip=192.168.1.1" \
-d "term_url_3ds=https://yoursite.com/3ds-return" \
-d "hash={HASH}"$card = '4111111111111111'; $params = [ 'action' => 'SALE', 'client_key' => CLIENT_KEY, 'order_id' => 'ORD-001', 'order_amount' => '5000', 'order_currency' => 'KZT', 'order_description'=> 'Product purchase', 'card_number' => $card, 'card_exp_month' => '01', 'card_exp_year' => '2038', 'card_cvv2' => '000', 'payer_first_name' => 'John', 'payer_last_name' => 'Smith', 'payer_email' => 'john@example.com', 'payer_phone' => '77001234567', 'payer_country' => 'KZ', 'payer_city' => 'Astana', 'payer_address' => '10 Kunayeva St', 'payer_zip' => '010000', 'payer_ip' => $_SERVER['REMOTE_ADDR'], 'term_url_3ds' => 'https://yoursite.com/3ds-return', ]; // Formula 1 hash $cp = substr($card,0,6).substr($card,-4); $params['hash'] = md5(strtoupper( strrev($params['payer_email']).PASSWORD.strrev($cp) )); $ch = curl_init(PAYMENT_URL.'/post'); curl_setopt_array($ch,[ CURLOPT_POST=>true, CURLOPT_POSTFIELDS=>http_build_query($params), CURLOPT_RETURNTRANSFER=>true, ]); $result = json_decode(curl_exec($ch),true);
import requests, hashlib
card = '4111111111111111'
email = 'john@example.com'
cp = card[:6] + card[-4:]
h = hashlib.md5((email[::-1] + PASSWORD + cp[::-1]).upper().encode()).hexdigest()
resp = requests.post(f'{PAYMENT_URL}/post', data={
'action': 'SALE',
'client_key': CLIENT_KEY,
'order_id': 'ORD-001',
'order_amount': '5000',
'order_currency': 'KZT',
'order_description': 'Product purchase',
'card_number': card,
'card_exp_month': '01',
'card_exp_year': '2038',
'card_cvv2': '000',
'payer_first_name': 'John',
'payer_last_name': 'Smith',
'payer_email': email,
'payer_phone': '77001234567',
'payer_country': 'KZ',
'payer_city': 'Astana',
'payer_address': '10 Kunayeva St',
'payer_zip': '010000',
'payer_ip': '192.168.1.1',
'term_url_3ds': 'https://yoursite.com/3ds-return',
'hash': h,
})const crypto = require('crypto');
const axios = require('axios');
const qs = require('qs');
const rev = s => s.split('').reverse().join('');
const card = '4111111111111111';
const email = 'john@example.com';
const cp = card.slice(0,6) + card.slice(-4);
const hash = crypto.createHash('md5')
.update((rev(email) + PASSWORD + rev(cp)).toUpperCase())
.digest('hex');
const {data} = await axios.post(`${PAYMENT_URL}/post`,
qs.stringify({
action:'SALE', client_key:CLIENT_KEY,
order_id:'ORD-001', order_amount:'5000',
order_currency:'KZT', order_description:'Product purchase',
card_number:card, card_exp_month:'01',
card_exp_year:'2038', card_cvv2:'000',
payer_first_name:'John', payer_last_name:'Smith',
payer_email:email, payer_phone:'77001234567',
payer_country:'KZ', payer_city:'Astana',
payer_address:'10 Kunayeva St', payer_zip:'010000',
payer_ip:req.ip,
term_url_3ds:'https://yoursite.com/3ds-return',
hash
}));Currencies#
| Country | Currency | Code | Amount Format |
|---|---|---|---|
| 🇰🇿 Kazakhstan | Kazakhstani Tenge | KZT | Integer — e.g. 5000 |
| 🇺🇿 Uzbekistan | Uzbekistani Som | UZS | Integer — e.g. 120000 |
| International | US Dollar | USD | Float XX.XX — e.g. 49.99 |
AUTH & CAPTURE — Dual Message (DMS)#
Authorise only (hold funds), then capture separately. Add auth=Y to the SALE request. For AUTH, you can set order_amount to 0 for card validation / tokenisation only.
To capture, send a CAPTURE request with the trans_id from the AUTH response. Partial capture is supported (once only). See CAPTURE reference.
status: PENDING. The funds are held on the cardholder's account. You must CAPTURE within the hold period (typically 7 days, varies by issuer) or the hold expires.Tokenisation#
Request a card token on the first transaction by adding req_token=Y to the SALE request. The response and callback return a card_token you can store and reuse for future charges without transmitting card data again.
To charge using a stored token, send card_token instead of card_number, card_exp_month, card_exp_year, card_cvv2. The hash formula changes — use the card_token variant of Formula 1.
Digital Wallets#
Accept Apple Pay and Google Pay via S2S using payment tokens obtained from the wallet APIs on the client side. No card data touches your server.
Apple Pay#
Prerequisites
In your Apple Developer account:
- Create a Merchant ID in Certificates, Identifiers & Profiles
- Register and verify all payment domains
- Create a Merchant Identity Certificate — generate
*.csr+*.key, upload CSR, download*.pem - Create a Processing Private Key (Payment Processing Certificate) for token decryption
Then configure the admin panel (Merchants → Wallets → Apple Pay) with: Merchant Identifier, Certificate (.pem), Private Key (.key), Processing Private Key.
Client-Side Flow
Check Apple Pay availability
Use ApplePaySession.canMakePayments()
Show Apple Pay button
Per Apple's UX guidelines
Validate merchant identity
Server-side merchant validation session
Create payment request & session
Customer authorises with Face ID / Touch ID
Receive Apple Pay token
Pass the paymentData object to your server
SALE Request — Apple Pay
Include digital_wallet=applepay and payment_token (the full Apple Pay token JSON). Omit all card fields. Use Formula 8 for the hash.
curl -X POST https://{PAYMENT_URL}/post \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "action=SALE" \
-d "client_key={CLIENT_KEY}" \
-d "order_id=ORD-AP-001" \
-d "order_amount=99.99" \
-d "order_currency=USD" \
-d "order_description=Purchase" \
-d "digital_wallet=applepay" \
-d "payment_token={APPLE_PAY_TOKEN_JSON}" \
-d "payer_first_name=Jane" \
-d "payer_last_name=Doe" \
-d "payer_email=jane@example.com" \
-d "payer_phone=77001234567" \
-d "payer_country=KZ" \
-d "payer_city=Astana" \
-d "payer_address=10+Kunayeva" \
-d "payer_zip=010000" \
-d "payer_ip=192.168.1.1" \
-d "term_url_3ds=https://yoursite.com/return" \
-d "hash={FORMULA_8_HASH}"// Formula 8: md5(strtoupper(strrev(email) . PASSWORD)) $hash = md5(strtoupper(strrev($email) . PASSWORD)); $params = [ 'action' => 'SALE', 'client_key' => CLIENT_KEY, 'digital_wallet' => 'applepay', 'payment_token' => $applePayTokenJson, // ... all payer fields ... 'hash' => $hash, ];
Apple Pay Payment Flow
By default, Apple Pay payments are classified as virtual — card details are not stored, and DMS / recurring creation is limited. To enable the card flow (decrypt token, store card data for recurring):
- Set up the Processing Private Key in the admin panel
- Verify your payment provider supports card flow (contact support)
Google Pay#
Prerequisites
- Review Google Pay Web or Android documentation
- Complete the integration checklist and branding requirements
- Verify domains in Google Business Console
- Adhere to Google Pay APIs Acceptable Use Policy and Terms of Service
Obtaining the Token
Get PaymentData using these parameters:
allowPaymentMethods: CARDtokenizationSpecification: { "type": "PAYMENT_GATEWAY" }allowedCardNetworks: ['MASTERCARD', 'VISA', 'AMEX', 'DISCOVER', 'JCB']allowedCardAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS']gateway= value from your CentaPay account managergatewayMerchantId= yourCLIENT_KEY
SALE Request — Google Pay
Identical to Apple Pay but with digital_wallet=googlepay. Use Formula 8 for the hash. Omit card fields.
PAN_ONLY auth method, 3DS responsibility transfers to the acquirer. Ensure your acquirer supports this.Google Pay Payment Flow
Same as Apple Pay: payments default to virtual. To enable card flow, configure the Private Key in admin panel and verify provider support. The "Environment" setting must match (TEST or PRODUCTION) between your token generation and admin panel config.
Recurring Payments#
Charge customers on a schedule using stored card data from an initial transaction.
Setup — Initial Transaction#
Add recurring_init=Y to the initial SALE request. If the transaction succeeds, the response and callback include a recurring_token. Store this token against the customer record.
4111111111111111 with expiry 01/2038 supports recurring token generation in sandbox. Other test cards will not return a recurring token.RECURRING_SALE#
Creates a new transaction using stored cardholder data from a previous operation. Uses Formula 1 for the hash.
https://{PAYMENT_URL}/post
| Parameter | Description | Required |
|---|---|---|
action | RECURRING_SALE | Yes |
client_key | Your account key (UUID) | Yes |
order_id | New unique order ID for this charge | Yes |
order_amount | Amount to charge | Yes |
order_currency | Currency code | Yes |
order_description | Description for this charge | Yes |
recurring_first_trans_id | Transaction ID of the primary transaction | Yes |
recurring_token | Token from original transaction | Yes |
schedule_id | Link to a schedule object | No |
auth | Y for auth-only | No |
hash | Formula 1 | Yes |
Response is identical to SALE (with action=RECURRING_SALE).
card mask from the initial SALE callback) alongside the recurring_token to compute hashes for subsequent charges. You also need the payer's email from the initial transaction.RETRY#
Retry a soft-declined recurring transaction. Only valid for soft declines — hard declines (stolen card, fraud) will not succeed. Check decline_reason before retrying.
| Parameter | Description | Required |
|---|---|---|
action | RETRY | Yes |
client_key | Your account key | Yes |
trans_id | Transaction ID of the declined recurring transaction | Yes |
hash | Formula 1 | Yes |
Synchronous response returns result: ACCEPTED. Final result delivered via callback with action: RETRY.
Schedule Management#
Schedules automate recurring charges at defined intervals. All schedule operations use POST https://{PAYMENT_URL}/post.
| Action | Purpose | Hash |
|---|---|---|
CREATE_SCHEDULE | Create a new schedule with name, interval, payment count | Formula 3 |
PAUSE_SCHEDULE | Suspend scheduled payments | Formula 4 |
RUN_SCHEDULE | Resume a paused schedule | Formula 4 |
DELETE_SCHEDULE | Permanently delete a schedule | Formula 4 |
SCHEDULE_INFO | Get schedule details | Formula 4 |
DESCHEDULE | Stop payments by schedule (requires recurring_token + schedule_id) | Formula 4 |
CREATE_SCHEDULE Parameters
| Parameter | Description | Required |
|---|---|---|
action | CREATE_SCHEDULE | Yes |
client_key | Your account key | Yes |
name | Schedule name (up to 100 chars) | Yes |
interval_length | How often payments occur (cannot be 0) | Yes |
interval_unit | day or month | Yes |
day_of_month | 1–31; only if interval_unit=month. If 29/30/31 and month is shorter, last day is used | No |
payments_count | Total number of payments | Yes |
delays | Number of skipped intervals before cycle starts | No |
hash | Formula 3 | Yes |
Response returns schedule_id which you pass to SALE or RECURRING_SALE via the schedule_id parameter.
Full parameter tables for all schedule operations are in the API Reference.
3D Secure#
Most Kazakhstan and Uzbekistan card transactions trigger 3DS authentication. Your integration must handle the redirect flow before going to production.
3DS Flow#
Send SALE request
Your server posts to CentaPay as normal.
Receive REDIRECT response
Response contains result: REDIRECT, status: 3DS, and redirect_url.
Redirect cardholder to ACS
Build an HTML form or JS redirect to send the customer to the issuer's 3DS page.
Cardholder authenticates
SMS code, biometric, or frictionless flow on the issuer's page.
Return to your site
Customer returns to your term_url_3ds.
Receive callback
CentaPay sends the final result (SUCCESS or DECLINED) to your callback URL.
Handling the Redirect#
The SALE response for a 3DS transaction contains:
| Field | Description |
|---|---|
redirect_url | URL to redirect the cardholder to |
redirect_params | Object of 3DS parameters (key-value pairs). May be an empty array or absent entirely. |
redirect_method | POST or GET |
redirect_params varies by acquirer. It may contain PaReq, TermUrl, or other values. It may also be empty or missing (commonly when redirect_method=GET). Always check for its presence before processing.Option 1: HTML Form (POST or GET)
// Build auto-submitting form from SALE response
$html = '<form id="3ds" method="'
. $response['redirect_method']
. '" action="' . $response['redirect_url'] . '">';
if (!empty($response['redirect_params'])
&& is_array($response['redirect_params'])) {
foreach ($response['redirect_params'] as $k => $v) {
$html .= '<input type="hidden" name="'
. htmlspecialchars($k) . '" value="'
. htmlspecialchars($v) . '">';
}
}
$html .= '</form>';
$html .= '<script>document.getElementById("3ds").submit();</script>';
echo $html;Option 2: JavaScript Redirect (GET with query params)
If redirect_method=GET and the URL already contains query parameters:
document.location = response.redirect_url;
Alternative Endpoint: /v2/post#
The standard endpoint /post returns redirect_params as a key-value object. The alternative endpoint /v2/post returns them as an array of {name, value} objects:
"redirect_params": {
"PaReq": "eJxVUt1...",
"TermUrl": "https://..."
}"redirect_params": [
{"name": "PaReq", "value": "eJxVUt1..."},
{"name": "TermUrl", "value": "https://..."}
]term_url_target#
If your checkout runs inside an iframe, use term_url_target to control where the customer returns after 3DS. Values: _blank, _self, _parent, _top (default), or a custom iframe name.
Card Payouts (CREDIT2CARD)#
Push funds from your CentaPay settlement balance to a recipient's card. Used for merchant payouts, refunds to alternate cards, or disbursements to end-users.
https://{PAYMENT_URL}/post
Request Parameters#
| Parameter | Description | Required |
|---|---|---|
action | CREDIT2CARD | Yes |
client_key | Your account key | Yes |
channel_id | Sub-account (up to 16 chars) | No |
order_id | Your unique transaction ID | Yes |
order_amount | Amount to disburse. Integer for KZT/UZS, float XX.XX for USD. | Yes |
order_currency | ISO 4217 code | Yes |
order_description | Description (up to 1024 chars) | Yes |
card_number | Recipient's card number | Yes |
payee_first_name | Recipient's first name | No |
payee_last_name | Recipient's last name | No |
payee_middle_name | Recipient's middle name | No |
payee_birth_date | Format: yyyy-MM-dd | No |
payee_address | Recipient's address | No |
payee_country | 2-letter code | No |
payee_city | Recipient's city | No |
payee_zip | Postal code | No |
payee_email | Recipient's email | No |
payee_phone | Recipient's phone | No |
payer_first_name | Sender's first name | No |
payer_last_name | Sender's last name | No |
parameters | Acquirer-specific extra fields | No |
hash | Formula 5 | Yes |
Response#
result | status | Meaning |
|---|---|---|
| SUCCESS | SETTLED | Payout completed |
| DECLINED | DECLINED | Payout rejected — check decline_reason |
| UNDEFINED | PREPARE | Processing — await callback |
Callback#
Callback hash uses Formula 6 (not Formula 2). This is the only action with a different callback hash formula.
Testing#
Use test card 4601541833776519 for successful CREDIT2CARD. Response: {action: CREDIT2CARD, result: SUCCESS, status: SETTLED}.
curl -X POST https://{PAYMENT_URL}/post \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "action=CREDIT2CARD" \
-d "client_key={CLIENT_KEY}" \
-d "order_id=PAY-001" \
-d "order_amount=10000" \
-d "order_currency=KZT" \
-d "order_description=Merchant+payout" \
-d "card_number=4601541833776519" \
-d "hash={FORMULA_5_HASH}"// Formula 5: md5(strtoupper(PASSWORD . strrev(first6+last4))) $card = '4601541833776519'; $cp = substr($card,0,6).substr($card,-4); $hash = md5(strtoupper(PASSWORD . strrev($cp))); $params = [ 'action' => 'CREDIT2CARD', 'client_key' => CLIENT_KEY, 'order_id' => 'PAY-001', 'order_amount' => '10000', 'order_currency' => 'KZT', 'order_description' => 'Merchant payout', 'card_number' => $card, 'hash' => $hash, ];
Virtual Account Payouts#
Direct Merchants#
International businesses integrating directly with CentaPay. Each direct merchant receives a dedicated MID, full transaction reporting, and settlement in their preferred currency.
Onboarding#
| Step | Detail |
|---|---|
| 1. Contact CentaPay | Email info@centapay.com or visit centapay.com/contact |
| 2. Client agreement | Professional Client classification under AFSA conduct of business rules |
| 3. KYB & PCI review | Submit compliance documentation for approval |
| 4. Sandbox credentials | Receive CLIENT_KEY, PASSWORD, and PAYMENT_URL |
| 5. Build & test | Integrate using this documentation and the sandbox environment |
| 6. Go live | Receive production credentials and start processing |
Settlement#
Settlement Cycle
CentaPay settles to clients on a T+1 (next business day) cycle. Transactions that reach SETTLED status before the daily cutoff are included in the next business day's settlement batch.
| Parameter | Value |
|---|---|
| Cycle | T+1 (next business day) |
| Settlement currency | Agreed at onboarding (typically USD) |
Transaction Status Lifecycle
How a transaction moves through the system toward settlement:
| Status | Meaning |
|---|---|
PENDING | AUTH hold placed — funds reserved on cardholder's account but not yet captured |
PREPARE | Processing in progress — final status not yet determined |
3DS | Awaiting cardholder 3DS authentication |
REDIRECT | Awaiting redirect completion |
SETTLED | Transaction completed successfully. Funds will be included in the next settlement batch to you. |
DECLINED | Transaction rejected by issuer or risk engine |
VOID | Same-day cancellation — no funds movement |
REFUND | Full refund processed — amount deducted from your settlement balance |
REVERSAL | AUTH hold released (no capture occurred) |
CHARGEBACK | Issuer-initiated dispute — amount debited from your settlement balance |
SETTLED status. The partial refund amount is deducted from your settlement balance. Use GET_TRANS_DETAILS to see the full transaction history including partial refund entries.FX & Currency Conversion
When the transaction currency differs from your settlement currency, CentaPay applies a foreign exchange conversion. The applicable rate is included in callback parameters:
| Callback Field | Description |
|---|---|
exchange_rate | FX rate applied to the transaction |
exchange_rate_base | Base conversion rate (if double conversion applies) |
exchange_currency | Original transaction currency |
exchange_amount | Original transaction amount before conversion |
Settlement Reports
Settlement reports are available via the admin panel and include transaction-level detail for reconciliation against your callback records. Contact info@centapay.com for details on report format, delivery schedule, and available export options for your account.
Querying Settlement Status
Use GET_TRANS_STATUS or GET_TRANS_DETAILS to check whether a transaction has settled.
GET_TRANS_STATUS returns the current status field. A value of SETTLED confirms the transaction completed successfully and will be included in settlement.
GET_TRANS_DETAILS returns the full order history including an array of all sub-transactions (sale, 3ds, auth, capture, credit, chargeback, reversal, refund) with individual dates, statuses, and amounts.
GET_TRANS_STATUS for settlement confirmation. Use callbacks as the authoritative source. Reserve status queries for reconciliation or when a callback has not arrived within your expected window.PSPs & Platforms#
Licensed payment service providers adding Central Asian acquiring to their platform. CentaPay provides per-merchant MID visibility with aggregated settlement and sub-merchant reporting.
PayFac Model#
PSP clients operate under a Payment Facilitator (PayFac) or Merchant of Record (MOR) structure. CentaPay holds the acquiring relationship; you onboard and manage your sub-merchants under your account.
Sub-Merchants#
Use the channel_id parameter to route transactions to specific sub-merchants. This value is echoed in callbacks and available in reporting, enabling you to reconcile at sub-merchant level.
KYC Delegation#
PSP clients retain responsibility for KYC on their sub-merchants. CentaPay performs KYC on the PSP itself. Sub-merchant due diligence obligations are set out in your client agreement. See AML Obligations for details.
Settlement & Reporting#
PSP clients receive a single aggregated settlement per cycle covering all sub-merchant transactions.
| Parameter | Value |
|---|---|
| Cycle | T+1 (next business day) |
| Settlement currency | Agreed at onboarding (typically USD) |
| Scope | All sub-merchants under your account, aggregated |
Each transaction callback includes order_id (your reference), trans_id (CentaPay reference), and channel_id (if provided) for sub-merchant-level reconciliation.
Use GET_TRANS_DETAILS to retrieve the full history of any transaction, including payer details, masked card, and all status transitions.
rrn, approval_code, connector_name, and other acquirer-level fields in callbacks — useful for cross-referencing with upstream acquirer reports.For details on sub-merchant settlement breakdowns and report formats, contact info@centapay.com.
Refunds & Reversals (CREDITVOID)#
CREDITVOID handles both reversals (cancel an AUTH hold) and refunds (return settled funds). Full and partial refunds are supported. Multiple partial refunds are allowed.
https://{PAYMENT_URL}/post
| Parameter | Description | Required |
|---|---|---|
action | CREDITVOID | Yes |
client_key | Your account key | Yes |
trans_id | Original CentaPay transaction ID | Yes |
amount | Partial refund amount. Omit for full refund. | No |
hash | Formula 2 | Yes |
Synchronous response returns result: ACCEPTED. Final result delivered via callback:
Callback status | Meaning |
|---|---|
REFUND or REVERSAL | Full refund / full reversal completed |
SETTLED | Partial refund applied (original transaction remains settled) |
curl -X POST https://{PAYMENT_URL}/post \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "action=CREDITVOID" \
-d "client_key={CLIENT_KEY}" \
-d "trans_id={TRANS_ID}" \
-d "amount=25.00" \
-d "hash={FORMULA_2_HASH}"// Formula 2: md5(strtoupper(strrev(email).PASSWORD.trans_id.strrev(first6+last4))) $hash = md5(strtoupper(strrev($email).PASSWORD.$transId.strrev($cardPart))); $params = [ 'action' => 'CREDITVOID', 'client_key' => CLIENT_KEY, 'trans_id' => $transId, 'amount' => '25.00', 'hash' => $hash, ];
Same-Day Void (VOID)#
Cancel a transaction performed the same financial day. Only allowed for SETTLED transactions from SALE, CAPTURE, or RECURRING_SALE operations. Unlike CREDITVOID, VOID does not process a refund — it cancels the transaction entirely.
| Parameter | Description | Required |
|---|---|---|
action | VOID | Yes |
client_key | Your account key | Yes |
trans_id | Transaction ID to void | Yes |
hash | Formula 2 | Yes |
Transaction Status (GET_TRANS_STATUS)#
Query the current status of a transaction. Use when a callback hasn't arrived or for reconciliation.
| Parameter | Description | Required |
|---|---|---|
action | GET_TRANS_STATUS | Yes |
client_key | Your account key | Yes |
trans_id | CentaPay transaction ID | Yes |
hash | Formula 2 (or Formula 6 for CREDIT2CARD) | Yes |
Response includes status (one of: 3DS, REDIRECT, PENDING, PREPARE, DECLINED, SETTLED, REVERSAL, REFUND, VOID, CHARGEBACK), plus decline_reason if declined, and recurring_token / schedule_id / digital_wallet if applicable, arn* if configured.
SETTLED is the terminal success status — the transaction has been authorised and captured, and will be included in the next settlement batch to you. For the full settlement lifecycle and status definitions, see Settlement.Transaction Details (GET_TRANS_DETAILS)#
Returns full order history including payer details, card mask, and an array of all transactions in the order.
| Parameter | Description | Required |
|---|---|---|
action | GET_TRANS_DETAILS | Yes |
client_key | Your account key | Yes |
trans_id | CentaPay transaction ID | Yes |
hash | Formula 2 (or Formula 6 for CREDIT2CARD) | Yes |
Response includes: name, mail, ip, amount, currency, card (masked), decline_reason if declined, recurring_token, schedule_id, pan_type, digital_wallet, arn* if configured, and a transactions array with entries containing date, type (sale, 3ds, auth, capture, credit, chargeback, reversal, refund), status, and amount.
Status by Order ID (GET_TRANS_STATUS_BY_ORDER)#
Look up the most recent transaction status using your order_id instead of trans_id. Useful when trans_id is lost or for reconciliation.
| Parameter | Description | Required |
|---|---|---|
action | GET_TRANS_STATUS_BY_ORDER | Yes |
client_key | Your account key | Yes |
order_id | Your order ID | Yes |
hash | Formula 7 (or Formula 6 for CREDIT2CARD) | Yes |
Response: status (3DS/REDIRECT/PENDING/PREPARE/DECLINED/SETTLED/REVERSAL/REFUND/VOID/CHARGEBACK), decline_reason if declined, recurring_token, schedule_id, digital_wallet if applicable. With cascading enabled, returns most recent transaction only.
Chargebacks#
Chargebacks are initiated by the issuing bank, not by API request. CentaPay sends a callback notification when a chargeback occurs.
| Callback Parameter | Description |
|---|---|
action | CHARGEBACK |
result | SUCCESS |
status | CHARGEBACK |
order_id | Your order ID |
trans_id | CentaPay transaction ID |
amount | Chargeback amount |
chargeback_date | System date of the chargeback |
bank_date | Bank date of the chargeback |
reason_code | Chargeback reason code |
connector_name* | Payment gateway name |
rrn* | Retrieval Reference Number |
approval_code* | Issuer authorisation code |
gateway_id* / extra_gateway_id* | Gateway transaction identifiers |
merchant_name* / mid_name* | Merchant and MID names |
issuer_country* / issuer_bank* | Card issuer details |
hash | Formula 2 |
* Extended data fields - included only if configured in admin panel (Configuration → Protocol Mappings → "Add Extended Data to Callback").
Callback Delivery Rules#
CentaPay sends HTTP POST callbacks to your notification URL as transaction states change. Always use callbacks — not the synchronous response — as the authoritative result.
Content Type
Callbacks are sent as application/x-www-form-urlencoded.
Required Response
Your endpoint must return the plain string OK — nothing else. Any other content, HTML, or timeout is treated as a failure.
When Callbacks Are Sent
| Transaction Type | Callback Sent On |
|---|---|
| SALE, CREDITVOID, RECURRING_SALE | SUCCESS, FAIL, WAITING, UNDEFINED |
| CAPTURE, VOID, CREDIT2CARD | SUCCESS, FAIL, UNDEFINED |
| CHARGEBACK | Always (platform-initiated) |
Callback Parameters by Action#
SALE Callback (Success)
| Parameter | Description |
|---|---|
action | SALE |
result | SUCCESS |
status | PENDING / PREPARE / SETTLED |
order_id | Your order ID |
trans_id | CentaPay transaction ID |
trans_date | Timestamp (YYYY-MM-DD hh:mm:ss) |
amount | Transaction amount |
currency | Currency code |
card | Masked PAN (e.g. 411111****1111). For wallets: decrypted token PAN. |
card_expiration_date | Card expiry |
descriptor | Statement descriptor |
hash | Callback signature — verify with Formula 2 |
recurring_token | If recurring_init=Y was sent |
schedule_id | If schedule used |
card_token | If req_token=Y was sent |
digital_wallet | googlepay or applepay (if wallet used) |
pan_type | DPAN or FPAN (wallet transactions) |
exchange_rate | FX rate (if currency exchange applied) |
exchange_rate_base | Base conversion rate (double conversion) |
exchange_currency | Original currency |
exchange_amount | Original amount |
custom_data | Echoed custom data from request |
connector_name*, rrn*, approval_code*, gateway_id*, merchant_name*, issuer_country*, brand*, arn*, extended_data* | See Extended Callback Data |
SALE Callback (Declined)
card, card_expiration_date, descriptor, amount, currency, card_token, recurring_token, or extended data. You must use the card mask and email stored from your original request when verifying the callback hash.| Parameter | Description |
|---|---|
action | SALE |
result | DECLINED |
status | DECLINED |
order_id | Your order ID |
trans_id | CentaPay transaction ID |
trans_date | Timestamp |
decline_reason | Human-readable decline reason |
custom_data | Echoed custom data from request |
digital_wallet | googlepay or applepay (if wallet used) |
pan_type | DPAN or FPAN (wallet transactions) |
hash | Callback signature — verify with Formula 2 |
CAPTURE Callback
| Outcome | Parameters |
|---|---|
| Success | action: CAPTURE, result: SUCCESS, status: SETTLED, order_id, trans_id, amount, trans_date, descriptor, currency, hash (Formula 2). Extended data fields* if configured. |
| Declined | action: CAPTURE, result: DECLINED, status: PENDING, order_id, trans_id, decline_reason, hash |
| Undefined | action: CAPTURE, result: UNDEFINED, status: PENDING, order_id, trans_id, trans_date, descriptor, amount, currency, hash |
CREDITVOID Callback
Success: action, result: SUCCESS, status (REFUND/REVERSAL/SETTLED), order_id, trans_id, creditvoid_date, amount, hash (Formula 2). Extended data fields* if configured.
Declined: action, result: DECLINED, order_id, trans_id, decline_reason, hash (Formula 2).
Undefined: result: UNDEFINED, status: SETTLED, order_id, trans_id, creditvoid_date, amount, hash (Formula 2).
VOID Callback
| Outcome | Parameters |
|---|---|
| Success | action: VOID, result: SUCCESS, status: VOID, order_id, trans_id, trans_date, hash. Extended data fields* if configured. |
| Declined | action: VOID, result: DECLINED, status: SETTLED, order_id, trans_id, trans_date, decline_reason, hash |
| Undefined | action: VOID, result: UNDEFINED, status: PENDING / SETTLED, order_id, trans_id, trans_date, hash |
md5(strtoupper(strrev(trans_id)) . PASSWORD). Verify with Akurateco before go-live.CREDIT2CARD Callback
Uses Formula 6 for hash (not Formula 2). This is the only action with a different callback hash formula.
| Outcome | Parameters |
|---|---|
| Success | action: CREDIT2CARD, result: SUCCESS, status: SETTLED, order_id, trans_id, trans_date, hash (Formula 6). Extended data fields* if configured. |
| Declined | action: CREDIT2CARD, result: DECLINED, status: DECLINED, order_id, trans_id, trans_date, decline_reason, hash (Formula 6) |
| Undefined | action: CREDIT2CARD, result: UNDEFINED, status: PREPARE, order_id, trans_id, trans_date, hash (Formula 6) |
* Extended data fields are included if configured in admin panel (Configuration → Protocol Mappings → "Add Extended Data to Callback").
Hash Verification#
Always verify the callback hash before processing. For most actions, use Formula 2. For CREDIT2CARD, use Formula 6. For VOID, see hash note under VOID Callback above (source references undefined "Void signature" - verify with Akurateco).
email is not included in callback parameters. You must store it from the original SALE request. For SUCCESS callbacks, the card field provides the masked PAN (first 6 + last 4 visible). For DECLINED callbacks, card is not present — use the card mask stored from your original request.$data = $_POST;
// Use card from callback if present (SUCCESS), otherwise use stored mask (DECLINED)
$cardMask = isset($data['card']) ? $data['card'] : $storedCardMask;
$cardPart = substr($cardMask,0,6).substr($cardMask,-4);
$expected = md5(strtoupper(
strrev($storedEmail) . PASSWORD . $data['trans_id'] . strrev($cardPart)
));
if (!hash_equals($expected, $data['hash'])) {
http_response_code(400);
exit('ERROR');
}
// Process based on result
if ($data['result'] === 'SUCCESS') {
fulfillOrder($data['order_id'], $data['trans_id']);
}
echo 'OK';import hashlib, hmac
data = request.form.to_dict()
# Use card from callback if present (SUCCESS), otherwise use stored mask (DECLINED)
card_mask = data.get('card', stored_card_mask)
card_part = card_mask[:6] + card_mask[-4:]
raw = stored_email[::-1] + PASSWORD + data['trans_id'] + card_part[::-1]
expected = hashlib.md5(raw.upper().encode()).hexdigest()
if not hmac.compare_digest(expected, data.get('hash','')):
return 'ERROR', 400
if data['result'] == 'SUCCESS':
fulfill_order(data['order_id'])
return 'OK'const rev = s => s.split('').reverse().join('');
app.post('/webhook', express.urlencoded({extended:false}), (req,res) => {
const d = req.body;
// Use card from callback if present (SUCCESS), otherwise use stored mask (DECLINED)
const cardMask = d.card || storedCardMask;
const cp = cardMask.slice(0,6) + cardMask.slice(-4);
const raw = rev(storedEmail) + PASSWORD + d.trans_id + rev(cp);
const exp = crypto.createHash('md5').update(raw.toUpperCase()).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(exp), Buffer.from(d.hash||'')))
return res.status(400).send('ERROR');
if (d.result === 'SUCCESS') fulfillOrder(d.order_id);
res.send('OK');
});Cascading Behaviour#
When cascading is enabled (auto-retry across MIDs on decline):
- General case: You receive only a callback for the last payment attempt with the final status.
- Particular case: If a redirect is required (e.g. 3DS), you also receive a callback for the first attempt with redirect data. The last-attempt callback includes only the final status.
- Intermediate attempts are not sent. The
trans_idmay differ between first and final callbacks. Do not assume you will receive a first-attempt callback.
Extended Callback Data#
Additional fields if configured in admin panel (Configuration → Protocol Mappings → "Add Extended Data to Callback"): connector_name, rrn, approval_code, gateway_id / extra_gateway_id, merchant_name / mid_name, issuer_country / issuer_bank, brand, arn, extended_data.
API Reference — SALE#
https://{PAYMENT_URL}/post| Parameter | Format | Req | Notes |
|---|---|---|---|
action | SALE | Y | |
client_key | UUID | Y | |
channel_id | ≤16 chars | N | Sub-account routing |
order_id | ≤255 chars | Y | Your unique ID |
order_amount | Number | Y | Integer for KZT/UZS. Float XX.XX for USD. 0 allowed with auth=Y |
order_currency | 3-letter | Y | KZT, UZS, USD |
order_description | ≤1024 chars | Y | |
card_number | PAN | Y* | Optional if card_token or payment_token |
card_exp_month | MM | Y* | |
card_exp_year | YYYY | Y* | |
card_cvv2 | 3-4 digits | Y** | Optional if payment_token |
card_token | 64 chars | N | Replaces card fields |
digital_wallet | googlepay/applepay | N | Pair with payment_token |
payment_token | String | N | From Apple/Google Pay |
payer_first_name | ≤32 chars | Y | |
payer_last_name | ≤32 chars | Y | |
payer_middle_name | ≤32 chars | N | |
payer_birth_date | yyyy-MM-dd | N | |
payer_address | ≤255 chars | Y | |
payer_address2 | ≤255 chars | N | |
payer_house_number | ≤9 chars | N | |
payer_country | 2-letter | Y | ISO 3166-1 |
payer_state | ≤32 chars | N | |
payer_city | ≤40 chars | Y | |
payer_district | ≤32 chars | N | |
payer_zip | ≤10 chars | Y | |
payer_email | ≤256 chars | Y | |
payer_phone | ≤32 chars | Y | |
payer_phone_country_code | String | N | |
payer_ip | IPv4/IPv6 | Y | |
term_url_3ds | URL ≤1024 | Y | 3DS return URL |
term_url_target | ≤1024 | N | _blank/_self/_parent/_top/iframe name |
auth | Y/N | N | Y = AUTH only (DMS) |
req_token | Y/N | N | Request card token |
recurring_init | Y/N | N | Init recurring sequence |
schedule_id | String | N | Link to schedule |
parameters | Object | N | Acquirer-specific extra fields |
custom_data | Object | N | Echoed in callback |
hash | MD5 hex | Y | Formula 1 (cards), Formula 8 (wallets) |
* Optional if card_token or payment_token provided. ** Optional if payment_token provided.
Parameter precedence: If card_token and card data are both sent, card_token is ignored. If req_token and card_token are both sent, req_token is ignored. If payment_token and card data are both sent, payment_token is ignored. If card_token is specified, payment_token is ignored.
Response — Success
result | status | Key Response Fields |
|---|---|---|
| SUCCESS | SETTLED / PENDING / PREPARE | order_id, trans_id, trans_date, descriptor, amount, currency, card_token*, recurring_token*, schedule_id*, digital_wallet, pan_type |
Response — 3DS Redirect
result | status | Key Response Fields |
|---|---|---|
| REDIRECT | 3DS / REDIRECT | order_id, trans_id, trans_date, descriptor, amount, currency, redirect_url, redirect_params, redirect_method, digital_wallet, pan_type |
Response — Declined
result | status | Key Response Fields |
|---|---|---|
| DECLINED | DECLINED | order_id, trans_id, trans_date, descriptor, amount, currency, decline_reason, digital_wallet, pan_type |
Response — Undefined
result | status | Key Response Fields |
|---|---|---|
| UNDEFINED | PENDING / PREPARE | order_id, trans_id, trans_date, descriptor, amount, currency, digital_wallet, pan_type — await callback |
API Reference — CAPTURE#
https://{PAYMENT_URL}/post| Parameter | Format | Req | Notes |
|---|---|---|---|
action | CAPTURE | Y | |
client_key | UUID | Y | |
trans_id | UUID | Y | From SALE (auth) response |
amount | Number | N | Omit for full capture. One partial capture allowed. |
hash | MD5 hex | Y | Formula 2 |
Response
SUCCESS → status: SETTLED. DECLINED → status: PENDING + decline_reason. UNDEFINED → status: PENDING.
All responses include: action, result, status, order_id, trans_id, trans_date, descriptor, amount, currency.
API Reference — CREDITVOID#
https://{PAYMENT_URL}/post| Parameter | Format | Req | Notes |
|---|---|---|---|
action | CREDITVOID | Y | |
client_key | UUID | Y | |
trans_id | UUID | Y | |
amount | Number | N | Omit for full refund. Multiple partials allowed. |
hash | MD5 hex | Y | Formula 2 |
Response
Synchronous: result: ACCEPTED. Callback: SUCCESS with status: REFUND/REVERSAL (full) or SETTLED (partial). DECLINED includes decline_reason.
API Reference — VOID#
https://{PAYMENT_URL}/post| Parameter | Format | Req | Notes |
|---|---|---|---|
action | VOID | Y | |
client_key | UUID | Y | |
trans_id | ≤255 chars | Y | |
hash | MD5 hex | Y | Formula 2 |
Response
result | status | Notes |
|---|---|---|
| SUCCESS | VOID | Transaction voided successfully |
| DECLINED | SETTLED | Void rejected — includes decline_reason. Original transaction remains settled. |
| UNDEFINED | PENDING / SETTLED | Status undetermined — await callback for final result |
Same-day only. Applies to SALE, CAPTURE, RECURRING_SALE in SETTLED status.
md5(strtoupper(strrev(trans_id)) . PASSWORD). Verify with Akurateco before go-live.API Reference — SALE#
https://{PAYMENT_URL}/post| Parameter | Format | Req | Notes |
|---|---|---|---|
action | SALE | Y | |
client_key | UUID | Y | |
channel_id | ≤16 chars | N | Sub-account routing |
order_id | ≤255 chars | Y | Your unique ID |
order_amount | Number | Y | Integer for KZT/UZS. Float XX.XX for USD. 0 allowed with auth=Y |
order_currency | 3-letter | Y | KZT, UZS, USD |
order_description | ≤1024 chars | Y | |
card_number | PAN | Y* | Optional if card_token or payment_token |
card_exp_month | MM | Y* | |
card_exp_year | YYYY | Y* | |
card_cvv2 | 3-4 digits | Y** | Optional if payment_token |
card_token | 64 chars | N | Replaces card fields |
digital_wallet | googlepay/applepay | N | Pair with payment_token |
payment_token | String | N | From Apple/Google Pay |
payer_first_name | ≤32 chars | Y | |
payer_last_name | ≤32 chars | Y | |
payer_middle_name | ≤32 chars | N | |
payer_birth_date | yyyy-MM-dd | N | |
payer_address | ≤255 chars | Y | |
payer_address2 | ≤255 chars | N | |
payer_house_number | ≤9 chars | N | |
payer_country | 2-letter | Y | ISO 3166-1 |
payer_state | ≤32 chars | N | |
payer_city | ≤40 chars | Y | |
payer_district | ≤32 chars | N | |
payer_zip | ≤10 chars | Y | |
payer_email | ≤256 chars | Y | |
payer_phone | ≤32 chars | Y | |
payer_phone_country_code | String | N | |
payer_ip | IPv4/IPv6 | Y | |
term_url_3ds | URL ≤1024 | Y | 3DS return URL |
term_url_target | ≤1024 | N | _blank/_self/_parent/_top/iframe name |
auth | Y/N | N | Y = AUTH only (DMS) |
req_token | Y/N | N | Request card token |
recurring_init | Y/N | N | Init recurring sequence |
schedule_id | String | N | Link to schedule |
parameters | Object | N | Acquirer-specific extra fields |
custom_data | Object | N | Echoed in callback |
hash | MD5 hex | Y | Formula 1 (cards), Formula 8 (wallets) |
* Optional if card_token or payment_token provided. ** Optional if payment_token provided.
Response
result | status | Key Response Fields |
|---|---|---|
| SUCCESS | SETTLED / PENDING / PREPARE | trans_id, trans_date, descriptor, amount, currency, card_token*, recurring_token*, digital_wallet, pan_type |
| REDIRECT | 3DS / REDIRECT | trans_id, redirect_url, redirect_params, redirect_method |
| DECLINED | DECLINED | trans_id, decline_reason |
| UNDEFINED | PENDING / PREPARE | trans_id — await callback |
API Reference — CAPTURE#
https://{PAYMENT_URL}/post| Parameter | Format | Req | Notes |
|---|---|---|---|
action | CAPTURE | Y | |
client_key | UUID | Y | |
trans_id | UUID | Y | From SALE (auth) response |
amount | Number | N | Omit for full capture. One partial capture allowed. |
hash | MD5 hex | Y | Formula 2 |
Response
SUCCESS → status: SETTLED. DECLINED → status: PENDING + decline_reason. UNDEFINED → status: PENDING.
API Reference — CREDITVOID#
https://{PAYMENT_URL}/post| Parameter | Format | Req | Notes |
|---|---|---|---|
action | CREDITVOID | Y | |
client_key | UUID | Y | |
trans_id | UUID | Y | |
amount | Number | N | Omit for full refund. Multiple partials allowed. |
hash | MD5 hex | Y | Formula 2 |
Response
Synchronous: result: ACCEPTED. Callback: SUCCESS with status: REFUND/REVERSAL (full) or SETTLED (partial). DECLINED includes decline_reason.
API Reference — VOID#
https://{PAYMENT_URL}/post| Parameter | Format | Req | Notes |
|---|---|---|---|
action | VOID | Y | |
client_key | UUID | Y | |
trans_id | ≤255 chars | Y | |
hash | MD5 hex | Y | Formula 2 |
Response
result | status | Notes |
|---|---|---|
| SUCCESS | VOID | Transaction voided successfully |
| DECLINED | SETTLED | Void rejected — includes decline_reason. Original transaction remains settled. |
| UNDEFINED | PENDING / SETTLED | Status undetermined — await callback for final result |
Same-day only. Applies to SALE, CAPTURE, SALE_RECURRING in SETTLED status.
API Reference — CREDIT2CARD#
https://{PAYMENT_URL}/post| Parameter | Format | Req | Notes |
|---|---|---|---|
action | CREDIT2CARD | Y | |
client_key | UUID | Y | |
channel_id | ≤16 chars | N | Sub-account |
order_id | ≤255 chars | Y | |
order_amount | Number | Y | |
order_currency | 3-letter | Y | |
order_description | ≤1024 chars | Y | |
card_number | PAN | Y | Recipient card |
payee_first_name | ≤32 chars | N | Recipient name |
payee_last_name | ≤32 chars | N | |
payee_middle_name | ≤32 chars | N | |
payee_birth_date | yyyy-MM-dd | N | |
payee_address | ≤255 chars | N | |
payee_address2 | ≤255 chars | N | |
payee_country | 2-letter | N | |
payee_state | ≤32 chars | N | |
payee_city | ≤32 chars | N | |
payee_zip | ≤10 chars | N | |
payee_email | ≤256 chars | N | |
payee_phone | ≤32 chars | N | |
payer_first_name | ≤32 chars | N | Sender name |
payer_last_name | ≤32 chars | N | |
payer_middle_name | ≤32 chars | N | |
payer_birth_date | yyyy-MM-dd | N | |
payer_address | ≤255 chars | N | |
payer_address2 | ≤255 chars | N | |
payer_country | 2-letter | N | |
payer_state | ≤32 chars | N | |
payer_city | ≤32 chars | N | |
payer_zip | ≤10 chars | N | |
payer_email | ≤256 chars | N | |
payer_phone | ≤32 chars | N | |
payer_ip | IPv4 | N | |
parameters | Object | N | Acquirer-specific |
hash | MD5 hex | Y | Formula 5. Callback uses Formula 6. |
amount or currency on SUCCESS. All responses return: action, result, status, order_id, trans_id, trans_date.result | status | Additional Fields |
|---|---|---|
| SUCCESS | SETTLED | descriptor |
| DECLINED | DECLINED | decline_reason |
| UNDEFINED | PREPARE | descriptor (if available) |
Test card: 4601541833776519.
API Reference — GET_TRANS_STATUS#
https://{PAYMENT_URL}/post| Parameter | Format | Req | Notes |
|---|---|---|---|
action | GET_TRANS_STATUS | Y | |
client_key | UUID | Y | |
trans_id | UUID | Y | |
hash | MD5 hex | Y | Formula 2 (Formula 6 for CREDIT2CARD) |
Response: status (3DS/REDIRECT/PENDING/PREPARE/DECLINED/SETTLED/REVERSAL/REFUND/VOID/CHARGEBACK), decline_reason, recurring_token, schedule_id, digital_wallet, arn* if configured.
API Reference — GET_TRANS_DETAILS#
https://{PAYMENT_URL}/post| Parameter | Format | Req | Notes |
|---|---|---|---|
action | GET_TRANS_DETAILS | Y | |
client_key | UUID | Y | |
trans_id | UUID | Y | |
hash | MD5 hex | Y | Formula 2 (Formula 6 for CREDIT2CARD) |
Response includes: name, mail, ip, amount, currency, card (masked), decline_reason if declined, recurring_token, schedule_id, pan_type, digital_wallet, arn* if configured, transactions[] array (each with date, type, status, amount).
API Reference — GET_TRANS_STATUS_BY_ORDER#
https://{PAYMENT_URL}/post| Parameter | Format | Req | Notes |
|---|---|---|---|
action | GET_TRANS_STATUS_BY_ORDER | Y | |
client_key | UUID | Y | |
order_id | ≤255 chars | Y | Your order ID |
hash | MD5 hex | Y | Formula 7 (Formula 6 for CREDIT2CARD) |
Response: status (3DS/REDIRECT/PENDING/PREPARE/DECLINED/SETTLED/REVERSAL/REFUND/VOID/CHARGEBACK), decline_reason if declined, recurring_token, schedule_id, digital_wallet if applicable. With cascading enabled, returns most recent transaction only.
API Reference — CHARGEBACK#
Callback-only — not merchant-initiated. See Chargebacks for the full callback schema.
API Reference — RECURRING_SALE#
https://{PAYMENT_URL}/post| Parameter | Format | Req | Notes |
|---|---|---|---|
action | RECURRING_SALE | Y | |
client_key | UUID | Y | |
order_id | ≤255 chars | Y | New unique order ID |
order_amount | Number | Y | |
order_description | ≤1024 chars | Y | |
recurring_first_trans_id | UUID | Y | trans_id of initial transaction |
recurring_token | UUID | Y | Token from initial transaction |
schedule_id | String | N | Link to a schedule |
auth | Y/N | N | AUTH only (DMS) |
custom_data | Object | N | Overrides initial SALE custom_data |
hash | MD5 hex | Y | Formula 1 |
Response identical to SALE but action=RECURRING_SALE. Bypasses 3DS.
API Reference — RETRY#
https://{PAYMENT_URL}/post| Parameter | Format | Req | Notes |
|---|---|---|---|
action | RETRY | Y | |
client_key | UUID | Y | |
trans_id | UUID | Y | Declined recurring trans_id |
hash | MD5 hex | Y | Formula 1 |
Sync: result: ACCEPTED, order_id, trans_id. Only for soft declines.
Callback Parameters
Success: action: RETRY, result: SUCCESS, status: SETTLED, order_id, trans_id, amount, currency, hash (Formula 2).
Declined: action: RETRY, result: DECLINED, status: DECLINED, order_id, trans_id, amount, currency, decline_reason, hash (Formula 2).
API Reference — Schedule Operations#
All schedule actions use POST https://{PAYMENT_URL}/post.
CREATE_SCHEDULE
| Parameter | Format | Req | Notes |
|---|---|---|---|
action | CREATE_SCHEDULE | Y | |
client_key | UUID | Y | |
name | ≤100 chars | Y | Schedule name |
interval_length | Number >0 | Y | e.g. 15 for every 15 days |
interval_unit | day/month | Y | |
day_of_month | 1-31 | N | Only if interval_unit=month. 29/30/31 → last day if month shorter. |
payments_count | Number | Y | Total payments in schedule |
delays | Number | N | Intervals to skip before starting |
hash | MD5 hex | Y | Formula 3 |
Response: schedule_id.
PAUSE_SCHEDULE
| Parameter | Req | Notes |
|---|---|---|
action = PAUSE_SCHEDULE | Y | |
client_key | Y | |
schedule_id | Y | |
hash | Y | Formula 4 |
RUN_SCHEDULE
Same parameters as PAUSE_SCHEDULE with action=RUN_SCHEDULE. Resumes paused schedule.
DELETE_SCHEDULE
Same parameters with action=DELETE_SCHEDULE. Permanently removes schedule.
SCHEDULE_INFO
Same parameters with action=SCHEDULE_INFO. Returns: name, interval_length, interval_unit, day_of_month, payments_count, delays, paused (Y/N).
DESCHEDULE
| Parameter | Req | Notes |
|---|---|---|
action = DESCHEDULE | Y | |
client_key | Y | |
recurring_token | Y | From initial transaction |
schedule_id | Y | |
hash | Y | Formula 4 |
Test Cards#
Use these test values in the sandbox environment. All transactions are processed by the test engine — no real funds are moved. Use any 3-digit CVV.
S2S CARD — Scenario Simulation
All scenarios use card number 4111111111111111. The expiry date determines the outcome:
| Expiry | Scenario | Response |
|---|---|---|
01/2038 |
Successful SALE (also use for recurring init — only card that returns recurring_token) |
result: SUCCESS, status: SETTLEDAUTH: status: PENDING |
02/2038 |
Declined SALE / AUTH | result: DECLINED, status: DECLINED |
03/2038 |
Successful AUTH, then declined CAPTURE | AUTH: SUCCESS/PENDINGCAPTURE: DECLINED/PENDING |
05/2038 |
3DS verification → Success | SALE: REDIRECT/3DS → After ACS: SUCCESS/SETTLED |
06/2038 |
3DS verification → Decline | SALE: REDIRECT/3DS → After ACS: DECLINED |
12/2038 |
Redirect → Success | SALE: REDIRECT/REDIRECT → Return: SUCCESS/SETTLED |
12/2039 |
Redirect → Decline | SALE: REDIRECT/REDIRECT → Return: DECLINED |
CREDIT2CARD Test Card
| Card Number | Scenario | Response |
|---|---|---|
4601541833776519 |
Successful card payout | result: SUCCESS, status: SETTLED |
4111111111111111 expiry 01/2038. Other test cards will process the SALE but will not return a recurring_token.Error & Decline Codes#
When a request fails validation, the synchronous response contains:
{
"result": "ERROR",
"error_message": "Description of the error",
"error_code": 204002
}Error Code Reference
| Code | Description | Category |
|---|---|---|
204002 | Enabled merchant mappings or MIDs not found | Configuration |
204003 | Payment type not supported | Configuration |
204004 | Payment method not supported | Configuration |
204005 | Payment action not supported | Configuration |
204006 | Payment system/brand not supported | Configuration |
204007 | Day MID limit is not set or exceeded | Limits |
204008 | Day merchant mapping limit is not set or exceeded | Limits |
204009 | Payment type not found | Configuration |
204010 | Payment method not found | Configuration |
204011 | Payment system/brand not found | Configuration |
204012 | Payment currency not found | Configuration |
204013 | Payment action not found | Configuration |
204014 | Month MID limit exceeded | Limits |
204015 | Week merchant mapping limit exceeded | Limits |
208001 | Payment not found | Transaction |
208002 | Cannot request 3DS for payment not in 3DS status | Transaction |
208003 | Cannot capture payment not in PENDING status | Transaction |
208004 | Capture amount exceeds auth amount | Transaction |
208005 | Cannot refund payment not in SETTLED or PENDING status | Transaction |
208006 | Refund amount exceeds payment amount | Transaction |
208008 | Reversal amount exceeds payment amount | Transaction |
208009 | Partial reversal not allowed | Transaction |
208010 | Chargeback amount exceeds payment amount | Transaction |
205005 | Card token is invalid or not found | Token |
205006 | Card token is expired | Token |
205007 | Card token is not accessible | Token |
400 | Duplicate request | Validation |
100000 | Previous payment not completed | Validation |
decline_reason field for DECLINED transactions. These are human-readable strings from the issuer or risk engine — not codes. Common examples: "Insufficient funds", "Card expired", "Do not honor".Hash Calculator#
Generate test hashes during development. The PASSWORD is processed client-side only — do not use in production.
Formula 1 — SALE (card)
Formula 2 — CAPTURE / CREDITVOID / VOID / Callback
Formula 5 — CREDIT2CARD
Formula 8 — Digital Wallets (Apple Pay / Google Pay)
Request Builder#
Build a complete SALE request with auto-generated hash and cURL command. Enter your sandbox credentials and card details below.
Go-Live Checklist#
Complete these items before requesting production credentials.
Technical
| Item | Detail |
|---|---|
| ✅ SALE tested — success | Card 4111111111111111 exp 01/2038 returns SUCCESS/SETTLED |
| ✅ SALE tested — decline | Exp 02/2038 returns DECLINED |
| ✅ 3DS redirect handled | Exp 05/2038 → redirect → return → SUCCESS. Exp 06/2038 → DECLINED |
| ✅ Callback handler implemented | Endpoint returns plain OK. Hash verified using Formula 2. Tested with all result types. |
| ✅ CREDITVOID tested | Full and partial refunds work correctly |
| ✅ VOID tested (if used) | Same-day cancellation on SETTLED transaction |
| ✅ GET_TRANS_STATUS tested | Status polling works as fallback when callback delayed |
| ✅ Error handling implemented | All error codes handled gracefully. User sees meaningful messages. |
| ✅ Idempotent order IDs | Each payment attempt uses a unique order_id. Duplicates handled. |
| ✅ IP addresses provided | Production server IPs sent to CentaPay for whitelisting |
| ✅ Callback URL configured | Production callback URL registered with CentaPay |
| ✅ HTTPS everywhere | All endpoints use TLS. No plain HTTP. |
If Using Recurring
| Item | Detail |
|---|---|
| ✅ RECURRING_SALE tested | Initial SALE with recurring_init=Y, then RECURRING_SALE with token |
| ✅ RETRY tested | Soft decline → RETRY → final result via callback |
| ✅ Schedule ops tested (if used) | CREATE, PAUSE, RUN, DELETE, SCHEDULE_INFO, DESCHEDULE |
If Using Payouts
| Item | Detail |
|---|---|
| ✅ CREDIT2CARD tested | Test card 4601541833776519 returns SUCCESS |
| ✅ Formula 5 hash verified | Request hash uses Formula 5 (not Formula 1) |
| ✅ Formula 6 callback hash | Callback verification uses Formula 6 (not Formula 2) |
If Using Digital Wallets
| Item | Detail |
|---|---|
| ✅ Apple Pay: Merchant ID configured | Certificates and keys uploaded to admin panel |
| ✅ Apple Pay: Domains verified | All payment domains registered in Apple Developer |
| ✅ Google Pay: Integration checklist | Completed per Google's requirements |
| ✅ Google Pay: Domains verified | Verified in Google Business Console |
| ✅ Formula 8 hash used | Wallet SALE uses email + PASSWORD only |
Compliance
| Item | Detail |
|---|---|
| ✅ PCI DSS evidence | SAQ D or full assessment (for raw card S2S). Reduced scope for wallet-only. |
| ✅ KYB complete | Business verification documents submitted and approved |
| ✅ Prohibited MCCs reviewed | Business model confirmed against prohibited categories |
| ✅ Client agreement signed | Executed with CentaPay |
Commercial
| Item | Detail |
|---|---|
| ✅ Production credentials received | Separate CLIENT_KEY, PASSWORD, PAYMENT_URL for production |
| ✅ Settlement currency confirmed | USD or preferred currency agreed with CentaPay |
| ✅ MID configured | Production MID mapped to your account by CentaPay ops |
Contact info@centapay.com when all items are complete to begin the go-live process.
Eligibility#
CentaPay is an AFSA-regulated Money Services Provider operating within a strict compliance framework.
- All clients are international businesses or licensed payment service providers
- CentaPay does not provide services to retail consumers
- All payment flows are cross-border
- Domestic merchant acquiring within Kazakhstan or Uzbekistan is outside scope
Prohibited Categories#
Prohibited categories include: unlicensed financial services, gambling without appropriate licensing, adult content, weapons and controlled substances, and services subject to international sanctions.
Permitted Verticals
| Vertical | Examples | Notes |
|---|---|---|
| Retail e-commerce | Fashion, electronics, consumer goods | Standard checkout flows |
| Online gaming — low risk | In-app purchases, game credits | Low-risk gaming only. Gambling and lottery excluded. |
| Social media content | Subscriptions, content creator platforms | Excluding adult/18+ content |
PCI DSS#
| Integration Type | PCI Requirement |
|---|---|
| S2S with raw card data | PCI DSS SAQ D or full on-site assessment |
| S2S with Apple Pay / Google Pay tokens | Reduced scope — confirm with your QSA |
S2S with CentaPay card token (card_token) | Reduced scope for subsequent charges (initial charge still requires raw card data) |
AML Obligations#
As a CentaPay client, you retain responsibility for:
- KYC on your end-users — Know Your Customer checks on the cardholders and recipients in your system
- Sanctions screening — Screen counterparties against applicable sanctions lists before submitting payment requests
- Suspicious transaction reporting — Report unusual patterns to CentaPay and your own regulatory authority where applicable
- Data accuracy — Submitting false payer data (names, addresses, IPs) constitutes a breach of your client agreement
