Authentication
Authenticated endpoints need a short-lived access token. You get one by signing a JWT — the client assertion — with your private key and exchanging it for a token. You then send that token on every request.
Your identity is proven by a signature only you can produce; the request itself carries no API key and no shared secret.
One-time setup
Generate an RSA key pair. Give AliX the public key during onboarding and keep the private key secret — it never leaves your server. Two OpenSSL commands produce the pair:
openssl genrsa -out partner-private.pem 2048
openssl rsa -in partner-private.pem -pubout -out partner-public.pem
partner-private.pem is the key that signs your client assertions — the samples on this page load
exactly this file. partner-public.pem is the half you give AliX at onboarding, and it is plain
PEM text:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA48/RNJOibAJlduffMtqy
…
uwIDAQAB
-----END PUBLIC KEY-----
Send it exactly as the file holds it, BEGIN line to END line.
AliX also allowlists the server IPs you call from. Requests from any other IP are rejected with
PARTNER_IP_NOT_ALLOWED (HTTP 403), no matter how valid the token is.
Step 1 — Build the client assertion
Sign a JWT with RS256 using your private key. Use a standard JWT library; do not hand-roll the encoding.
| Claim | Value |
|---|---|
iss | Your partner code. AliX picks your public key by this. |
aud | The literal string alixpay-v3-token. |
iat | Issue time, as a Unix timestamp in seconds. |
jti | A unique id for this assertion. Recommended — it lets AliX reject replays. |
exp | Optional short expiry, Unix seconds. |
The assertion is accepted only briefly after iat, so sign a fresh one for each token request
instead of caching it. A bad signature, a wrong aud, or a stale assertion returns
INVALID_SIGNATURE (HTTP 401).
Step 2 — Exchange it for a token
POST /v3/auth/tokens with the assertion and the email of the user you are acting for:
The data payload returns accessToken and expiresAt (Unix seconds).
Access tokens are valid for 60 minutes. When a token nears its expiresAt, repeat this handshake
for a new one — the exchange is the refresh.
See Issue an access token for the full contract and runnable code samples.
Step 3 — Call the API
Send the token on every request:
Authorization: Bearer <accessToken>
A missing, invalid, or expired token returns ACCESS_TOKEN_INVALID (HTTP 401).
One token, one user
A token names the user it acts for. Everything you do with it — KYC, quotes, orders — happens on behalf of that user. If you serve many customers, hold one token per customer, not one token for your platform.
Endpoints that need no token
- The token exchange itself,
POST /v3/auth/tokens. - QR decoding,
POST /v3/scan-to-pay/qr-decodings. - Service metadata,
GET /v3/public/version.
The IP allowlist still applies to all of them.
Example
import jwt from 'jsonwebtoken';
import {randomUUID} from 'node:crypto';
import {readFileSync} from 'node:fs';
const privateKey = readFileSync('partner-private.pem', 'utf8');
function clientAssertion() {
return jwt.sign(
{
iss: 'YOUR_PARTNER_CODE',
aud: 'alixpay-v3-token',
iat: Math.floor(Date.now() / 1000),
jti: randomUUID(),
},
privateKey,
{algorithm: 'RS256'},
);
}
export async function accessTokenFor(userEmail) {
const res = await fetch('https://sandbox.alixpay.com/v3/auth/tokens', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({clientAssertion: clientAssertion(), userEmail}),
});
const {code, message, data} = await res.json();
if (code !== 'SUCCESS') throw new Error(`${code}: ${message}`);
// Cache it against data.expiresAt (Unix seconds) — it lives 60 minutes.
return data.accessToken;
}