Skip to content

Request format & encryption ​

The business parameters of every request are encrypted with RSA.

Keys ​

Under Developers → API & callbacks in the merchant console you have an RSA key pair (4096-bit, PEM):

KeyStarts withWhat you use it for
Public key-----BEGIN RSA PUBLIC KEY-----Encrypt request parameters
Private key-----BEGIN RSA PRIVATE KEY-----Decrypt callbacks from the platform

WARNING

  • Keep the private key only on your servers (a file or a key management service). Never put it in code, a repository or a chat tool.
  • After you regenerate the keys in the merchant console, the old keys stop working immediately: requests encrypted with the old public key fail to decrypt (40000), and callbacks are encrypted with the new public key. Prepare the switch before regenerating.

Request body ​

json
{
  "merchantNo": "your merchant number",
  "encryptedData": "Base64 string"
}

How to build encryptedData:

  1. Write the business parameters as JSON (UTF-8), including merchantNo, for example:

    json
    {"merchantNo":"your merchant number","merchantOrderNo":"A20261013001","amount":100,"currencyType":"CNY","customerName":"Zhang San"}
  2. Encrypt it with the public key using RSA with PKCS#1 v1.5 padding (RSA/ECB/PKCS1Padding).

  3. Base64-encode the result.

The platform decrypts it with the private key of that merchant; if it can't, it returns 40000. The IP whitelist and keys are checked against the outer merchantNo, and the merchantNo inside the encrypted JSON must be the same.

Maximum length

RSA can encrypt at most "key size in bytes − 11" bytes at once: 501 bytes for a 4096-bit key (a Chinese character takes 3 bytes). If the parameters are longer, encryption fails — leave out optional parameters you don't need. The sample code reports this clearly.

Examples ​

The complete code (all endpoints and callback verification) is in Sample code. Only the core encryption lines are shown here.

js
const crypto = require('node:crypto');

function encrypt(publicKeyPem, params) {
  const data = Buffer.from(JSON.stringify(params), 'utf8');
  return crypto.publicEncrypt({ key: publicKeyPem, padding: crypto.constants.RSA_PKCS1_PADDING }, data).toString('base64');
}
python
import base64, json
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding

def encrypt(public_key_pem: str, params: dict) -> str:
    key = serialization.load_pem_public_key(public_key_pem.encode())
    data = json.dumps(params, ensure_ascii=False).encode("utf-8")
    return base64.b64encode(key.encrypt(data, padding.PKCS1v15())).decode()
php
function encrypt(string $publicKeyPem, array $params): string {
    $data = json_encode($params, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
    openssl_public_encrypt($data, $encrypted, $publicKeyPem, OPENSSL_PKCS1_PADDING) or throw new RuntimeException('encryption failed');
    return base64_encode($encrypted);
}
java
// The console gives you a PKCS#1 public key (BEGIN RSA PUBLIC KEY); Java's KeyFactory only reads X.509,
// so the sample's IBitPayCrypto.readPublicKey() converts it for you
RSAPublicKey key = IBitPayCrypto.readPublicKey(publicKeyPem);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
String encryptedData = Base64.getEncoder().encodeToString(cipher.doFinal(json.getBytes(StandardCharsets.UTF_8)));
csharp
using var rsa = RSA.Create();
rsa.ImportFromPem(publicKeyPem);   // reads both PKCS#1 and X.509 PEM
var encryptedData = Convert.ToBase64String(rsa.Encrypt(Encoding.UTF8.GetBytes(json), RSAEncryptionPadding.Pkcs1));

Security requirements ​

  • Always call the API over https:// with certificate validation enabled.
  • The calling server's outbound IP must be whitelisted. Creating orders and payouts, Get order, List balance flows, List blacklist, Cancel order and Mark as paid all check the whitelist. Get balance, the rate endpoints and List payout methods don't check it yet but will in the future, so call them from whitelisted IPs too.
  • Enter exact outbound IPs in the whitelist — no ranges or wildcards.