The Trigger Order API V2 uses a challenge-response flow to authenticate users. Your wallet signs a challenge, and the server issues a JWT token valid for 24 hours.
Authentication Flow
1. Request challenge → POST /v2/auth/challenge
2. Sign with wallet → (client-side)
3. Submit signature → POST /v2/auth/verify → JWT token
4. Use JWT → Authorization: Bearer <token>
Prerequisites
Signing challenges and transactions requires @solana/web3.js and bs58:
npm install @solana/web3.js bs58
Step 1: Request a Challenge
Request a challenge for your wallet. Choose message for standard wallets or transaction for hardware wallets that only support transaction signing.
const challengeResponse = await fetch('https://api.jup.ag/trigger/v2/auth/challenge', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key',
},
body: JSON.stringify({
walletPubkey: walletAddress,
type: 'message', // or 'transaction' for hardware wallets
}),
});
const challenge = await challengeResponse.json();
Message challenge response:
{
"type": "message",
"challenge": "Sign this message to authenticate with Jupiter Trigger Order API..."
}
Transaction challenge response:
{
"type": "transaction",
"transaction": "Base64EncodedTransactionWithMemoInstruction..."
}
Challenges expire after 5 minutes. Request a new one if your challenge expires.
Step 2: Sign and Verify
Sign the challenge with your wallet, then submit the signature to receive a JWT token.
For message signing:
import bs58 from 'bs58';
// Sign the challenge message with your wallet
const encodedMessage = new TextEncoder().encode(challenge.challenge);
const signature = await wallet.signMessage(encodedMessage);
const verifyResponse = await fetch('https://api.jup.ag/trigger/v2/auth/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key',
},
body: JSON.stringify({
type: 'message',
walletPubkey: walletAddress,
signature: bs58.encode(signature),
}),
});
const { token } = await verifyResponse.json();
// Use this token in Authorization: Bearer <token> for all authenticated requests
For transaction signing (hardware wallets):
import { VersionedTransaction } from '@solana/web3.js';
const signedTx = await wallet.signTransaction(
VersionedTransaction.deserialize(Buffer.from(challenge.transaction, 'base64'))
);
const verifyResponse = await fetch('https://api.jup.ag/trigger/v2/auth/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key',
},
body: JSON.stringify({
type: 'transaction',
walletPubkey: walletAddress,
signedTransaction: Buffer.from(signedTx.serialize()).toString('base64'),
}),
});
const { token } = await verifyResponse.json();
Using the JWT
Include the JWT in all authenticated requests:
const response = await fetch('https://api.jup.ag/trigger/v2/vault', {
headers: {
'x-api-key': 'your-api-key',
'Authorization': `Bearer ${token}`,
},
});
Token Lifecycle
| Property | Value |
|---|
| Challenge TTL | 5 minutes |
| JWT TTL | 24 hours |
When a token expires, repeat the challenge-response flow to obtain a new one. There is no refresh endpoint.
Security Notes
For integrators building user-facing applications:
- Never store JWT tokens in local storage. Use secure, httpOnly cookies or in-memory storage
- Always verify the challenge content before signing. Do not blindly sign arbitrary messages
- Implement token refresh logic to handle expiration gracefully
- The JWT is tied to the wallet public key. Do not reuse tokens across wallets
What Happens if a JWT is Leaked
The JWT grants limited access. An attacker with a leaked token can:
- Cancel orders: This transitions the order from
open to ready_to_cancel, but does not withdraw funds. Withdrawal requires signing a transaction with the wallet private key.
- Edit order parameters: Updating trigger prices or slippage does not require transaction signing.
An attacker cannot:
- Withdraw funds: All withdrawal operations require the wallet owner to sign a transaction. The vault’s funds remain secure.
- Create new orders: Depositing tokens requires signing a deposit transaction with the wallet.
All operations involving funds (deposits, withdrawals) require the wallet owner to sign a transaction. A leaked JWT alone cannot result in loss of funds. However, if the wallet private key is also compromised, an attacker could sign transactions and withdraw funds from the vault.
Verify Token (Optional)
To check if a token is still valid without making an authenticated request:
const verifyTokenResponse = await fetch('https://api.jup.ag/trigger/v2/auth/verify-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
walletPubkey: walletAddress,
}),
});
// Returns 200 if valid, 401 if expired or invalid