Collect the card on your own payment form and send it to Manuell from your server. The customer confirms the payment with 3-D Secure and you receive the result by webhook.
Your server talks to Manuell directly: it asks which flow a card needs, creates the order with the card details and then follows the order until it is paid or failed.
Base URL
https://www.manuellsolutions.com/api/v4
Currency
TRY only. One payment per order, no installments.
Amounts
Strings with 2 decimals, for example "1500.00".
Environment
sandbox or live, sent in X-CEK-Environment. It must match the environment of your key.
Errors
application/problem+json (RFC 7807) with a 7-digit code.
Rate limit
300 requests per minute per key (600 per minute per IP address before authentication). Beyond that: 429 with Retry-After.
Duplicates
There is no Idempotency-Key. merchant_order_id protects against duplicate orders.
Look up the BIN
Send the first 6 digits of the card and the amount to GET /api/v4/bins/{bin}. payment_mode tells you whether to collect the card yourself (direct) or hand the customer to a hosted page (hosted).
Create the order
Send POST /api/v4/orders with the card (direct) or only the BIN (hosted). The order comes back as requires_action, requires_hosted, pending or failed.
Finish and confirm
Send the customer to redirect_url (3-D Secure) or hosted_url. The final result arrives as a webhook; if it is late, read the order with GET /api/v4/orders/{merchantOrderId}.
Set up in the panel
Generate an API key and secret for each environment on “API Key & Integration”. Keep the secret on your server.
Add the public IP addresses of your servers to the IP allowlist of the environment. While the list is empty, every call is rejected with 403 and code 0055501.
Set an HTTPS webhook address. Its host must be public.
Set the success and failure return addresses (HTTPS in live). Without them the customer stays on the result screen after paying.
Your merchant code is MRC- followed by your 6-digit account number, for example MRC-123456.
Authentication
Every call carries X-CEK-* headers. The signature is an HMAC-SHA256 over a string that joins the timestamp, the method, the path and the hash of the body.
Required headers
Header
Description
X-CEK-Api-Key
The API key of the environment.
X-CEK-Environment
sandbox or live. Must match the environment of the key.
X-CEK-Merchant-Code
Your merchant code, for example MRC-123456. Required in sandbox; in live, if you send it, it must match your account.
X-CEK-Timestamp
Current Unix time in seconds (10 digits). A difference of more than 300 seconds is rejected.
X-CEK-Signature
64 lowercase hex characters, computed as described below.
Content-Type
application/json on POST requests.
How to sign a request
Take the body exactly as you send it and compute sha256(body) as lowercase hex. For a request without a body use the hash of the empty string.
Join the timestamp, the uppercase method, the path and the body hash with line feeds (\n). The path is the full path without host and query string, for example /api/v4/orders.
Compute HMAC-SHA256 of that string with your API secret as the key, and write it as 64 lowercase hex characters.
Send it as X-CEK-Signature together with the other headers.
Each sample looks up a BIN and creates an order in sandbox. Credentials come from environment variables; never put the secret in code. Every run uses a new merchant_order_id.
#!/usr/bin/env bash
# export MERCHANT_API_KEY=... MERCHANT_API_SECRET=... MERCHANT_CODE=MRC-123456 (sandbox key)
set -euo pipefail
BASE_URL="https://www.manuellsolutions.com"
signed() { # signed METHOD PATH [BODY]
local method="$1" path="$2" body="${3:-}" timestamp sha string_to_sign signature
timestamp="$(date +%s)"
sha="$(printf '%s' "$body" | openssl dgst -sha256 -hex | awk '{print $NF}')"
string_to_sign="$(printf '%s\n%s\n%s\n%s' "$timestamp" "$method" "${path%%\?*}" "$sha")"
# the secret travels in the environment, not on the command line
signature="$(printf '%s' "$string_to_sign" | python3 -c 'import hashlib,hmac,os,sys; print(hmac.new(os.environ["MERCHANT_API_SECRET"].encode(), sys.stdin.buffer.read(), hashlib.sha256).hexdigest())')"
local args=(--silent --show-error --request "$method" "$BASE_URL$path"
--header "X-CEK-Api-Key: $MERCHANT_API_KEY"
--header "X-CEK-Environment: sandbox"
--header "X-CEK-Merchant-Code: $MERCHANT_CODE"
--header "X-CEK-Timestamp: $timestamp"
--header "X-CEK-Signature: $signature")
if [ -n "$body" ]; then args+=(--header "Content-Type: application/json" --data-binary "$body"); fi
curl "${args[@]}"
echo
}
# 1) BIN lookup: no body, so the hash is sha256("")
signed GET "/api/v4/bins/435509?amount=1500.00"
# 2) create an order; a new merchant_order_id for every order
ORDER_ID="ORDER-$(date +%s)-$(openssl rand -hex 4)"
signed POST "/api/v4/orders" "{\"merchant_order_id\":\"$ORDER_ID\",\"amount\":\"1500.00\",\"card\":{\"holder_name\":\"TEST CUSTOMER\",\"number\":\"4355090000044242\",\"expiry_month\":\"12\",\"expiry_year\":\"30\",\"cvv\":\"123\"}}"
<?php
$config = [
'base' => 'https://www.manuellsolutions.com',
'key' => getenv('MERCHANT_API_KEY'),
'secret' => getenv('MERCHANT_API_SECRET'),
'code' => getenv('MERCHANT_CODE'),
'env' => 'sandbox',
];
function signedRequest(array $config, string $method, string $path, ?array $payload = null): array
{
$method = strtoupper($method);
$signingPath = explode('?', $path, 2)[0];
// serialise once: the same bytes are hashed and sent
$body = $payload === null ? '' : json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$timestamp = (string) time();
$stringToSign = implode("\n", [$timestamp, $method, $signingPath, hash('sha256', $body)]);
$signature = hash_hmac('sha256', $stringToSign, $config['secret']);
$headers = [
'X-CEK-Api-Key: ' . $config['key'],
'X-CEK-Environment: ' . $config['env'],
'X-CEK-Merchant-Code: ' . $config['code'],
'X-CEK-Timestamp: ' . $timestamp,
'X-CEK-Signature: ' . $signature,
];
if ($body !== '') {
$headers[] = 'Content-Type: application/json';
}
$ch = curl_init($config['base'] . $path);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 35, // an order can take up to 30 seconds
]);
if ($body !== '') {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($response === false) {
throw new RuntimeException('No response; repeat the same request or read the order with GET.');
}
return [$status, json_decode((string) $response, true)];
}
// 1) BIN lookup: no body, so the hash is sha256("")
[$status, $bin] = signedRequest($config, 'GET', '/api/v4/bins/435509?amount=1500.00');
echo $status, ' ', $bin['data']['payment_mode'] ?? '', PHP_EOL;
// 2) create an order; a new merchant_order_id for every order
[$status, $order] = signedRequest($config, 'POST', '/api/v4/orders', [
'merchant_order_id' => 'ORDER-' . time() . '-' . bin2hex(random_bytes(4)),
'amount' => '1500.00',
'card' => ['holder_name' => 'TEST CUSTOMER', 'number' => '4355090000044242', 'expiry_month' => '12', 'expiry_year' => '30', 'cvv' => '123'],
]);
echo $status, ' ', $order['data']['status'] ?? '', PHP_EOL;
const crypto = require("node:crypto");
const BASE_URL = "https://www.manuellsolutions.com";
const { MERCHANT_API_KEY, MERCHANT_API_SECRET, MERCHANT_CODE } = process.env;
async function signedRequest(method, path, payload) {
method = method.toUpperCase();
const signingPath = path.split("?", 1)[0];
// serialise once: the same bytes are hashed and sent
const body = payload === undefined ? "" : JSON.stringify(payload);
const timestamp = String(Math.floor(Date.now() / 1000));
const bodyHash = crypto.createHash("sha256").update(body).digest("hex");
const stringToSign = [timestamp, method, signingPath, bodyHash].join("\n");
const signature = crypto.createHmac("sha256", MERCHANT_API_SECRET).update(stringToSign).digest("hex");
const headers = {
"X-CEK-Api-Key": MERCHANT_API_KEY,
"X-CEK-Environment": "sandbox",
"X-CEK-Merchant-Code": MERCHANT_CODE,
"X-CEK-Timestamp": timestamp,
"X-CEK-Signature": signature,
};
if (body !== "") headers["Content-Type"] = "application/json";
// an order can take up to 30 seconds
const res = await fetch(BASE_URL + path, { method, headers, body: body === "" ? undefined : body, signal: AbortSignal.timeout(35000) });
return { status: res.status, body: JSON.parse(await res.text()) };
}
(async () => {
// 1) BIN lookup: no body, so the hash is sha256("")
const bin = await signedRequest("GET", "/api/v4/bins/435509?amount=1500.00");
console.log(bin.status, bin.body.data?.payment_mode);
// 2) create an order; a new merchant_order_id for every order
const order = await signedRequest("POST", "/api/v4/orders", {
merchant_order_id: `ORDER-${Date.now()}-${crypto.randomBytes(4).toString("hex")}`,
amount: "1500.00",
card: { holder_name: "TEST CUSTOMER", number: "4355090000044242", expiry_month: "12", expiry_year: "30", cvv: "123" },
});
console.log(order.status, order.body.data?.status);
})();
import hashlib
import hmac
import json
import os
import time
import urllib.error
import urllib.request
import uuid
BASE_URL = "https://www.manuellsolutions.com"
API_KEY = os.environ["MERCHANT_API_KEY"]
API_SECRET = os.environ["MERCHANT_API_SECRET"]
MERCHANT_CODE = os.environ["MERCHANT_CODE"]
def signed_request(method, path, payload=None):
method = method.upper()
signing_path = path.split("?", 1)[0]
# serialise once: the same bytes are hashed and sent
body = json.dumps(payload, separators=(",", ":")) if payload is not None else ""
timestamp = str(int(time.time()))
string_to_sign = "\n".join([timestamp, method, signing_path, hashlib.sha256(body.encode()).hexdigest()])
signature = hmac.new(API_SECRET.encode(), string_to_sign.encode(), hashlib.sha256).hexdigest()
headers = {
"X-CEK-Api-Key": API_KEY,
"X-CEK-Environment": "sandbox",
"X-CEK-Merchant-Code": MERCHANT_CODE,
"X-CEK-Timestamp": timestamp,
"X-CEK-Signature": signature,
}
if body:
headers["Content-Type"] = "application/json"
request = urllib.request.Request(BASE_URL + path, data=body.encode() or None, headers=headers, method=method)
try:
with urllib.request.urlopen(request, timeout=35) as response: # an order can take up to 30 seconds
return response.status, json.loads(response.read())
except urllib.error.HTTPError as error: # problem+json body
return error.code, json.loads(error.read())
# 1) BIN lookup: no body, so the hash is sha256("")
status, bin_info = signed_request("GET", "/api/v4/bins/435509?amount=1500.00")
print(status, bin_info.get("data", {}).get("payment_mode"))
# 2) create an order; a new merchant_order_id for every order
status, order = signed_request("POST", "/api/v4/orders", {
"merchant_order_id": f"ORDER-{int(time.time())}-{uuid.uuid4().hex[:8]}",
"amount": "1500.00",
"card": {"holder_name": "TEST CUSTOMER", "number": "4355090000044242", "expiry_month": "12", "expiry_year": "30", "cvv": "123"},
})
print(status, order.get("data", {}).get("status"))
Look up a BIN
GET/api/v4/bins/{bin}
Look up a card BIN
Call it as soon as the customer has typed the first 6 digits. The answer is a snapshot that helps you choose between your own card form and the hosted page; the final decision is made when you create the order.
Parameters
Field
Type
Required
Description
bin
string · path
Required
First six digits of the card number.
amount
string · query
Required
Order amount in TRY with 2 decimals. Eligibility is evaluated for this amount.
Response · 200
Field
Type
Description
bin
string
The BIN you asked about.
bank_name
string | null
Issuing bank, or null if unknown.
network
string | null
Card network: visa, mastercard, amex, troy or null.
card_type
string | null
credit, debit, prepaid or null.
card_program
string | null
Card programme name, or null.
country
string | null
Issuing country (ISO alpha-2), or null.
domestic
boolean
true for a card issued in Türkiye.
payment_mode
string
direct: collect the card yourself. hosted: use the hosted page.
One endpoint creates the order in both modes. The body decides: send card for a direct order, or only bin for a hosted order. Sending both returns 422.
Direct order (with card)
Field
Type
Required
Description
merchant_order_id
string
Required
Your order reference: 1–64 characters from A-Z a-z 0-9 . _ -, unique per account and environment.
amount
string
Required
Amount in TRY with 2 decimals, for example "1500.00".
merchant_order_id must be unique per account and environment. It is the only protection against duplicates.
Repeating a request with the same merchant_order_id and the same content starts no new payment and returns the order as it is now. Different content returns 409 with code 0055503.
A field that is not in the schema returns 422 with code 1000001.
On a 4xx response no payment was taken. Use a new merchant_order_id for the next attempt. A failed order is final as well.
After a 5xx response or a timeout the payment may already have started. Do not create a new order: repeat the same request unchanged or read the order with GET. Allow at least 30 seconds before you time out.
If the BIN is hosted at that moment, a request that contains card still returns requires_hosted and the card details are not used.
The order object
Create and read calls return the order inside data.
Field
Type
Description
order_code
string
Order number, 9 digits as a string. It is the value that comes back on the return address.
merchant_order_id
string
Your merchant_order_id.
status
string
requires_action, requires_hosted, pending, paid or failed.
amount
string
Order amount, 2 decimals.
currency
string
Always TRY.
redirect_url
string | null
3-D Secure address. Set while the status is requires_action.
hosted_url
string | null
Hosted page address. Set while the status is requires_hosted.
hosted_expires_at
string | null
When hosted_url stops working (ISO 8601, UTC).
failure_reason_code
string | null
Reason code for a failed order; see Decline reasons. Can be null if the payment was never attempted.
failure_message
string | null
Short customer-facing text for the reason. It is always in Turkish; branch on the code instead.
card
object | null
Card summary (holder_name, bin, last_four, network, program, issuer_bank_name). Present at once for direct; for hosted once the result is known.
The bank has not answered yet, or the customer left the screen. Wait for the webhook.
paid
Yes
The payment was collected.
failed
Yes
The payment did not go through. failure_reason_code explains why.
The customer has 10 minutes for 3-D Secure. The hosted page works until hosted_expires_at. A payment the bank never answers becomes paid or failed within 24 hours, and an expired payment becomes failed.
Rarely, a failed order can become paid later. Treat paid as the truth.
Get an order
GET/api/v4/orders/{merchantOrderId}
Get an order
Returns the same object as the create call, without a request body. redirect_url is returned until the 3-D Secure window ends and hosted_url until hosted_expires_at.
Parameters
Field
Type
Required
Description
merchantOrderId
string · path
Required
The merchant_order_id you used when creating the order.
The webhook is the main channel. If it is late, read the order at most every 30 seconds.
After the order is created, the customer finishes the payment on a page that is not yours. Send them there with a normal full-page redirect.
3-D Secure (`requires_action`)
Redirect the customer's browser to redirect_url.
The customer confirms the payment with their bank.
When the payment is final, the customer is sent to your return address and you receive the webhook.
Hosted page (`requires_hosted`)
Send the customer to hosted_url. A full-page redirect always works; showing it in a modal frame depends on the page's permission, so keep the redirect as the fallback.
The customer enters the card and completes 3-D Secure on that page. A failed attempt does not close the order; the customer can try again there.
If the customer closes the page, the same hosted_url opens it again until hosted_expires_at.
When the payment ends, the whole page is sent to your return address. Confirm the result with the webhook.
Return address
When an order becomes paid or failed, the customer is sent to the matching return address you set in the panel. If a webhook address is set, the address gets these signed query parameters.
Field
Type
Description
order_code
string
The 9-digit order number. The address has no merchant_order_id; match the order with this value.
status
string
paid or failed.
failure_code
string
Reason code, only for a failed order that has one. Otherwise the parameter is absent.
ts
integer
Unix time in seconds when the address was built.
key_version
integer
Version of your secret. It goes up each time you renew the secret.
sig
string
Signature, 64 lowercase hex characters.
Canonical signing string
sig = hex(HMAC-SHA256(key = API_SECRET, message = ts + "." + order_code + "." + status + "." + failure_code))
Verify sig before you trust the parameters. If failure_code is absent, use an empty string in its place; the dot stays.
Reject a ts older than 10 minutes.
The return only says where the customer is. The result is final when the webhook arrives or GET /api/v4/orders/{merchantOrderId} says so.
If no webhook address is set, the address is called without these parameters.
Results are delivered as signed JSON POST requests to the HTTPS webhook address of the environment.
Event
Sent when
checkout.order.succeeded
The order is paid.
checkout.order.failed
The order is failed.
Header
Header
Description
X-CEK-Timestamp
Unix time in seconds. Reject a difference of more than 300 seconds.
X-CEK-Signature
Signature: hex of the HMAC-SHA256 of the timestamp, a dot and the raw body, with your API secret as the key.
X-CEK-Event-Id
UUID of the event. Same as event_id in the body and unchanged on retries.
X-CEK-Environment
sandbox or live.
Verify every notification
Read the raw body before parsing the JSON.
Compute the signature over X-CEK-Timestamp, a dot and the raw body, and compare it with X-CEK-Signature in constant time.
Reject the request if the timestamp differs from your clock by more than 300 seconds.
Deduplicate with X-CEK-Event-Id. Delivery is at least once and can be out of order. If you have seen checkout.order.succeeded for an order, ignore any checkout.order.failed for it.
Answer with any 2xx within 10 seconds (3 seconds to connect). Redirects (3xx), 4xx, 5xx and timeouts count as failures and are retried.
Signature check samples
<?php
// Verify a notification: the signature covers "<X-CEK-Timestamp>.<raw body>".
function isValidDirectWebhook(string $rawBody, string $timestamp, string $signature, string $secret): bool
{
if (preg_match('/^[0-9]{10}$/D', $timestamp) !== 1 || preg_match('/^[0-9a-f]{64}$/D', $signature) !== 1
|| abs(time() - (int) $timestamp) > 300) {
return false;
}
return hash_equals(hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret), $signature);
}
$rawBody = file_get_contents('php://input');
if (!isValidDirectWebhook($rawBody, $_SERVER['HTTP_X_CEK_TIMESTAMP'] ?? '', $_SERVER['HTTP_X_CEK_SIGNATURE'] ?? '', getenv('MERCHANT_API_SECRET'))) {
http_response_code(401);
exit;
}
// Deduplicate with X-CEK-Event-Id, save the event, then answer 2xx.
http_response_code(204);
const crypto = require("node:crypto");
// Verify a notification: the signature covers "<X-CEK-Timestamp>.<raw body>".
// rawBody must be the untouched request bytes (Buffer), not re-serialised JSON.
function isValidDirectWebhook(rawBody, timestamp, signature, secret) {
if (!/^[0-9]{10}$/.test(timestamp) || !/^[0-9a-f]{64}$/.test(signature)
|| Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(timestamp + "." + rawBody.toString("utf8")).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signature);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Then deduplicate with X-CEK-Event-Id, save the event and answer 2xx.
module.exports = { isValidDirectWebhook };
import hashlib
import hmac
import re
import time
def is_valid_direct_webhook(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
"""Verify a notification: the signature covers "<X-CEK-Timestamp>.<raw body>"."""
if (not re.fullmatch(r"[0-9]{10}", timestamp) or not re.fullmatch(r"[0-9a-f]{64}", signature)
or abs(int(time.time()) - int(timestamp)) > 300):
return False
expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
# Then deduplicate with X-CEK-Event-Id, save the event and answer 2xx.
Retries
A failed delivery is repeated after the waits below, counted from the previous failure, up to 11 attempts in total.
Attempt
Wait before it
1
—
2
60 s (1 min)
3
300 s (5 min)
4
900 s (15 min)
5
1800 s (30 min)
6
3600 s (1 h)
7
10800 s (3 h)
8
21600 s (6 h)
9
43200 s (12 h)
10
86400 s (24 h)
11
172800 s (48 h)
Body
The body carries schema_version (3), event_id, event_type, an order object, a merchant object and the card summary in payment_details.card. A failed event adds failure_code, failure_reason_code and failure_message. Ignore fields you do not use; new fields can appear.
failure_reason_code is one of these codes. Build your logic on the code; failure_message is a Turkish text for the customer.
Code
Description
payment_declined
The issuing bank did not approve the payment.
do_not_honor
The bank refused without giving a specific reason.
insufficient_funds
The balance or limit of the card is too low for this payment.
card_limit_reached
The card has reached its transaction limit.
card_details_invalid
The card number was not accepted.
card_expiry_invalid
The expiry date is invalid.
card_security_code_invalid
The security code (CVV) is wrong.
card_not_active
The card is not enabled for payments.
card_brand_not_supported
This card brand is not supported for this payment.
three_ds_failed
3-D Secure could not be completed.
security_declined
A security check stopped the payment. Tell the customer only that the bank did not approve it.
payment_not_permitted
This card cannot be used for this payment.
amount_not_allowed
The amount is outside the range this card accepts.
currency_not_supported
The currency is not supported for this payment.
payment_details_rejected
The payment details were rejected.
duplicate_payment
The same payment seems to have been taken already.
cancelled_by_customer
The customer cancelled before the payment finished.
payment_timed_out
The payment time ran out.
bank_unavailable
The bank cannot be reached right now. Try again shortly.
bank_requested_retry
The bank asked to repeat the payment.
provider_unavailable
The payment could not be completed right now. Try again shortly.
The list can grow. Treat a code you do not know like payment_declined. In the webhook, security_declined is reported as payment_declined in failure_code.
Error codes
Errors use the application/problem+json format. Branch on code; title and detail are for people.
Field
Type
Description
type, title, status, detail
string · integer
Standard problem fields.
instance
string
The path of the request.
code
string
The 7-digit error code from the table below.
correlation_id
string
Quote it when you contact support. Also sent as X-Correlation-Id.
metadata
object
developer_detail, retry_class and audit_event: extra information for debugging.
amount_limit
object
Only on 0055235: min and max of the allowed amount, or null for a volume limit.
errors
object
Only on 1000001: the fields that failed validation.
application/problem+json
{
"type": "https://www.manuellsolutions.com/problems/0055233",
"title": "BIN not recognised",
"status": 409,
"detail": "No card range matches this BIN.",
"instance": "/api/v4/bins/999999",
"code": "0055233",
"correlation_id": "3BAEB108-41A8-40AE-81EF-0A82AA1132D6",
"metadata": {
"developer_detail": "bin_not_found",
"retry_class": "no_retry",
"audit_event": "bin_lookup_rejected"
}
}
Error responses also carry X-Correlation-Id, X-RateLimit-Limit, X-RateLimit-Remaining and Content-Language; a 429 adds Retry-After.
Checks run in this order: schema (1000001), amount (0055235), BIN (0055233, 0055230), card (0055234).
Code
HTTP
Meaning
0055501
403
The IP allowlist is empty or the request comes from an address that is not on it.
0055502
401 / 403
Wrong key, environment or merchant code, an inactive account, or Direct API access that is not open.
0055504
401
The timestamp is not 10 digits or is more than 300 seconds off, or the signature is not 64 lowercase hex characters.
0055505
401
The signature could not be verified.
1000001
422
The body does not match the schema: a missing, wrong or unknown field, or card together with bin. errors lists the fields.
1000005
429
Rate limit exceeded. Wait for Retry-After.
0055001
409
The account is not ready to take payments yet; a setup step is missing.
0055002
403
Card payments are turned off for the account.
0055235
422
The amount is not valid or is outside the limits (amount_limit is set), or a daily, weekly or monthly volume limit was reached (amount_limit is null).
0055230
409
No payment channel is available for this BIN.
0055233
409
The BIN could not be identified.
0055210
422
bin is not exactly six digits.
0055234
409
The card number or the expiry date is not valid.
0055221
409
The daily attempt or amount limit of this card was reached.
0055231
409
This card cannot be charged at the moment.
0055503
409
The same merchant_order_id was used with different content, or the first request is still being processed.
0055402
404
No order exists for this merchant_order_id.
0055404
409
Sandbox result endpoint only: the order is neither pending nor requires_hosted.
Sandbox
Sandbox uses the same endpoints with a sandbox key and X-CEK-Environment: sandbox. No real money moves.
X-CEK-Merchant-Code is required in sandbox. The webhook and return addresses of the sandbox environment are used.
Use any future expiry date and any 3-digit CVV.
Volume limits count sandbox orders only.
BIN
HTTP
Result code
435509
200
direct
540061
200
hosted
979210
409
0055230
any other BIN
409
0055233
Card number
What happens
4355090000044242
requires_action. Approve the test 3-D Secure screen to get paid.
4355090000005151
requires_action. After 3-D Secure the order becomes failed with insufficient_funds.
4355090000089999
requires_action. After 3-D Secure the order stays pending; close it with the sandbox result endpoint.
4355090000040000
failed at once with payment_declined, before 3-D Secure.
any other valid card
requires_action. On the test 3-D Secure screen you choose the result: success gives paid, failure gives failed (payment_declined), timeout leaves pending.
An invalid card number (Luhn) or a past expiry date returns 409 with code 0055234. A hosted order uses the BIN 540061; on its test page you choose success (paid) or failure (failed with insufficient_funds). While it is still requires_hosted, you can also close it with the sandbox result endpoint.
Closes a sandbox order that is pending or, for a hosted order, still waiting for its hosted page (requires_hosted). The webhook and the return address work as in live.
Field
Type
Required
Description
result
string
Required
paid or failed (a failed order gets the reason payment_declined).