v1.0

CentaPay Developer Documentation

Everything you need to integrate cross-border payment acceptance and disbursements for Central Asia.

AFSA Regulated S2S · Server-to-Server Professional Clients Only PCI DSS Required

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.

⚠️
PCI DSS required. Because card data is handled server-to-server, you must be PCI DSS compliant or handle only tokenised card data. Contact info@centapay.com to discuss your compliance posture before going live.

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.

ℹ️
Before integrating, review the Compliance & Restrictions section. CentaPay enforces strict prohibited merchant category controls.

Credentials & Environments#

Before you get an account, you must provide the following data to CentaPay:

DataDescription
IP listIP addresses from which your server will send requests. Requests from un-whitelisted IPs are rejected silently.
Callback URLURL that will receive transaction result notifications (webhooks). Maximum 255 characters. Mandatory if your account supports 3D Secure.
Contact emailEmail of the person who will monitor transactions, conduct refunds, and handle operational queries.

You will receive the following credentials:

CredentialDescriptionWhere 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
🔒
Store PASSWORD securely. Use an environment variable or secrets manager. Never hardcode it in source files or commit it to version control.
ℹ️
Credential rotation. There is no self-service PASSWORD rotation. To rotate your PASSWORD — following a personnel change, suspected compromise, or as a periodic security measure — contact info@centapay.com. CentaPay will issue a new PASSWORD and coordinate the switchover window with you. We recommend rotating credentials at least annually and immediately after any suspected exposure.

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.

ProtocolUsed ForEndpoint
S2S CARDCard payments (Visa, MC, UZCARD, HUMO), Apple Pay, Google Pay, CREDIT2CARD payoutshttps://{PAYMENT_URL}/post
ℹ️
All requests must use 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.

🔒
The 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#

ActionFormulaKey Inputs
SALE (card)Formula 1email + PASSWORD + card first6/last4
SALE (card_token)Formula 1 variantemail + PASSWORD + card_token
SALE (digital wallet)Formula 8email + PASSWORD only
CAPTUREFormula 2email + PASSWORD + trans_id + card
CREDITVOIDFormula 2email + PASSWORD + trans_id + card
VOIDFormula 2email + PASSWORD + trans_id + card
RECURRING_SALEFormula 1email + PASSWORD + card first6/last4
RETRYFormula 1email + PASSWORD + card first6/last4
CREDIT2CARDFormula 5PASSWORD + card first6/last4
CREDIT2CARD callbackFormula 6PASSWORD + trans_id + card
GET_TRANS_STATUSFormula 2 (or 6 for CREDIT2CARD)See formula
GET_TRANS_DETAILSFormula 2 (or 6 for CREDIT2CARD)See formula
GET_TRANS_STATUS_BY_ORDERFormula 7 (or 6 for CREDIT2CARD)email + PASSWORD + order_id + card
CREATE_SCHEDULEFormula 3PASSWORD only
Other schedule opsFormula 4schedule_id + PASSWORD
Callback (all except CREDIT2CARD, VOID)Formula 2email + PASSWORD + trans_id + card
Callback (CREDIT2CARD)Formula 6PASSWORD + trans_id + card
Callback (VOID)TBCSource references undefined "Void signature" - verify with Akurateco
ℹ️
The interactive Hash Calculator in the Testing section lets you generate test hashes during development.

Quickstart#

Process your first card payment through CentaPay in under 30 minutes. This guide assumes you have received sandbox credentials from your account manager.

S2S Only PCI DSS Required Content-Type: application/x-www-form-urlencoded

What You Need#

CredentialDescription
CLIENT_KEYYour unique account identifier. Sent as a POST parameter on every request.
PASSWORDUsed only to generate the hash signature. Never sent directly over the wire.
PAYMENT_URLThe 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.

POST 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' } }
);
ℹ️
The test card 4111111111111111 with expiry 01/2038 returns a successful SALE. See Test Cards for all simulation scenarios.

Handling the Response#

resultMeaningYour 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.
⚠️
Never use the synchronous response alone to fulfil an order. The final authoritative result is always the callback to your notification URL. Always wait for the callback before triggering fulfilment.

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.

KZ · UZ PCI DSS Required 3DS Supported

SALE — Single Message (SMS)#

Authorise and capture in one step. This is the standard flow for immediate payments.

POST 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#

