Skip to main content

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.

The signature proves your identity, so requests carry no API key or shared secret.

One-time setup​

Generate an RSA key pair. Give us the public key during onboarding and keep the private key secret — it never leaves your server. Two OpenSSL commands produce the pair:

Bash
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 us 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.

We also allowlist the server IPs you call from. Requests from other IPs return PARTNER_IP_NOT_ALLOWED (HTTP 403).

Step 1 — Build the client assertion​

Sign a JWT with RS256 using your private key. A standard JWT library handles the encoding, such as jsonwebtoken in the example below.

ClaimValue
issYour partner code. We use it to find your public key.
audThe literal string alixpay-v3-token.
iatIssue time, as a Unix timestamp in seconds.
jtiA unique id for this assertion. Recommended — it lets us reject replays.
expOptional 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:

JSON
{ "clientAssertion": "<signed JWT>", "userEmail": "[email protected]" }

The data payload returns accessToken and expiresAt (Unix seconds).

Access tokens are valid for 60 minutes. When a token nears its expiresAt, repeat the exchange to get a new one.

See Issue an access token for the full contract and runnable code samples.

Step 3 — Call the API​

Send the token on every request:

HTTP
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 IP allowlist still applies to all of them.

Example​

JavaScript
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;
}