Signing & Auth (RSA2)
The platform uses asymmetric signing: you sign with your own private key and the platform verifies with your public key — the platform stores only your public key. Callbacks go the other way: the platform signs with its private key and you verify with the platform public key (see collection callbacks / payout callbacks).
Common parameters
Every request must carry these common parameters (signed together with business parameters):
| Field | Description |
|---|---|
merchant_no | Merchant number |
app_id | Application ID |
key_version | Your public-key version (supports smooth rotation) |
timestamp | Unix seconds; must be within 5 minutes of server time |
nonce | Random string ≥16 chars, unique within the validity window |
sign_type | Fixed RSA2 (SHA256withRSA, RSA-2048, PKCS#1 v1.5) |
sign | Signature, standard Base64, no line breaks |
Signing algorithm (4 steps)
- Take all common + business parameters, excluding
signitself and any field whose value isnullor an empty string (sign_typeis included to prevent downgrade). - Sort by parameter name in ASCII ascending order.
- Percent-encode each value per RFC3986, then join as
k1=v1&k2=v2&…(keys themselves are not encoded). This is the message to sign (UTF-8 bytes). sign = Base64( RSA_SHA256_Sign(your private key, message) ). The platform verifies with your registered public key.
Percent-encoding must be RFC3986
Space → %20 (not +); A-Za-z0-9-_.~ stay unencoded; hex uppercase; over UTF-8 bytes. In Java do NOT use URLEncoder.encode (form encoding turns spaces into +); in PHP use rawurlencode; in Go do not use url.QueryEscape directly. All languages must produce byte-identical messages.
Signed values must be scalars
Values must be strings or integers only — never JSON objects/arrays; amounts are integers in minor units. Pass-through fields such as attach must be strings (serialize complex content to a JSON string yourself first).
Example:
message = amount=10000&app_id=app_10001¤cy=PHP&key_version=1&mch_order_no=T20260528001
&merchant_no=M100001&nonce=a1b2c3d4e5f6a1b2&product_code=QRPH&sign_type=RSA2×tamp=1769990400
sign = Base64( SHA256withRSA( merchant_private_key, message ) )Key & encoding formats
- Private key: PKCS#8 PEM (
-----BEGIN PRIVATE KEY-----). Generate your own RSA-2048 key pair; the private key never leaves your server. - Public key: X.509/SPKI PEM (
-----BEGIN PUBLIC KEY-----), registered with the platform (bound toapp_id+key_version). - Signature output and key transport: standard Base64 (not URL-safe), no line breaks.
Generate keys:
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out merchant_private.pem
openssl pkey -in merchant_private.pem -pubout -out merchant_public.pemSecurity check order
Requests are checked in this order; any failure rejects:
- Rate limit (
1007) - All common parameters present
- Look up your public key by
merchant_no/app_id/key_version - Timestamp within 5 minutes of server time (
1003) - RSA signature verification (
1002) - Merchant / app / key status active (
1006) - Source IP in your allowlist; an empty allowlist rejects everything (
1005) - Anti-replay:
(app_id, nonce)is consumed only after a valid signature (1004)