CountryCurrencyCodeAmount Format
🇰🇿 KazakhstanKazakhstani TengeKZTInteger — e.g. 5000
🇺🇿 UzbekistanUzbekistani SomUZSInteger — e.g. 120000
InternationalUS DollarUSDFloat 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.

ℹ️
A successful AUTH returns 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.

⚠️
PCI scope: Even with tokenisation, your initial SALE request transmits raw card data server-to-server. Subsequent token-based charges reduce scope.

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.

S2S Token Flow Reduced PCI Scope

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.

ℹ️
Contact info@centapay.com to initiate Apple Pay setup. See Apple's demo at applepaydemo.apple.com.

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: CARD
  • tokenizationSpecification: { "type": "PAYMENT_GATEWAY" }
  • allowedCardNetworks: ['MASTERCARD', 'VISA', 'AMEX', 'DISCOVER', 'JCB']
  • allowedCardAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS']
  • gateway = value from your CentaPay account manager
  • gatewayMerchantId = your CLIENT_KEY

SALE Request — Google Pay

Identical to Apple Pay but with digital_wallet=googlepay. Use Formula 8 for the hash. Omit card fields.

⚠️
For 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.

ℹ️
Only the test card 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.

POST https://{PAYMENT_URL}/post
ParameterDescriptionRequired
actionRECURRING_SALEYes
client_keyYour account key (UUID)Yes
order_idNew unique order ID for this chargeYes
order_amountAmount to chargeYes
order_currencyCurrency codeYes
order_descriptionDescription for this chargeYes
recurring_first_trans_idTransaction ID of the primary transactionYes
recurring_tokenToken from original transactionYes
schedule_idLink to a schedule objectNo
authY for auth-onlyNo
hashFormula 1Yes

Response is identical to SALE (with action=RECURRING_SALE).

