Authentication
Authentication methods for Wirex BaaS API integration.
Before You Start
Read the following guides before proceeding:
| Guide | Why |
|---|---|
| Platform Overview | Credentials, environments, and onboarding requirements |
Overview
Wirex supports three authentication patterns:
| Pattern | Use Case | Token Source |
|---|---|---|
| Server-to-Server (S2S) | Backend operations without user context | OAuth2 client credentials |
| User Token Issuance | Direct client-to-server communication using user-scoped token | S2S obtained, user-scoped |
| Privy-based | Frontend/mobile apps with Privy wallet integration | Privy SDK token exchange |
1. Server-to-Server Authentication
S2S authentication is used when your backend needs to call Wirex APIs without a specific user context. Examples include user registration, system-level queries, and batch operations.
Token Exchange
POST /api/v1/token
Request
{
"client_id": "your-client-uuid",
"client_secret": "your-client-secret",
"grant_type": "client_credentials"
}Response
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_at": 1739612400
}Token Characteristics
| Property | Value |
|---|---|
| Algorithm | HMAC-SHA256 |
| Validity | 48 hours (172800 seconds) |
| Scope | partner:full |
Rate Limiting
The POST /api/v1/token endpoint has rate limiting:
- Per-IP limit
- Per-client limit
Implement exponential backoff on 429 responses.
Token Claims
The issued JWT contains:
{
"iss": "https://api-baas.wirexapp.com/srv",
"sub": "your-client-uuid",
"azp": "your-client-uuid",
"company_id": "your-company-uuid",
"scope": "partner:full",
"iat": 1739525600,
"exp": 1739612400
}Usage
Include the token in the Authorization header:
Authorization: Bearer <access_token>
JavaScript
class WirexAuthClient {
constructor(clientId, clientSecret, baseUrl) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.baseUrl = baseUrl;
this.token = null;
this.expiresAt = 0;
}
async getToken() {
// Return cached token if still valid (with 5 minute buffer)
if (this.token && Date.now() / 1000 < this.expiresAt - 300) {
return this.token;
}
const response = await fetch(`${this.baseUrl}/api/v1/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: this.clientId,
client_secret: this.clientSecret,
grant_type: 'client_credentials',
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Token exchange failed: ${error.description}`);
}
const data = await response.json();
this.token = data.access_token;
this.expiresAt = data.expires_at;
return this.token;
}
}
// Usage
const auth = new WirexAuthClient(
'your-client-uuid',
'your-client-secret',
'https://api-baas.wirexapp.com'
);
const token = await auth.getToken();Python
import time
import requests
class WirexAuthClient:
def __init__(self, client_id: str, client_secret: str, base_url: str):
self.client_id = client_id
self.client_secret = client_secret
self.base_url = base_url
self._token = None
self._expires_at = 0
def get_token(self) -> str:
# Return cached token if still valid (with 5 minute buffer)
if self._token and time.time() < self._expires_at - 300:
return self._token
response = requests.post(
f"{self.base_url}/api/v1/token",
json={
"client_id": self.client_id,
"client_secret": self.client_secret,
"grant_type": "client_credentials",
},
)
response.raise_for_status()
data = response.json()
self._token = data["access_token"]
self._expires_at = data["expires_at"]
return self._token
auth = WirexAuthClient(
"your-client-uuid",
"your-client-secret",
"https://api-baas.wirexapp.com"
)
token = auth.get_token()Go
package wirex
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"sync"
"time"
)
type AuthClient struct {
clientID string
clientSecret string
baseURL string
token string
expiresAt int64
mu sync.RWMutex
}
func NewAuthClient(clientID, clientSecret, baseURL string) *AuthClient {
return &AuthClient{
clientID: clientID,
clientSecret: clientSecret,
baseURL: baseURL,
}
}
func (c *AuthClient) GetToken() (string, error) {
c.mu.RLock()
// Return cached token if still valid (with 5 minute buffer)
if c.token != "" && time.Now().Unix() < c.expiresAt-300 {
token := c.token
c.mu.RUnlock()
return token, nil
}
c.mu.RUnlock()
c.mu.Lock()
defer c.mu.Unlock()
// Double-check after acquiring write lock
if c.token != "" && time.Now().Unix() < c.expiresAt-300 {
return c.token, nil
}
reqBody, _ := json.Marshal(map[string]string{
"client_id": c.clientID,
"client_secret": c.clientSecret,
"grant_type": "client_credentials",
})
resp, err := http.Post(
c.baseURL+"/api/v1/token",
"application/json",
bytes.NewBuffer(reqBody),
)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("token exchange failed: %d", resp.StatusCode)
}
var tokenResp struct {
AccessToken string `json:"access_token"`
ExpiresAt int64 `json:"expires_at"`
}
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
return "", err
}
c.token = tokenResp.AccessToken
c.expiresAt = tokenResp.ExpiresAt
return c.token, nil
}User Identity Headers
When using S2S authentication, most endpoints require user identity headers to specify which user the request is for:
| Header | Description |
|---|---|
X-User-Address | User's EOA address |
X-User-Email | User's email address |
X-User-Id | User's UUID |
You must provide at least one of these headers. For endpoints that support multiple identifiers, X-User-Id takes precedence.
User-Agnostic Endpoints
The following endpoints do not require user identity headers (X-User-Address, X-User-Email, X-User-Id):
| Endpoint | Description |
|---|---|
POST /api/v1/token | S2S token exchange |
POST /api/v1/user | User registration (V1) |
POST /api/v2/user | User registration (V2) |
GET /api/v1/config | Application configuration |
GET /api/v1/validation/rules | Validation rules |
For all other endpoints, user identity headers are mandatory.
2. User Token Issuance (Login as User)
This flow allows you to obtain a user-specific token from your backend, then transfer it to a client application. The client application uses this token for direct API calls without exposing your credentials.
How It Works
- Your backend authenticates with S2S credentials
- Your backend calls
POST /api/v1/user/authorizewith user identity headers - Wirex returns a user-specific access token
- You transfer this token to the client application
- Client application makes direct API calls using the user token
Login as User Endpoint
POST /api/v1/user/authorize
Required Headers
| Header | Description |
|---|---|
Authorization | Bearer <s2s_token> from S2S auth |
X-User-Address | User's EOA address (not the Smart Wallet address) |
X-Chain-Id | Target blockchain ID (137=Polygon, 8453=Base) |
Response
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"expires_at": 1739612400
}Token Flow
Your Backend Wirex API Client App
| | |
| 1. S2S Token Exchange | |
|------------------------->| |
|<-------------------------| |
| { s2s_token } | |
| | |
| 2. POST /user/authorize | |
| + X-User-Address | |
|------------------------->| |
| | |
|<-------------------------| |
| { user_token } | |
| | |
| 3. Transfer token | |
|-----------------------------------------------> |
| | |
| | 4. Direct API calls |
| |<---------------------------|
| | Authorization: Bearer |
| | <user_token> |
| |--------------------------->|
| | |
JavaScript
class WirexClient {
constructor(authClient, baseUrl) {
this.authClient = authClient;
this.baseUrl = baseUrl;
}
// Get user-specific token (call from your backend)
async getUserToken(userAddress, chainId) {
const s2sToken = await this.authClient.getToken();
const response = await fetch(`${this.baseUrl}/api/v1/user/authorize`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${s2sToken}`,
'Content-Type': 'application/json',
'X-User-Address': userAddress,
'X-Chain-Id': chainId.toString(),
},
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Login as user failed: ${error.description}`);
}
return response.json();
}
}
// Backend usage
const auth = new WirexAuthClient(clientId, clientSecret, baseUrl);
const client = new WirexClient(auth, baseUrl);
// Get user token
const { access_token, expires_at } = await client.getUserToken(
'0xA7E41d5680dE394EaA2ed417169DFf56840Fb3EE',
137
);
// Transfer access_token to client application// Client application usage (after receiving token from backend)
class WirexUserClient {
constructor(userToken, baseUrl, chainId) {
this.userToken = userToken;
this.baseUrl = baseUrl;
this.chainId = chainId;
}
async request(method, path, body = null) {
const headers = {
'Authorization': `Bearer ${this.userToken}`,
'Content-Type': 'application/json',
'X-Chain-Id': this.chainId.toString(),
};
const options = { method, headers };
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(`${this.baseUrl}${path}`, options);
if (!response.ok) {
const error = await response.json();
throw new Error(`Request failed: ${error.description}`);
}
return response.json();
}
getUser() {
return this.request('GET', '/api/v1/user');
}
getCards() {
return this.request('GET', '/api/v1/cards');
}
getWallet() {
return this.request('GET', '/api/v1/wallet');
}
}
// Client app receives token from backend
const userClient = new WirexUserClient(access_token, baseUrl, 137);
const user = await userClient.getUser();
const cards = await userClient.getCards();Python
class WirexClient:
def __init__(self, auth_client: WirexAuthClient, base_url: str):
self.auth_client = auth_client
self.base_url = base_url
def get_user_token(self, user_address: str, chain_id: int) -> dict:
"""Get user-specific token (call from your backend)"""
s2s_token = self.auth_client.get_token()
response = requests.post(
f"{self.base_url}/api/v1/user/authorize",
headers={
"Authorization": f"Bearer {s2s_token}",
"Content-Type": "application/json",
"X-User-Address": user_address,
"X-Chain-Id": str(chain_id),
},
)
response.raise_for_status()
return response.json()
auth = WirexAuthClient(client_id, client_secret, base_url)
client = WirexClient(auth, base_url)
token_data = client.get_user_token(
"0xA7E41d5680dE394EaA2ed417169DFf56840Fb3EE",
137
)class WirexUserClient:
"""Client application usage (after receiving token from backend)"""
def __init__(self, user_token: str, base_url: str, chain_id: int):
self.user_token = user_token
self.base_url = base_url
self.chain_id = chain_id
def _request(self, method: str, path: str, body: dict = None) -> dict:
response = requests.request(
method,
f"{self.base_url}{path}",
headers={
"Authorization": f"Bearer {self.user_token}",
"Content-Type": "application/json",
"X-Chain-Id": str(self.chain_id),
},
json=body,
)
response.raise_for_status()
return response.json()
def get_user(self) -> dict:
return self._request("GET", "/api/v1/user")
def get_cards(self) -> dict:
return self._request("GET", "/api/v1/cards")
def get_wallet(self) -> dict:
return self._request("GET", "/api/v1/wallet")
user_client = WirexUserClient(access_token, base_url, 137)
user = user_client.get_user()
cards = user_client.get_cards()Go
type Client struct {
auth *AuthClient
baseURL string
http *http.Client
}
func NewClient(auth *AuthClient, baseURL string) *Client {
return &Client{
auth: auth,
baseURL: baseURL,
http: &http.Client{Timeout: 30 * time.Second},
}
}
type UserTokenResponse struct {
AccessToken string `json:"access_token"`
ExpiresAt int64 `json:"expires_at"`
}
// GetUserToken obtains a user-specific token (call from your backend)
func (c *Client) GetUserToken(userAddress string, chainID int) (*UserTokenResponse, error) {
s2sToken, err := c.auth.GetToken()
if err != nil {
return nil, err
}
req, _ := http.NewRequest("POST", c.baseURL+"/api/v1/user/authorize", nil)
req.Header.Set("Authorization", "Bearer "+s2sToken)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-Address", userAddress)
req.Header.Set("X-Chain-Id", fmt.Sprintf("%d", chainID))
resp, err := c.http.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("login as user failed: %d", resp.StatusCode)
}
var tokenResp UserTokenResponse
json.NewDecoder(resp.Body).Decode(&tokenResp)
return &tokenResp, nil
}// UserClient for client application (after receiving token from backend)
type UserClient struct {
userToken string
baseURL string
chainID int
http *http.Client
}
func NewUserClient(userToken, baseURL string, chainID int) *UserClient {
return &UserClient{
userToken: userToken,
baseURL: baseURL,
chainID: chainID,
http: &http.Client{Timeout: 30 * time.Second},
}
}
func (c *UserClient) request(method, path string, body interface{}) ([]byte, error) {
var reqBody io.Reader
if body != nil {
jsonBody, _ := json.Marshal(body)
reqBody = bytes.NewBuffer(jsonBody)
}
req, _ := http.NewRequest(method, c.baseURL+path, reqBody)
req.Header.Set("Authorization", "Bearer "+c.userToken)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Chain-Id", fmt.Sprintf("%d", c.chainID))
resp, err := c.http.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
func (c *UserClient) GetUser() ([]byte, error) {
return c.request("GET", "/api/v1/user", nil)
}
func (c *UserClient) GetCards() ([]byte, error) {
return c.request("GET", "/api/v1/cards", nil)
}3. Privy-Based Authentication
Privy-based authentication is used when your frontend or mobile application integrates Privy SDK for wallet management. Privy tokens are exchanged for Wirex-issued tokens, which are then used for all subsequent API calls.
Prerequisites
- Set up a Privy application at privy.io
- Provide your Privy App ID to Wirex during onboarding
- Wirex configures the Privy App ID in your company credentials
How It Works
- User authenticates via Privy SDK in your application
- Privy issues an access token and identity token
- Your app calls
POST /api/v1/user/authorizewith Privy tokens - Wirex validates Privy tokens against Privy's JWKS endpoint
- Wirex returns a Wirex-issued access token
- Your app stores the Wirex token and uses it for all subsequent API calls
Token Exchange Headers
When exchanging Privy tokens for a Wirex token:
| Header | Description |
|---|---|
Authorization | Bearer <privy_access_token> |
Identity | Bearer <privy_identity_token> |
X-Chain-Id | Target blockchain ID |
Token Exchange Endpoint
POST /api/v1/user/authorize
Response
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"expires_at": 1739612400
}Token Flow
User Your App Privy Wirex API
| | | |
| 1. Login | | |
|----------------------->| | |
| | 2. Authenticate | |
| |---------------------->| |
| | | |
| |<----------------------| |
| | privy_access_token + | |
| | privy_identity_token | |
| | | |
| | 3. POST /user/authorize |
| |------------------------------------------------->|
| | Authorization: Bearer <privy_access_token> |
| | Identity: Bearer <privy_identity_token> |
| | | |
| | | 4. Verify Privy tokens |
| | |<------------------------|
| | |------------------------>|
| | | |
| |<-------------------------------------------------|
| | { access_token, expires_at } |
| | (Wirex token) | |
| | | |
| | 5. Store Wirex token | |
| | | |
| | 6. Subsequent API calls with Wirex token |
| |------------------------------------------------->|
| | Authorization: Bearer <wirex_token> |
| | | |
|<-----------------------| | |
JavaScript (React with Privy SDK)
import { usePrivy } from '@privy-io/react-auth';
import { useState, useEffect } from 'react';
// Token manager for storing Wirex token
const tokenManager = {
token: null,
expiresAt: 0,
setToken(accessToken, expiresAt) {
this.token = accessToken;
this.expiresAt = expiresAt;
localStorage.setItem('wirex_token', JSON.stringify({ accessToken, expiresAt }));
},
getToken() {
if (this.token && Date.now() / 1000 < this.expiresAt - 300) {
return this.token;
}
const stored = localStorage.getItem('wirex_token');
if (stored) {
const { accessToken, expiresAt } = JSON.parse(stored);
if (Date.now() / 1000 < expiresAt - 300) {
this.token = accessToken;
this.expiresAt = expiresAt;
return accessToken;
}
}
return null;
},
clear() {
this.token = null;
this.expiresAt = 0;
localStorage.removeItem('wirex_token');
}
};
// Hook for token exchange and API calls
function useWirexAuth() {
const { getAccessToken, getIdentityToken } = usePrivy();
const baseUrl = 'https://api-baas.wirexapp.com';
const chainId = 137;
// Exchange Privy tokens for Wirex token
async function authenticate() {
const existingToken = tokenManager.getToken();
if (existingToken) {
return existingToken;
}
const privyAccessToken = await getAccessToken();
const privyIdentityToken = await getIdentityToken();
const response = await fetch(`${baseUrl}/api/v1/user/authorize`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${privyAccessToken}`,
'Identity': `Bearer ${privyIdentityToken}`,
'Content-Type': 'application/json',
'X-Chain-Id': chainId.toString(),
},
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.description);
}
const { access_token, expires_at } = await response.json();
tokenManager.setToken(access_token, expires_at);
return access_token;
}
// Make authenticated API request using Wirex token
async function request(method, path, body = null) {
const wirexToken = await authenticate();
const headers = {
'Authorization': `Bearer ${wirexToken}`,
'Content-Type': 'application/json',
'X-Chain-Id': chainId.toString(),
};
const options = { method, headers };
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(`${baseUrl}${path}`, options);
if (!response.ok) {
const error = await response.json();
throw new Error(error.description);
}
return response.json();
}
return {
authenticate,
getUser: () => request('GET', '/api/v1/user'),
getWallet: () => request('GET', '/api/v1/wallet'),
getCards: () => request('GET', '/api/v1/cards'),
createVirtualCard: () => request('POST', '/api/v1/cards/virtual'),
};
}
// Usage in component
function Dashboard() {
const { getUser } = useWirexAuth();
const [user, setUser] = useState(null);
useEffect(() => {
getUser().then(setUser);
}, []);
return <div>Welcome, {user?.email}</div>;
}User Registration with Privy
For new users authenticating via Privy, use the retail registration endpoint first:
POST /api/v1/user/retail
This endpoint also exchanges Privy tokens and returns a Wirex token.
Request Headers
| Header | Description |
|---|---|
Authorization | Bearer <privy_access_token> |
Identity | Bearer <privy_identity_token> |
X-Chain-Id | Target blockchain ID |
Request Body
{
"country": "US"
}The user's email and wallet address are extracted from the Privy tokens.
Response
{
"id": "user-uuid"
}After registration, call POST /api/v1/user/authorize to obtain the Wirex token for subsequent API calls.
JavaScript
async function registerAndAuthenticate(country) {
const privyAccessToken = await getAccessToken();
const privyIdentityToken = await getIdentityToken();
const baseUrl = 'https://api-baas.wirexapp.com';
const chainId = '137';
// Step 1: Register user
const registerResponse = await fetch(`${baseUrl}/api/v1/user/retail`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${privyAccessToken}`,
'Identity': `Bearer ${privyIdentityToken}`,
'Content-Type': 'application/json',
'X-Chain-Id': chainId,
},
body: JSON.stringify({ country }),
});
if (!registerResponse.ok) {
const error = await registerResponse.json();
throw new Error(error.description);
}
const { id: userId } = await registerResponse.json();
// Step 2: Exchange Privy tokens for Wirex token
const authResponse = await fetch(`${baseUrl}/api/v1/user/authorize`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${privyAccessToken}`,
'Identity': `Bearer ${privyIdentityToken}`,
'Content-Type': 'application/json',
'X-Chain-Id': chainId,
},
});
if (!authResponse.ok) {
const error = await authResponse.json();
throw new Error(error.description);
}
const { access_token, expires_at } = await authResponse.json();
// Step 3: Store Wirex token for subsequent API calls
tokenManager.setToken(access_token, expires_at);
return { userId, accessToken: access_token };
}Privy Token Claims
Wirex extracts the following from Privy tokens:
| Claim | Source | Description |
|---|---|---|
sub | Access token | Privy user ID |
aud | Access token | Privy App ID (must match configured) |
linked_accounts | Identity token | Array of linked wallets |
The linked_accounts claim contains wallet information:
{
"linked_accounts": [
{
"type": "smart_wallet",
"address": "0xA7E41d5680dE394EaA2ed417169DFf56840Fb3EE",
"chain_type": "ethereum"
}
]
}4. Security Best Practices
Credential Storage
- Store
client_secretin a secrets manager (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager) - Never commit credentials to source control
- Never expose credentials in client-side code or logs
- Rotate credentials immediately if compromised
Token Handling
- Cache tokens until near expiration (recommended: refresh 5 minutes before expiry)
- Use secure storage for tokens (encrypted at rest)
- Never log full tokens; truncate for debugging
- Implement token refresh with retry logic
Request Security
- Always use HTTPS (TLS 1.2+)
- Validate SSL certificates
- Implement request timeouts (recommended: 30 seconds)
- Use idempotency keys for mutation requests where supported
5. Error Responses
Error Response Format
{
"error_reason": "ErrorPermissionDenied",
"error_description": "Invalid client credentials",
"error_category": {
"category": "CategoryUnauthorized",
"http_status_code": 401
},
"error_details": [
{"key": "field", "details": "client_secret"}
]
}Error Categories
| Category | HTTP Status | Description |
|---|---|---|
CategoryValidationFailure | 400 | Invalid request data, missing fields, format errors |
CategoryUnauthorized | 401 | Authentication or authorization failure |
CategoryInternalFailure | 500 | Server configuration or internal errors |
CategoryTransientFailure | 429 | Rate limiting |
S2S Token Exchange Errors (POST /api/v1/token)
POST /api/v1/token)| HTTP | Reason | Message | Cause |
|---|---|---|---|
| 400 | ErrorMissingField | "client id is required" | Missing client_id in request |
| 400 | ErrorMissingField | "client secret is required" | Missing client_secret in request |
| 400 | ErrorInvalidField | "Invalid value for grant type" | grant_type is not client_credentials |
| 401 | ErrorPermissionDenied | "Invalid client credentials" | Unknown client_id |
| 401 | ErrorPermissionDenied | "Invalid client credentials" | Incorrect client_secret |
| 401 | ErrorPermissionDenied | "Client credentials access is not enabled for this account" | No secret configured for client |
| 401 | ErrorNotSupported | "Client credentials are not supported for this company type" | Company type restriction |
S2S Request Errors (Using S2S Token)
| HTTP | Reason | Message | Cause |
|---|---|---|---|
| 400 | ErrorMissingField | "Missing Authorization Headers" | No Authorization header |
| 400 | ErrorInvalidField | "Bearer token is not in the correct format" | Token not in Bearer <token> format |
| 401 | ErrorExpired | "Token is expired" | Token exp claim exceeded |
| 401 | ErrorPermissionDenied | "Token signature is not valid" | Invalid HMAC signature |
| 401 | ErrorMissingField | "Client id claim is missing" | Missing azp claim in token |
| 401 | ErrorMissingField | "Company id claim is missing" | Missing company_id claim |
| 400 | ErrorMissingField | "X-Chain-Id header is missing" | Required header not provided |
| 400 | ErrorMissingField | "No valid user identifier provided" | Missing X-User-Address, X-User-Email, or X-User-Id |
| 400 | ErrorInvalidField | "Multiple user identifiers provided in headers. Only one is allowed" | More than one user identifier header |
| 400 | ErrorNotFound | "Requested user was not found" | User does not exist or not in your company |
Privy Token Exchange Errors
| HTTP | Reason | Message | Cause |
|---|---|---|---|
| 400 | ErrorMissingField | "Missing Authorization Headers" | No Authorization header |
| 400 | ErrorMissingField | "Missing Identity Headers" | No Identity header |
| 401 | ErrorExpired | "Access token is expired" | Privy access token expired |
| 401 | ErrorExpired | "Id token is expired" | Privy identity token expired |
| 401 | ErrorPermissionDenied | "Access token signature is not valid" | Invalid Privy signature |
| 401 | ErrorPermissionDenied | "Id token signature is not valid" | Invalid Privy signature |
| 401 | ErrorMissingField | "Linked accounts claim is missing" | No wallet linked in Privy |
| 400 | ErrorMissingField | "Not all identifiers found in linked accounts" | Email or wallet not in Privy account |
| 500 | ErrorConfigurationInvalid | "Client application not registered" | Privy App ID not configured |
User Token Errors (Using Wirex User Token)
| HTTP | Reason | Message | Cause |
|---|---|---|---|
| 401 | ErrorExpired | "Token is expired" | User token expired |
| 401 | ErrorPermissionDenied | "Token signature is not valid" | Invalid signature |
| 401 | ErrorMissingField | "User id claim is missing" | Malformed token |
| 401 | ErrorMissingField | "User email claim is missing" | Malformed token |
| 401 | ErrorMissingField | "User address claim is missing" | Malformed token |
Updated 8 days ago
