Skip to content

v2 Protocol Specification

This page is the protocol specification for Merchant API v2: encryption, signing, replay protection, and request/response formats. Before integrating, make sure you have obtained all 4 credentials from the Credentials & Keys page. Once your code is written, run the full chain end-to-end with the selfcheck endpoint before going live.

1. Protocol Overview

Every business request, response, and callback between the merchant and the platform goes through:

  • Encryption: RSA-OAEP (MGF1 = SHA-1), protecting request/response payloads.
  • Signing: SHA256withRSA, guaranteeing integrity and authenticity.
  • Replay protection: timestamp + nonce, verified in both directions (requests and callbacks).

Business parameters are expressed as JSON, encrypted into ciphertext, and transmitted as the HTTP body.

2. Core Rules (must match byte-for-byte)

2.1 Encryption: RSA-OAEP, MGF1 = SHA-1

  • Split the plaintext into 214-byte chunks, encrypt each chunk with RSA-OAEP, concatenate the ciphertexts, then Base64-encode the whole thing.
  • Decryption: Base64-decode, split into 256-byte chunks, decrypt each chunk, and concatenate.
  • ⚠️ The #1 interoperability pitfall: the OAEP hash must be SHA-1. Many languages default to OAEP-SHA256, and a mismatch between the two sides makes decryption fail. Explicit settings per language:
php
// SHA-1 is the default
openssl_public_encrypt($d, $o, $k, OPENSSL_PKCS1_OAEP_PADDING)
python
padding.OAEP(mgf=MGF1(SHA1()), algorithm=SHA1(), label=None)
js
{ padding: RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha1' }
go
rsa.EncryptOAEP(sha1.New(), rand.Reader, pub, msg, nil)
java
Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding")

2.2 Signing: SHA256withRSA (PKCS#1 v1.5 signature padding)

2.3 Canonical signing string (normative, not an example)

The input to signing / verification must be exactly the string below — three parts joined by newline characters \n (LF, 0x0A). It has to match byte-for-byte, or verification will fail 100% of the time:

ANONY-TIMESTAMP + "\n" + ANONY-NONCE + "\n" + <body>
  • Outbound requests: <body> = the encrypted request body (i.e. the HTTP body, Base64 ciphertext).
  • Responses / callbacks: <body> = the response body (Base64 ciphertext).

2.4 Replay protection

  • ANONY-TIMESTAMP: Unix timestamp in seconds. The server enforces a ±5-minute window (±300 seconds); anything outside it is rejected.
  • ANONY-NONCE: a random string of at least 16 bytes from a CSPRNG (hex or base64 both work; fixed-length hex recommended). The server deduplicates on (merchant ID, nonce) and rejects any repeat.
  • The same applies to callbacks: when the platform calls you back, it also sends ANONY-TIMESTAMP / ANONY-NONCE, and you must validate the time window and deduplicate nonces yourself to reject replayed callbacks (an atomic dedup such as Redis SET key 1 NX EX 900 is recommended, with an expiry of at least 15 minutes). For the full callback-side handling steps, see Callbacks.

3. Request & Response Format

3.1 Request headers (outbound)

ANONY-APP-ID:    <merchant ID>
ANONY-TOKEN:     <token>
ANONY-TIMESTAMP: <Unix timestamp in seconds>
ANONY-NONCE:     <random string, >=16 bytes>
ANONY-SIGN:      <SHA256withRSA signature of the canonical string, base64>
Body:            <business JSON, OAEP-encrypted, as base64 ciphertext>

The ANONY-TOKEN is obtained via getToken.

3.2 Response body (inbound)

json
{ "code": 10000, "data": "<base64 ciphertext>", "message": "success" }
  • code == 10000 means success; data is OAEP-encrypted base64 ciphertext — verify the signature first, then decrypt.
  • The response headers carry ANONY-TIMESTAMP / ANONY-NONCE / ANONY-SIGN.
  • ⚠️ Use a standard JSON library to parse the outer JSON (a / in the response body may be escaped as \/; standard libraries unescape it automatically — do not hand-roll string slicing).

3.3 Steps for handling a response

  1. Parse the outer {code,data,message} with a standard JSON library.
  2. Check that ANONY-TIMESTAMP is within ±5 minutes and that ANONY-NONCE has not been seen before (dedup).
  3. Verify the signature over canonical(ts, nonce, data) using the platform public key.
  4. OAEP-decrypt data with the merchant private key to get the business JSON.

For the meaning of error codes when code != 10000, see Error Codes; for debugging signature/decryption failures, see the Troubleshooting Guide.

4. Implementation Details

Token lifecycle (implementation detail)

ANONY-TOKEN uses a sliding 8-hour expiry with a 24-hour absolute cap: every successful call refreshes the 8-hour validity, but the token lives at most 24 hours from issuance. When you receive tokenExpired, simply call getToken again — there is no need to track expiry precisely on your side.

Request body size limit (implementation detail)

The request body (Base64 ciphertext after encryption) is capped at 128KB. Anything larger is rejected before protocol validation even runs (returning paramError). Normal business requests are far below this limit; if you hit it, check whether a large object has accidentally been stuffed into the business JSON.

v1 protocol in brief (implementation detail — new merchants can skip this)

Legacy merchants use the v1 protocol: MD5withRSA signature verification (over the ciphertext body) plus PKCS#1 v1.5 chunked encryption/decryption, with no timestamp/nonce replay-protection headers. v1 exists only for legacy merchants; all new integrations must use v2. The protocol version is determined on the platform side, per merchant ID, in the database — it cannot be switched or downgraded via any header or parameter on the request side (this prevents downgrade attacks). To upgrade from v1 to v2, first pass the selfcheck, then the platform completes the switch.

5. Security Notes

  • Keep your private key on your server only — never put it in frontend code, logs, or third-party systems.
  • Always verify the response signature before using the data; always apply the time-window check and nonce dedup to callbacks.
  • Timestamps depend on the system clock — make sure your servers are NTP-synchronized (otherwise you will easily fall outside the ±5-minute window).
  • The algorithm branch is decided by the platform per merchant ID and cannot be changed from the request side (downgrade protection).

Next steps: integrate each endpoint following the API Reference, or use the official SDKs (PHP / Python / Node / Go / Java) and skip hand-rolling the protocol details.