⚠️
Hash requires original card data. Formula 1 uses the first 6 and last 4 digits of the card number. Since RECURRING_SALE does not send card fields, you must store the card first 6 + last 4 digits (or the 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.
⚠️
Recurring transactions bypass 3DS. Ensure your agreement with the cardholder authorises recurring charges, and your acquiring setup supports MIT (Merchant Initiated Transactions).

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.

ParameterDescriptionRequired
actionRETRYYes
client_keyYour account keyYes
trans_idTransaction ID of the declined recurring transactionYes
hashFormula 1Yes

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.

ActionPurposeHash
CREATE_SCHEDULECreate a new schedule with name, interval, payment countFormula 3
PAUSE_SCHEDULESuspend scheduled paymentsFormula 4
RUN_SCHEDULEResume a paused scheduleFormula 4
DELETE_SCHEDULEPermanently delete a scheduleFormula 4
SCHEDULE_INFOGet schedule detailsFormula 4
DESCHEDULEStop payments by schedule (requires recurring_token + schedule_id)Formula 4

CREATE_SCHEDULE Parameters

ParameterDescriptionRequired
actionCREATE_SCHEDULEYes
client_keyYour account keyYes
nameSchedule name (up to 100 chars)Yes
interval_lengthHow often payments occur (cannot be 0)Yes
interval_unitday or monthYes
day_of_month1–31; only if interval_unit=month. If 29/30/31 and month is shorter, last day is usedNo
payments_countTotal number of paymentsYes
delaysNumber of skipped intervals before cycle startsNo
hashFormula 3Yes

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:

FieldDescription
redirect_urlURL to redirect the cardholder to
redirect_paramsObject of 3DS parameters (key-value pairs). May be an empty array or absent entirely.
redirect_methodPOST 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.

S2S CARD Formula 5 Hash
POST https://{PAYMENT_URL}/post

Request Parameters#

ParameterDescriptionRequired
actionCREDIT2CARDYes
client_keyYour account keyYes
channel_idSub-account (up to 16 chars)No
order_idYour unique transaction IDYes
order_amountAmount to disburse. Integer for KZT/UZS, float XX.XX for USD.Yes
order_currencyISO 4217 codeYes
order_descriptionDescription (up to 1024 chars)Yes
card_numberRecipient's card numberYes
payee_first_nameRecipient's first nameNo
payee_last_nameRecipient's last nameNo
payee_middle_nameRecipient's middle nameNo
payee_birth_dateFormat: yyyy-MM-ddNo
payee_addressRecipient's addressNo
payee_country2-letter codeNo
payee_cityRecipient's cityNo
payee_zipPostal codeNo
payee_emailRecipient's emailNo
payee_phoneRecipient's phoneNo
payer_first_nameSender's first nameNo
payer_last_nameSender's last nameNo
parametersAcquirer-specific extra fieldsNo
hashFormula 5Yes

Response#

resultstatusMeaning
SUCCESSSETTLEDPayout completed
DECLINEDDECLINEDPayout rejected — check decline_reason
UNDEFINEDPREPAREProcessing — 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#

🔜
Coming soon. CREDIT2VIRTUAL enables payouts to mobile money, bank transfer, and other virtual account methods. This capability is not yet available on CentaPay. Contact info@centapay.com for the latest availability dates.

Direct Merchants#

International businesses integrating directly with CentaPay. Each direct merchant receives a dedicated MID, full transaction reporting, and settlement in their preferred currency.

Professional Clients Only Cross-Border

Onboarding#

StepDetail
1. Contact CentaPayEmail info@centapay.com or visit centapay.com/contact
2. Client agreementProfessional Client classification under AFSA conduct of business rules
3. KYB & PCI reviewSubmit compliance documentation for approval
4. Sandbox credentialsReceive CLIENT_KEY, PASSWORD, and PAYMENT_URL
5. Build & testIntegrate using this documentation and the sandbox environment
6. Go liveReceive 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.

ParameterValue
CycleT+1 (next business day)
Settlement currencyAgreed at onboarding (typically USD)

Transaction Status Lifecycle

How a transaction moves through the system toward settlement:

StatusMeaning
PENDINGAUTH hold placed — funds reserved on cardholder's account but not yet captured
PREPAREProcessing in progress — final status not yet determined
3DSAwaiting cardholder 3DS authentication
REDIRECTAwaiting redirect completion
SETTLEDTransaction completed successfully. Funds will be included in the next settlement batch to you.
DECLINEDTransaction rejected by issuer or risk engine
VOIDSame-day cancellation — no funds movement
REFUNDFull refund processed — amount deducted from your settlement balance
REVERSALAUTH hold released (no capture occurred)
CHARGEBACKIssuer-initiated dispute — amount debited from your settlement balance
ℹ️
After partial refund: The original transaction retains 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 FieldDescription
exchange_rateFX rate applied to the transaction
exchange_rate_baseBase conversion rate (if double conversion applies)
exchange_currencyOriginal transaction currency
exchange_amountOriginal 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.

ℹ️
Best practice: Do not poll 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.

Professional Clients Only PayFac / MOR

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.

ParameterValue
CycleT+1 (next business day)
Settlement currencyAgreed at onboarding (typically USD)
ScopeAll 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.

ℹ️
Tip: Enable Extended Data in your admin panel (Configuration → Protocol Mappings → "Add Extended Data to Callback") to receive 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.

POST https://{PAYMENT_URL}/post
ParameterDescriptionRequired
actionCREDITVOIDYes
client_keyYour account keyYes
trans_idOriginal CentaPay transaction IDYes
amountPartial refund amount. Omit for full refund.No
hashFormula 2Yes

Synchronous response returns result: ACCEPTED. Final result delivered via callback:

Callback statusMeaning
REFUND or REVERSALFull refund / full reversal completed
SETTLEDPartial 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.

ParameterDescriptionRequired
actionVOIDYes
client_keyYour account keyYes
trans_idTransaction ID to voidYes
hashFormula 2Yes
ℹ️
VOID vs CREDITVOID: Use VOID for same-day cancellations (no funds movement). Use CREDITVOID for refunds after settlement day or to reverse an AUTH hold.

Transaction Status (GET_TRANS_STATUS)#

Query the current status of a transaction. Use when a callback hasn't arrived or for reconciliation.

ParameterDescriptionRequired
actionGET_TRANS_STATUSYes
client_keyYour account keyYes
trans_idCentaPay transaction IDYes
hashFormula 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.

ParameterDescriptionRequired
actionGET_TRANS_DETAILSYes
client_keyYour account keyYes
trans_idCentaPay transaction IDYes
hashFormula 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.

ParameterDescriptionRequired
actionGET_TRANS_STATUS_BY_ORDERYes
client_keyYour account keyYes
order_idYour order IDYes
hashFormula 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 ParameterDescription
actionCHARGEBACK
resultSUCCESS
statusCHARGEBACK
order_idYour order ID
trans_idCentaPay transaction ID
amountChargeback amount
chargeback_dateSystem date of the chargeback
bank_dateBank date of the chargeback
reason_codeChargeback 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
hashFormula 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.

⚠️
Blocking: 5 consecutive timeouts within 5 minutes will block your callback URL for 15 minutes. All merchants sharing that URL are affected. The counter resets on any successful response. You can manually unblock via the admin panel (Configuration → Merchants → Edit Merchant).

When Callbacks Are Sent

Transaction TypeCallback Sent On
SALE, CREDITVOID, RECURRING_SALESUCCESS, FAIL, WAITING, UNDEFINED
CAPTURE, VOID, CREDIT2CARDSUCCESS, FAIL, UNDEFINED
CHARGEBACKAlways (platform-initiated)

Callback Parameters by Action#

SALE Callback (Success)

ParameterDescription
actionSALE
resultSUCCESS
statusPENDING / PREPARE / SETTLED
order_idYour order ID
trans_idCentaPay transaction ID
trans_dateTimestamp (YYYY-MM-DD hh:mm:ss)
amountTransaction amount
currencyCurrency code
cardMasked PAN (e.g. 411111****1111). For wallets: decrypted token PAN.
card_expiration_dateCard expiry
descriptorStatement descriptor
hashCallback signature — verify with Formula 2
recurring_tokenIf recurring_init=Y was sent
schedule_idIf schedule used
card_tokenIf req_token=Y was sent
digital_walletgooglepay or applepay (if wallet used)
pan_typeDPAN or FPAN (wallet transactions)
exchange_rateFX rate (if currency exchange applied)
exchange_rate_baseBase conversion rate (double conversion)
exchange_currencyOriginal currency
exchange_amountOriginal amount
custom_dataEchoed 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)

