Introduction
WhiteBIT OAuth 2.0 implementation uses the standard Authorization Code Grant flow. This flow is suitable for server-side applications where the client secret can be securely stored. The OAuth 2.0 endpoints documented on this page cover the Authorization Code Grant flow for read access to account data. For partner-issued API keys, a separate OAuth API key flow uses Authorization Code with PKCE (S256), a 4-hour access token, and no refresh token. See the Fast API Key integration guide for the full integration.package main
import (
"bytes"
"context"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"time"
)
// TokenResponse represents the OAuth token response
type TokenResponse struct {
Data struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
Scope string `json:"scope"`
TokenType string `json:"token_type"`
} `json:"data"`
}
const BASE_URL = "https://whitebit.com"
const CLIENT_ID = "YOUR_CLIENT_ID"
const CLIENT_SECRET = "YOUR_CLIENT_SECRET"
// Initiate OAuth flow
func initiateOAuthFlowHandler(w http.ResponseWriter, r *http.Request) {
b := make([]byte, 32)
rand.Read(b)
state := base64.RawURLEncoding.EncodeToString(b)
// In production, store state in session
authURL, _ := url.Parse(BASE_URL + "/auth/login")
q := authURL.Query()
q.Set("clientId", CLIENT_ID)
q.Set("state", state)
authURL.RawQuery = q.Encode()
http.Redirect(w, r, authURL.String(), http.StatusFound)
}
// Handle OAuth callback
func oauthCallbackHandler(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
// Verify state here...
token, err := exchangeCodeForToken(r.Context(), code)
if err != nil {
http.Error(w, "Authentication failed", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "Token received: %s", token.Data.AccessToken)
}
func exchangeCodeForToken(ctx context.Context, code string) (*TokenResponse, error) {
data := url.Values{}
data.Set("client_id", CLIENT_ID)
data.Set("client_secret", CLIENT_SECRET)
data.Set("code", code)
resp, err := http.PostForm(BASE_URL+"/oauth2/token", data)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var tokenResponse TokenResponse
json.NewDecoder(resp.Body).Decode(&tokenResponse)
return &tokenResponse, nil
}
import http.server
import http.cookies
import json
import secrets
import socketserver
import time
import urllib.error
import urllib.parse
import urllib.request
from typing import Dict, Any, Optional, Tuple
# Region-aware base URL - set based on the region:
# Global: "https://whitebit.com"
# EU: "https://whitebit.eu"
BASE_URL = "https://whitebit.com"
# Simple in-memory session store (use Redis or similar for production)
sessions: Dict[str, Dict[str, Any]] = {}
class OAuthRequestHandler(http.server.BaseHTTPRequestHandler):
"""HTTP request handler for OAuth flow"""
def do_GET(self):
"""Handle GET requests"""
parsed_path = urllib.parse.urlparse(self.path)
path = parsed_path.path
if path == '/auth/login':
self._handle_login()
elif path == '/auth/callback':
self._handle_callback()
else:
self._send_response(404, b'Not Found')
def _handle_login(self):
"""Handle OAuth login initiation"""
# Generate secure state and session ID
state = generate_secure_token(32)
session_id = generate_secure_token(16)
# Create session with state and expiry
sessions[session_id] = {
'oauth_state': state,
'oauth_state_expiry': time.time() + 600 # 10 minutes
}
# Set session cookie
cookie = http.cookies.SimpleCookie()
cookie['session_id'] = session_id
cookie['session_id']['path'] = '/'
cookie['session_id']['httponly'] = True
cookie['session_id']['samesite'] = 'Lax'
cookie['session_id']['max-age'] = 600
if self.headers.get('X-Forwarded-Proto') == 'https':
cookie['session_id']['secure'] = True
# Build redirect URL
redirect_url = (
f"{BASE_URL}/auth/login?"
f"clientId={urllib.parse.quote('YOUR_CLIENT_ID')}&"
f"state={urllib.parse.quote(state)}"
)
# Send redirect response
self.send_response(302)
self.send_header('Location', redirect_url)
self.send_header('Set-Cookie', cookie.output(header=''))
self.end_headers()
def _handle_callback(self):
"""Handle OAuth callback"""
# Parse query parameters
parsed_path = urllib.parse.urlparse(self.path)
query_params = urllib.parse.parse_qs(parsed_path.query)
# Get received state
received_state = query_params.get('state', [''])[0]
if not received_state:
self._send_response(400, b'Missing state parameter')
return
# Get session ID from cookie
session_id = self._get_session_id_from_cookie()
if not session_id or session_id not in sessions:
self._send_response(400, b'Invalid session')
return
# Get session data
session = sessions[session_id]
# Verify state expiry
expiry_time = session.get('oauth_state_expiry', 0)
if time.time() > expiry_time:
del sessions[session_id]
self._send_response(400, b'State expired')
return
# Get stored state
stored_state = session.get('oauth_state', '')
# Clear stored state
session.pop('oauth_state', None)
session.pop('oauth_state_expiry', None)
# Verify state
if not stored_state or stored_state != received_state:
self._send_response(400, b'State validation failed')
return
# Get authorization code
code = query_params.get('code', [''])[0]
if not code:
self._send_response(400, b'Missing authorization code')
return
try:
# Exchange code for token
token_response = exchange_code_for_token(code)
# Store tokens in session
session['access_token'] = token_response['access_token']
session['refresh_token'] = token_response['refresh_token']
session['token_expires_at'] = time.time() + token_response['expires_in']
# Redirect to dashboard
self.send_response(302)
self.send_header('Location', '/dashboard')
self.end_headers()
except Exception as e:
print(f"Token exchange failed: {e}")
self._send_response(500, b'Authentication failed')
def _get_session_id_from_cookie(self) -> Optional[str]:
"""Extract session ID from cookies"""
cookie_str = self.headers.get('Cookie')
if not cookie_str:
return None
cookie = http.cookies.SimpleCookie()
cookie.load(cookie_str)
if 'session_id' not in cookie:
return None
return cookie['session_id'].value
def _send_response(self, status_code: int, content: bytes):
"""Send HTTP response with content"""
self.send_response(status_code)
self.send_header('Content-Type', 'text/plain')
self.send_header('Content-Length', str(len(content)))
self.end_headers()
self.wfile.write(content)
def generate_secure_token(length: int) -> str:
"""Generate a cryptographically secure random token"""
return secrets.token_urlsafe(length)
def exchange_code_for_token(code: str) -> Dict[str, Any]:
"""Exchange authorization code for access token"""
# Prepare request data
data = urllib.parse.urlencode({
'client_id': 'YOUR_CLIENT_ID',
'client_secret': 'YOUR_CLIENT_SECRET',
'code': code
}).encode('ascii')
# Prepare request
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
}
# Create request
req = urllib.request.Request(
f'{BASE_URL}/oauth2/token',
data=data,
headers=headers,
method='POST'
)
try:
# Send request and get response
with urllib.request.urlopen(req, timeout=10) as response:
response_data = response.read()
response_json = json.loads(response_data)
# Extract token data
return {
'access_token': response_json['data']['access_token'],
'refresh_token': response_json['data']['refresh_token'],
'expires_in': response_json['data']['expires_in'],
'token_type': response_json['data']['token_type'],
'scope': response_json['data']['scope']
}
except urllib.error.HTTPError as e:
print(f"HTTP error: {e.code} - {e.reason}")
raise
except urllib.error.URLError as e:
print(f"URL error: {e.reason}")
raise
except Exception as e:
print(f"Unexpected error: {e}")
raise
def main():
"""Run the server"""
port = 3000
handler = OAuthRequestHandler
with socketserver.TCPServer(("", port), handler) as httpd:
print(f"Server running on port {port}")
httpd.serve_forever()
if __name__ == "__main__":
main()
import crypto from 'crypto';
import http from 'http';
import https from 'https';
import { URL, URLSearchParams } from 'url';
import { parse as parseUrl } from 'url';
import { parse as parseQueryString } from 'querystring';
// Region-aware base URL - set based on the region:
// Global: "https://whitebit.com"
// EU: "https://whitebit.eu"
const BASE_URL = "https://whitebit.com";
// Simple in-memory session store (for production use a proper store)
const sessions = new Map<string, Record<string, any>>();
// Generate a secure random state value
function generateSecureState(): string {
return crypto.randomBytes(32)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
// Generate session ID
function generateSessionId(): string {
return crypto.randomBytes(16).toString('hex');
}
// Create HTTP server
const server = http.createServer((req, res) => {
// Get or create session
let sessionId = '';
const cookies = req.headers.cookie?.split(';').map(c => c.trim());
const sessionCookie = cookies?.find(c => c.startsWith('sessionId='));
if (sessionCookie) {
sessionId = sessionCookie.split('=')[1];
if (!sessions.has(sessionId)) {
sessionId = '';
}
}
if (!sessionId) {
sessionId = generateSessionId();
res.setHeader('Set-Cookie', `sessionId=${sessionId}; HttpOnly; Path=/; Max-Age=600`);
sessions.set(sessionId, {});
}
const session = sessions.get(sessionId) || {};
// Parse URL and path
const parsedUrl = parseUrl(req.url || '');
const pathname = parsedUrl.pathname || '';
// Handle routes
if (pathname === '/auth/login') {
// Initiate OAuth flow
const state = generateSecureState();
session.oauth_state = state;
res.writeHead(302, {
'Location': `${BASE_URL}/auth/login?clientId=YOUR_CLIENT_ID&state=${state}`
});
res.end();
}
else if (pathname === '/auth/callback') {
// Handle OAuth callback
const query = parseQueryString(parsedUrl.query || '');
const receivedState = query.state as string;
const storedState = session.oauth_state;
// Clear the stored state immediately
delete session.oauth_state;
// Verify the state parameter
if (!receivedState || receivedState !== storedState) {
res.writeHead(400);
res.end('State validation failed');
return;
}
// State is valid, proceed with code exchange
const code = query.code as string;
if (code) {
// Exchange code for token
exchangeCodeForToken(code, sessionId, res);
} else {
res.writeHead(400);
res.end('Missing authorization code');
}
}
else {
// Handle other routes or 404
res.writeHead(404);
res.end('Not found');
}
});
// Exchange the authorization code for access token
function exchangeCodeForToken(
code: string,
sessionId: string,
res: http.ServerResponse
): void {
const params = new URLSearchParams({
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET',
code: code
}).toString();
// Construct full URL and parse for https.request()
const tokenUrl = new URL(`${BASE_URL}/oauth2/token`);
const options = {
hostname: tokenUrl.hostname,
port: tokenUrl.port ? parseInt(tokenUrl.port, 10) : 443,
path: tokenUrl.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(params)
}
};
const request = https.request(options, (tokenRes) => {
let data = '';
tokenRes.on('data', (chunk) => {
data += chunk;
});
tokenRes.on('end', () => {
try {
const tokenData = JSON.parse(data);
// Store tokens in session
const session = sessions.get(sessionId);
if (session) {
session.access_token = tokenData.data.access_token;
session.refresh_token = tokenData.data.refresh_token;
}
// Redirect to dashboard
res.writeHead(302, {
'Location': '/dashboard'
});
res.end();
} catch (error) {
console.error('Failed to parse token response:', error);
res.writeHead(500);
res.end('Authentication failed');
}
});
});
request.on('error', (error) => {
console.error('Token exchange failed:', error);
res.writeHead(500);
res.end('Authentication failed');
});
// Write data to request body
request.write(params);
request.end();
}
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
public class OAuthServer {
// Region-aware base URL - set based on the region:
// Global: "https://whitebit.com"
// EU: "https://whitebit.eu"
private static final String BASE_URL = "https://whitebit.com";
// Simple in-memory session store (use Redis or similar for production)
private static final Map<String, Map<String, Object>> sessions = new ConcurrentHashMap<>();
private static final SecureRandom secureRandom = new SecureRandom();
private static final Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
public static void main(String[] args) throws IOException {
// Create HTTP server
HttpServer server = HttpServer.create(new InetSocketAddress(3000), 0);
// Register handlers
server.createContext("/auth/login", new InitiateOAuthFlowHandler());
server.createContext("/auth/callback", new OAuthCallbackHandler());
// Start server
server.setExecutor(null);
server.start();
System.out.println("Server started on port 3000");
}
/**
* Generate a cryptographically secure random string
*/
private static String generateSecureToken(int byteLength) {
byte[] bytes = new byte[byteLength];
secureRandom.nextBytes(bytes);
return encoder.encodeToString(bytes);
}
/**
* Handler for initiating OAuth flow
*/
static class InitiateOAuthFlowHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
// Generate secure state and session ID
String state = generateSecureToken(32);
String sessionId = generateSecureToken(16);
// Store state in session with expiry
Map<String, Object> sessionData = new HashMap<>();
sessionData.put("oauth_state", state);
sessionData.put("oauth_state_expiry", Instant.now().plusSeconds(600).getEpochSecond());
sessions.put(sessionId, sessionData);
// Set session cookie
String cookie = String.format(
"session_id=%s; Path=/; HttpOnly; SameSite=Lax; Max-Age=600", sessionId);
if (exchange.getRequestHeaders().containsKey("X-Forwarded-Proto") &&
exchange.getRequestHeaders().getFirst("X-Forwarded-Proto").equals("https")) {
cookie += "; Secure";
}
exchange.getResponseHeaders().add("Set-Cookie", cookie);
// Build authorization URL
String redirectUrl = String.format(
BASE_URL + "/auth/login?clientId=%s&state=%s",
URLEncoder.encode("YOUR_CLIENT_ID", StandardCharsets.UTF_8),
URLEncoder.encode(state, StandardCharsets.UTF_8)
);
// Redirect to authorization endpoint
exchange.getResponseHeaders().add("Location", redirectUrl);
exchange.sendResponseHeaders(302, -1);
exchange.close();
}
}
/**
* Handler for OAuth callback
*/
static class OAuthCallbackHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
try {
// Parse query parameters
String query = exchange.getRequestURI().getQuery();
Map<String, String> queryParams = parseQueryString(query);
// Get received state
String receivedState = queryParams.get("state");
if (receivedState == null || receivedState.isEmpty()) {
sendErrorResponse(exchange, 400, "Missing state parameter");
return;
}
// Get session ID from cookie
String sessionId = getSessionIdFromCookie(exchange);
if (sessionId == null) {
sendErrorResponse(exchange, 400, "Invalid session");
return;
}
// Get session data
Map<String, Object> session = sessions.get(sessionId);
if (session == null) {
sendErrorResponse(exchange, 400, "Session not found");
return;
}
// Verify state expiry
Long expiryTime = (Long) session.get("oauth_state_expiry");
if (expiryTime == null || Instant.now().getEpochSecond() > expiryTime) {
sessions.remove(sessionId);
sendErrorResponse(exchange, 400, "State expired");
return;
}
// Get stored state
String storedState = (String) session.get("oauth_state");
// Clear stored state
session.remove("oauth_state");
session.remove("oauth_state_expiry");
// Verify state
if (storedState == null || !storedState.equals(receivedState)) {
sendErrorResponse(exchange, 400, "State validation failed");
return;
}
// Get authorization code
String code = queryParams.get("code");
if (code == null || code.isEmpty()) {
sendErrorResponse(exchange, 400, "Missing authorization code");
return;
}
// Exchange code for token
TokenResponse tokenResponse = exchangeCodeForToken(code);
// Store tokens in session
session.put("access_token", tokenResponse.accessToken);
session.put("refresh_token", tokenResponse.refreshToken);
session.put("token_expires_at", Instant.now().plusSeconds(tokenResponse.expiresIn).getEpochSecond());
// Redirect to dashboard
exchange.getResponseHeaders().add("Location", "/dashboard");
exchange.sendResponseHeaders(302, -1);
} catch (Exception e) {
e.printStackTrace();
sendErrorResponse(exchange, 500, "Internal server error");
} finally {
exchange.close();
}
}
/**
* Get session ID from cookie
*/
private String getSessionIdFromCookie(HttpExchange exchange) {
String cookiesHeader = exchange.getRequestHeaders().getFirst("Cookie");
if (cookiesHeader == null) {
return null;
}
// Parse cookies
String[] cookies = cookiesHeader.split(";");
for (String cookie : cookies) {
String[] parts = cookie.trim().split("=", 2);
if (parts.length == 2 && parts[0].equals("session_id")) {
return parts[1];
}
}
return null;
}
/**
* Send error response
*/
private void sendErrorResponse(HttpExchange exchange, int statusCode, String message) throws IOException {
byte[] response = message.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(statusCode, response.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(response);
}
}
}
/**
* Parse query string into map
*/
private static Map<String, String> parseQueryString(String query) {
Map<String, String> params = new HashMap<>();
if (query == null || query.isEmpty()) {
return params;
}
String[] pairs = query.split("&");
for (String pair : pairs) {
String[] keyValue = pair.split("=", 2);
if (keyValue.length == 2) {
params.put(keyValue[0], keyValue[1]);
}
}
return params;
}
/**
* Exchange authorization code for access token
*/
private static TokenResponse exchangeCodeForToken(String code) throws IOException {
// Prepare request data
String requestBody = String.format(
"client_id=%s&client_secret=%s&code=%s",
URLEncoder.encode("YOUR_CLIENT_ID", StandardCharsets.UTF_8),
URLEncoder.encode("YOUR_CLIENT_SECRET", StandardCharsets.UTF_8),
URLEncoder.encode(code, StandardCharsets.UTF_8)
);
// Create connection
URL url = new URL(BASE_URL + "/oauth2/token");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
// Send request
try (OutputStream os = connection.getOutputStream()) {
os.write(requestBody.getBytes(StandardCharsets.UTF_8));
}
// Handle error response
int responseCode = connection.getResponseCode();
if (responseCode != 200) {
throw new IOException("HTTP error code: " + responseCode);
}
// Read response
try (BufferedReader br = new BufferedReader(new InputStreamReader(
connection.getInputStream(), StandardCharsets.UTF_8))) {
String responseText = br.lines().collect(Collectors.joining("\n"));
return parseTokenResponse(responseText);
}
}
/**
* Parse token response
*/
private static TokenResponse parseTokenResponse(String json) {
// In a real application, use a proper JSON library like Jackson or Gson
// Simplified example assuming valid JSON
TokenResponse response = new TokenResponse();
// Extract access_token
int accessTokenStart = json.indexOf("\"access_token\":\"") + 15;
int accessTokenEnd = json.indexOf("\"", accessTokenStart);
response.accessToken = json.substring(accessTokenStart, accessTokenEnd);
// Extract refresh_token
int refreshTokenStart = json.indexOf("\"refresh_token\":\"") + 16;
int refreshTokenEnd = json.indexOf("\"", refreshTokenStart);
response.refreshToken = json.substring(refreshTokenStart, refreshTokenEnd);
// Extract expires_in
int expiresInStart = json.indexOf("\"expires_in\":") + 13;
int expiresInEnd = json.indexOf(",", expiresInStart);
if (expiresInEnd == -1) {
expiresInEnd = json.indexOf("}", expiresInStart);
}
response.expiresIn = Integer.parseInt(json.substring(expiresInStart, expiresInEnd).trim());
return response;
}
/**
* Token response object
*/
static class TokenResponse {
String accessToken;
String refreshToken;
int expiresIn;
}
}
}
<?php
declare(strict_types=1);
// Region-aware base URL - set based on the region:
// Global: "https://whitebit.com"
// EU: "https://whitebit.eu"
define('BASE_URL', 'https://whitebit.com');
/**
* Generate a cryptographically secure random state value
*
* @return string The secure random state value
*/
function generateSecureState(): string {
return bin2hex(random_bytes(32)); // 256 bits of entropy
}
/**
* Initiate OAuth flow and redirect user to authorization endpoint
*
* @return void
*/
function initiateOAuthFlow(): void {
// Start secure session
session_start([
'cookie_httponly' => true,
'cookie_secure' => true,
'cookie_samesite' => 'Lax',
'use_strict_mode' => true
]);
// Generate and store state
$state = generateSecureState();
$_SESSION['oauth_state'] = $state;
$_SESSION['oauth_state_created_at'] = time();
// Set short expiration time for state
$_SESSION['oauth_state_expires_at'] = time() + 600; // 10 minutes
// Redirect to authorization endpoint
$authUrl = BASE_URL . '/auth/login?clientId=' .
urlencode('YOUR_CLIENT_ID') .
'&state=' . urlencode($state);
// Prevent header injection
if (!headers_sent()) {
header('Location: ' . $authUrl);
exit;
}
}
/**
* Handle OAuth callback and validate state parameter
*
* @return void
*/
function handleOAuthCallback(): void {
// Start secure session
session_start([
'cookie_httponly' => true,
'cookie_secure' => true,
'cookie_samesite' => 'Lax',
'use_strict_mode' => true
]);
// Retrieve states
$receivedState = $_GET['state'] ?? '';
$storedState = $_SESSION['oauth_state'] ?? '';
$expiresAt = $_SESSION['oauth_state_expires_at'] ?? 0;
// Clear the stored state immediately
unset($_SESSION['oauth_state'], $_SESSION['oauth_state_expires_at'], $_SESSION['oauth_state_created_at']);
// Verify the state parameter
if (empty($receivedState) ||
$receivedState !== $storedState ||
time() > $expiresAt) {
// Potential CSRF attack or expired state - abort authentication
error_log('OAuth state validation failed');
http_response_code(400);
echo 'State validation failed';
exit;
}
// State is valid, proceed with code exchange
$code = $_GET['code'] ?? '';
if (!empty($code)) {
exchangeCodeForToken($code);
} else {
http_response_code(400);
echo 'Missing authorization code';
exit;
}
}
/**
* Exchange authorization code for access token
*
* @param string $code The authorization code to exchange
* @return void
*/
function exchangeCodeForToken(string $code): void {
// Prepare request parameters
$params = [
'client_id' => 'YOUR_CLIENT_ID',
'client_secret' => 'YOUR_CLIENT_SECRET',
'code' => $code
];
// Initialize cURL session
$ch = curl_init();
// Set cURL options
curl_setopt_array($ch, [
CURLOPT_URL => BASE_URL . '/oauth2/token',
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($params),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/x-www-form-urlencoded',
'Accept: application/json'
],
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => true
]);
// Execute request
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Check for errors
if ($httpCode !== 200 || $response === false) {
error_log('Token exchange failed with HTTP code: ' . $httpCode);
http_response_code(500);
echo 'Authentication failed';
exit;
}
// Parse response
$tokenData = json_decode($response, true);
// Store tokens securely in session
$_SESSION['access_token'] = $tokenData['data']['access_token'] ?? null;
$_SESSION['refresh_token'] = $tokenData['data']['refresh_token'] ?? null;
$_SESSION['token_expires_at'] = time() + ($tokenData['data']['expires_in'] ?? 300);
// Redirect to dashboard
header('Location: /dashboard');
exit;
}
?>
Scopes
Available Scopes (requested during client setup):general: General API accessshow.userinfo: Access to basic user informationusers.read: Read user datausers.email.read: Read user email informationusers.kyc.read: Information about whether a user has passed KYC verificationorders.read: Read trading ordersorders.create: Create trading ordersorders.delete: Delete trading ordersbalances.read: Read account balancesmarkets.read: Read market informationdeals.read: Read trading dealsorders_history.read: Read order historyusers.transactions.read: Read user transactionsusers.converts.read: Read currency conversion historyusers.balances.read: Read user account balancesusers.orders.read: Read user ordersusers.deals.read: Read user dealsapikeys.create: Issue an OAuth-bound API key during the consent flowapikeys.read: Read OAuth-issued API key state and retrieve its secret onceapikeys.delete: Delete an OAuth-issued API key owned by the OAuth2 client