⚠️
Reduced field set. Declined callbacks do not include 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.
ParameterDescription
actionSALE
resultDECLINED
statusDECLINED
order_idYour order ID
trans_idCentaPay transaction ID
trans_dateTimestamp
decline_reasonHuman-readable decline reason
custom_dataEchoed custom data from request
digital_walletgooglepay or applepay (if wallet used)
pan_typeDPAN or FPAN (wallet transactions)
hashCallback signature — verify with Formula 2

CAPTURE Callback

OutcomeParameters
Successaction: CAPTURE, result: SUCCESS, status: SETTLED, order_id, trans_id, amount, trans_date, descriptor, currency, hash (Formula 2). Extended data fields* if configured.
Declinedaction: CAPTURE, result: DECLINED, status: PENDING, order_id, trans_id, decline_reason, hash
Undefinedaction: 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

OutcomeParameters
Successaction: VOID, result: SUCCESS, status: VOID, order_id, trans_id, trans_date, hash. Extended data fields* if configured.
Declinedaction: VOID, result: DECLINED, status: SETTLED, order_id, trans_id, trans_date, decline_reason, hash
Undefinedaction: VOID, result: UNDEFINED, status: PENDING / SETTLED, order_id, trans_id, trans_date, hash
⚠️
VOID hash note: The source documentation references a "Void signature" for VOID callbacks that is not the standard Formula 2. It may use: 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.

OutcomeParameters
Successaction: CREDIT2CARD, result: SUCCESS, status: SETTLED, order_id, trans_id, trans_date, hash (Formula 6). Extended data fields* if configured.
Declinedaction: CREDIT2CARD, result: DECLINED, status: DECLINED, order_id, trans_id, trans_date, decline_reason, hash (Formula 6)
Undefinedaction: 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).

⚠️
Formula 2 requires data not in the callback. The payer's 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):

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#

POSThttps://{PAYMENT_URL}/post
ParameterFormatReqNotes
actionSALEY
client_keyUUIDY
channel_id≤16 charsNSub-account routing
order_id≤255 charsYYour unique ID
order_amountNumberYInteger for KZT/UZS. Float XX.XX for USD. 0 allowed with auth=Y
order_currency3-letterYKZT, UZS, USD
order_description≤1024 charsY
card_numberPANY*Optional if card_token or payment_token
card_exp_monthMMY*
card_exp_yearYYYYY*
card_cvv23-4 digitsY**Optional if payment_token
card_token64 charsNReplaces card fields
digital_walletgooglepay/applepayNPair with payment_token
payment_tokenStringNFrom Apple/Google Pay
payer_first_name≤32 charsY
payer_last_name≤32 charsY
payer_middle_name≤32 charsN
payer_birth_dateyyyy-MM-ddN
payer_address≤255 charsY
payer_address2≤255 charsN
payer_house_number≤9 charsN
payer_country2-letterYISO 3166-1
payer_state≤32 charsN
payer_city≤40 charsY
payer_district≤32 charsN
payer_zip≤10 charsY
payer_email≤256 charsY
payer_phone≤32 charsY
payer_phone_country_codeStringN
payer_ipIPv4/IPv6Y
term_url_3dsURL ≤1024Y3DS return URL
term_url_target≤1024N_blank/_self/_parent/_top/iframe name
authY/NNY = AUTH only (DMS)
req_tokenY/NNRequest card token
recurring_initY/NNInit recurring sequence
schedule_idStringNLink to schedule
parametersObjectNAcquirer-specific extra fields
custom_dataObjectNEchoed in callback
hashMD5 hexYFormula 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

resultstatusKey Response Fields
SUCCESSSETTLED / PENDING / PREPAREorder_id, trans_id, trans_date, descriptor, amount, currency, card_token*, recurring_token*, schedule_id*, digital_wallet, pan_type

Response — 3DS Redirect

resultstatusKey Response Fields
REDIRECT3DS / REDIRECTorder_id, trans_id, trans_date, descriptor, amount, currency, redirect_url, redirect_params, redirect_method, digital_wallet, pan_type

Response — Declined

resultstatusKey Response Fields
DECLINEDDECLINEDorder_id, trans_id, trans_date, descriptor, amount, currency, decline_reason, digital_wallet, pan_type

Response — Undefined

resultstatusKey Response Fields
UNDEFINEDPENDING / PREPAREorder_id, trans_id, trans_date, descriptor, amount, currency, digital_wallet, pan_type — await callback

API Reference — CAPTURE#

POSThttps://{PAYMENT_URL}/post
ParameterFormatReqNotes
actionCAPTUREY
client_keyUUIDY
trans_idUUIDYFrom SALE (auth) response
amountNumberNOmit for full capture. One partial capture allowed.
hashMD5 hexYFormula 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#

POSThttps://{PAYMENT_URL}/post
ParameterFormatReqNotes
actionCREDITVOIDY
client_keyUUIDY
trans_idUUIDY
amountNumberNOmit for full refund. Multiple partials allowed.
hashMD5 hexYFormula 2

Response

Synchronous: result: ACCEPTED. Callback: SUCCESS with status: REFUND/REVERSAL (full) or SETTLED (partial). DECLINED includes decline_reason.

API Reference — VOID#

POSThttps://{PAYMENT_URL}/post
ParameterFormatReqNotes
actionVOIDY
client_keyUUIDY
trans_id≤255 charsY
hashMD5 hexYFormula 2

Response

resultstatusNotes
SUCCESSVOIDTransaction voided successfully
DECLINEDSETTLEDVoid rejected — includes decline_reason. Original transaction remains settled.
UNDEFINEDPENDING / SETTLEDStatus undetermined — await callback for final result

Same-day only. Applies to SALE, CAPTURE, RECURRING_SALE in SETTLED status.

⚠️
Hash note: The source documentation references a "Void signature" for VOID callbacks that may differ from Formula 2. It may use: md5(strtoupper(strrev(trans_id)) . PASSWORD). Verify with Akurateco before go-live.

API Reference — SALE#

POSThttps://{PAYMENT_URL}/post
ParameterFormatReqNotes
actionSALEY
client_keyUUIDY
channel_id≤16 charsNSub-account routing
order_id≤255 charsYYour unique ID
order_amountNumberYInteger for KZT/UZS. Float XX.XX for USD. 0 allowed with auth=Y
order_currency3-letterYKZT, UZS, USD
order_description≤1024 charsY
card_numberPANY*Optional if card_token or payment_token
card_exp_monthMMY*
card_exp_yearYYYYY*
card_cvv23-4 digitsY**Optional if payment_token
card_token64 charsNReplaces card fields
digital_walletgooglepay/applepayNPair with payment_token
payment_tokenStringNFrom Apple/Google Pay
payer_first_name≤32 charsY
payer_last_name≤32 charsY
payer_middle_name≤32 charsN
payer_birth_dateyyyy-MM-ddN
payer_address≤255 charsY
payer_address2≤255 charsN
payer_house_number≤9 charsN
payer_country2-letterYISO 3166-1
payer_state≤32 charsN
payer_city≤40 charsY
payer_district≤32 charsN
payer_zip≤10 charsY
payer_email≤256 charsY
payer_phone≤32 charsY
payer_phone_country_codeStringN
payer_ipIPv4/IPv6Y
term_url_3dsURL ≤1024Y3DS return URL
term_url_target≤1024N_blank/_self/_parent/_top/iframe name
authY/NNY = AUTH only (DMS)
req_tokenY/NNRequest card token
recurring_initY/NNInit recurring sequence
schedule_idStringNLink to schedule
parametersObjectNAcquirer-specific extra fields
custom_dataObjectNEchoed in callback
hashMD5 hexYFormula 1 (cards), Formula 8 (wallets)

* Optional if card_token or payment_token provided. ** Optional if payment_token provided.

Response

resultstatusKey Response Fields
SUCCESSSETTLED / PENDING / PREPAREtrans_id, trans_date, descriptor, amount, currency, card_token*, recurring_token*, digital_wallet, pan_type
REDIRECT3DS / REDIRECTtrans_id, redirect_url, redirect_params, redirect_method
DECLINEDDECLINEDtrans_id, decline_reason
UNDEFINEDPENDING / PREPAREtrans_id — await callback

API Reference — CAPTURE#

POSThttps://{PAYMENT_URL}/post
ParameterFormatReqNotes
actionCAPTUREY
client_keyUUIDY
trans_idUUIDYFrom SALE (auth) response
amountNumberNOmit for full capture. One partial capture allowed.
hashMD5 hexYFormula 2

Response

SUCCESS → status: SETTLED. DECLINED → status: PENDING + decline_reason. UNDEFINED → status: PENDING.

API Reference — CREDITVOID#

POSThttps://{PAYMENT_URL}/post
ParameterFormatReqNotes
actionCREDITVOIDY
client_keyUUIDY
trans_idUUIDY
amountNumberNOmit for full refund. Multiple partials allowed.
hashMD5 hexYFormula 2

Response

Synchronous: result: ACCEPTED. Callback: SUCCESS with status: REFUND/REVERSAL (full) or SETTLED (partial). DECLINED includes decline_reason.

API Reference — VOID#

POSThttps://{PAYMENT_URL}/post
ParameterFormatReqNotes
actionVOIDY
client_keyUUIDY
trans_id≤255 charsY
hashMD5 hexYFormula 2

Response

resultstatusNotes
SUCCESSVOIDTransaction voided successfully
DECLINEDSETTLEDVoid rejected — includes decline_reason. Original transaction remains settled.
UNDEFINEDPENDING / SETTLEDStatus undetermined — await callback for final result

Same-day only. Applies to SALE, CAPTURE, SALE_RECURRING in SETTLED status.

API Reference — CREDIT2CARD#

POSThttps://{PAYMENT_URL}/post
ParameterFormatReqNotes
actionCREDIT2CARDY
client_keyUUIDY
channel_id≤16 charsNSub-account
order_id≤255 charsY
order_amountNumberY
order_currency3-letterY
order_description≤1024 charsY
card_numberPANYRecipient card
payee_first_name≤32 charsNRecipient name
payee_last_name≤32 charsN
payee_middle_name≤32 charsN
payee_birth_dateyyyy-MM-ddN
payee_address≤255 charsN
payee_address2≤255 charsN
payee_country2-letterN
payee_state≤32 charsN
payee_city≤32 charsN
payee_zip≤10 charsN
payee_email≤256 charsN
payee_phone≤32 charsN
payer_first_name≤32 charsNSender name
payer_last_name≤32 charsN
payer_middle_name≤32 charsN
payer_birth_dateyyyy-MM-ddN
payer_address≤255 charsN
payer_address2≤255 charsN
payer_country2-letterN
payer_state≤32 charsN
payer_city≤32 charsN
payer_zip≤10 charsN
payer_email≤256 charsN
payer_phone≤32 charsN
payer_ipIPv4N
parametersObjectNAcquirer-specific
hashMD5 hexYFormula 5. Callback uses Formula 6.
ℹ️
Reduced response fields. Unlike SALE, the CREDIT2CARD synchronous response does not include amount or currency on SUCCESS. All responses return: action, result, status, order_id, trans_id, trans_date.
resultstatusAdditional Fields
SUCCESSSETTLEDdescriptor
DECLINEDDECLINEDdecline_reason
UNDEFINEDPREPAREdescriptor (if available)

Test card: 4601541833776519.

API Reference — GET_TRANS_STATUS#

POSThttps://{PAYMENT_URL}/post
ParameterFormatReqNotes
actionGET_TRANS_STATUSY
client_keyUUIDY
trans_idUUIDY
hashMD5 hexYFormula 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#

POSThttps://{PAYMENT_URL}/post
ParameterFormatReqNotes
actionGET_TRANS_DETAILSY
client_keyUUIDY
trans_idUUIDY
hashMD5 hexYFormula 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#

POSThttps://{PAYMENT_URL}/post
ParameterFormatReqNotes
actionGET_TRANS_STATUS_BY_ORDERY
client_keyUUIDY
order_id≤255 charsYYour order ID
hashMD5 hexYFormula 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#

POSThttps://{PAYMENT_URL}/post
ParameterFormatReqNotes
actionRECURRING_SALEY
client_keyUUIDY
order_id≤255 charsYNew unique order ID
order_amountNumberY
order_description≤1024 charsY
recurring_first_trans_idUUIDYtrans_id of initial transaction
recurring_tokenUUIDYToken from initial transaction
schedule_idStringNLink to a schedule
authY/NNAUTH only (DMS)
custom_dataObjectNOverrides initial SALE custom_data
hashMD5 hexYFormula 1

Response identical to SALE but action=RECURRING_SALE. Bypasses 3DS.

API Reference — RETRY#

POSThttps://{PAYMENT_URL}/post
ParameterFormatReqNotes
actionRETRYY
client_keyUUIDY
trans_idUUIDYDeclined recurring trans_id
hashMD5 hexYFormula 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

ParameterFormatReqNotes
actionCREATE_SCHEDULEY
client_keyUUIDY
name≤100 charsYSchedule name
interval_lengthNumber >0Ye.g. 15 for every 15 days
interval_unitday/monthY
day_of_month1-31NOnly if interval_unit=month. 29/30/31 → last day if month shorter.
payments_countNumberYTotal payments in schedule
delaysNumberNIntervals to skip before starting
hashMD5 hexYFormula 3

Response: schedule_id.

PAUSE_SCHEDULE

ParameterReqNotes
action = PAUSE_SCHEDULEY
client_keyY
schedule_idY
hashYFormula 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

ParameterReqNotes
action = DESCHEDULEY
client_keyY
recurring_tokenYFrom initial transaction
schedule_idY
hashYFormula 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:

ExpiryScenarioResponse
01/2038 Successful SALE (also use for recurring init — only card that returns recurring_token) result: SUCCESS, status: SETTLED
AUTH: status: PENDING
02/2038 Declined SALE / AUTH result: DECLINED, status: DECLINED
03/2038 Successful AUTH, then declined CAPTURE AUTH: SUCCESS/PENDING
CAPTURE: 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 NumberScenarioResponse
4601541833776519 Successful card payout result: SUCCESS, status: SETTLED
⚠️
Recurring token generation only works with 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

CodeDescriptionCategory
204002Enabled merchant mappings or MIDs not foundConfiguration
204003Payment type not supportedConfiguration
204004Payment method not supportedConfiguration
204005Payment action not supportedConfiguration
204006Payment system/brand not supportedConfiguration
204007Day MID limit is not set or exceededLimits
204008Day merchant mapping limit is not set or exceededLimits
204009Payment type not foundConfiguration
204010Payment method not foundConfiguration
204011Payment system/brand not foundConfiguration
204012Payment currency not foundConfiguration
204013Payment action not foundConfiguration
204014Month MID limit exceededLimits
204015Week merchant mapping limit exceededLimits
208001Payment not foundTransaction
208002Cannot request 3DS for payment not in 3DS statusTransaction
208003Cannot capture payment not in PENDING statusTransaction
208004Capture amount exceeds auth amountTransaction
208005Cannot refund payment not in SETTLED or PENDING statusTransaction
208006Refund amount exceeds payment amountTransaction
208008Reversal amount exceeds payment amountTransaction
208009Partial reversal not allowedTransaction
208010Chargeback amount exceeds payment amountTransaction
205005Card token is invalid or not foundToken
205006Card token is expiredToken
205007Card token is not accessibleToken
400Duplicate requestValidation
100000Previous payment not completedValidation
ℹ️
Decline reasons are returned in the 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)




Hash will appear here…

Formula 2 — CAPTURE / CREDITVOID / VOID / Callback





Hash will appear here…

Formula 5 — CREDIT2CARD



Hash will appear here…

Formula 8 — Digital Wallets (Apple Pay / Google Pay)



Hash will appear here…

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

ItemDetail
✅ SALE tested — successCard 4111111111111111 exp 01/2038 returns SUCCESS/SETTLED
✅ SALE tested — declineExp 02/2038 returns DECLINED
✅ 3DS redirect handledExp 05/2038 → redirect → return → SUCCESS. Exp 06/2038 → DECLINED
✅ Callback handler implementedEndpoint returns plain OK. Hash verified using Formula 2. Tested with all result types.
✅ CREDITVOID testedFull and partial refunds work correctly
✅ VOID tested (if used)Same-day cancellation on SETTLED transaction
✅ GET_TRANS_STATUS testedStatus polling works as fallback when callback delayed
✅ Error handling implementedAll error codes handled gracefully. User sees meaningful messages.
✅ Idempotent order IDsEach payment attempt uses a unique order_id. Duplicates handled.
✅ IP addresses providedProduction server IPs sent to CentaPay for whitelisting
✅ Callback URL configuredProduction callback URL registered with CentaPay
✅ HTTPS everywhereAll endpoints use TLS. No plain HTTP.

If Using Recurring

ItemDetail
✅ RECURRING_SALE testedInitial SALE with recurring_init=Y, then RECURRING_SALE with token
✅ RETRY testedSoft decline → RETRY → final result via callback
✅ Schedule ops tested (if used)CREATE, PAUSE, RUN, DELETE, SCHEDULE_INFO, DESCHEDULE

If Using Payouts

ItemDetail
✅ CREDIT2CARD testedTest card 4601541833776519 returns SUCCESS
✅ Formula 5 hash verifiedRequest hash uses Formula 5 (not Formula 1)
✅ Formula 6 callback hashCallback verification uses Formula 6 (not Formula 2)

If Using Digital Wallets

ItemDetail
✅ Apple Pay: Merchant ID configuredCertificates and keys uploaded to admin panel
✅ Apple Pay: Domains verifiedAll payment domains registered in Apple Developer
✅ Google Pay: Integration checklistCompleted per Google's requirements
✅ Google Pay: Domains verifiedVerified in Google Business Console
✅ Formula 8 hash usedWallet SALE uses email + PASSWORD only

Compliance

ItemDetail
✅ PCI DSS evidenceSAQ D or full assessment (for raw card S2S). Reduced scope for wallet-only.
✅ KYB completeBusiness verification documents submitted and approved
✅ Prohibited MCCs reviewedBusiness model confirmed against prohibited categories
✅ Client agreement signedExecuted with CentaPay

Commercial

ItemDetail
✅ Production credentials receivedSeparate CLIENT_KEY, PASSWORD, PAYMENT_URL for production
✅ Settlement currency confirmedUSD or preferred currency agreed with CentaPay
✅ MID configuredProduction 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.

AFSA Regulated AIFC Incorporated Professional Clients Only

Prohibited Categories#

⚠️
Review before integrating. The full prohibited services list is published at centapay.com/restricted. If your business model includes any restricted categories, discuss with your account manager before integration begins.

Prohibited categories include: unlicensed financial services, gambling without appropriate licensing, adult content, weapons and controlled substances, and services subject to international sanctions.

Permitted Verticals

VerticalExamplesNotes
Retail e-commerceFashion, electronics, consumer goodsStandard checkout flows
Online gaming — low riskIn-app purchases, game creditsLow-risk gaming only. Gambling and lottery excluded.
Social media contentSubscriptions, content creator platformsExcluding adult/18+ content
⚠️
Excluded by default: Dating platforms, live chat, gambling, lottery, and all verticals not listed above are excluded unless individually approved. Contact info@centapay.com to request a vertical review.

PCI DSS#

Integration TypePCI Requirement
S2S with raw card dataPCI DSS SAQ D or full on-site assessment
S2S with Apple Pay / Google Pay tokensReduced 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:

ℹ️
CentaPay operates under AFSA supervision and maintains its own AML/CTF programme. Your compliance obligations are complementary, not in place of ours.