Skip to main content

Token Refresh

Access tokens expire after 15 minutes for security reasons. The Token Refresh endpoint allows you to obtain a new access token using a valid refresh token without requiring credentials again.

Endpoint

POST /auth/refresh

Authentication

Required: No (public endpoint)

Request

Request Body

{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}

Field Descriptions

FieldTypeRequiredDescription
refreshstringYesValid refresh token from login or app token endpoint

Response

Success Response (200 OK)

{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}

Response Fields

FieldTypeDescription
accessstringNew JWT access token (valid for 15 minutes)
refreshstringNew JWT refresh token (valid for 7 days)

Token Lifecycle

┌─────────────────────────────────────────────────────────┐
│ Token Lifecycle │
├─────────────────────────────────────────────────────────┤
│ │
│ Login/Auth Refresh Before Expiry │
│ ↓ ↓ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Access: 15m │ │ Access: 15m │ │
│ │ Refresh: 7d │──────────│ Refresh: 7d │────── │
│ └─────────────┘ └──────────────┘ │ │
│ ▲ ▲ │ │
│ │ │ │ │
│ └──────────────────────────┘ │ │
│ │ │
│ Refresh Expired (7d) │
│ ↓ │
│ Must Re-login │
│ │
└─────────────────────────────────────────────────────────┘

Status Codes

CodeStatusDescription
200SuccessToken refreshed successfully
400Bad RequestMissing refresh token
401UnauthorizedInvalid or expired refresh token
403ForbiddenToken revoked or user deactivated
500Server ErrorInternal server error

Error Responses

400 Bad Request - Missing Refresh Token

{
"detail": "Refresh token is required"
}

401 Unauthorized - Invalid Token

{
"detail": "Invalid refresh token"
}

401 Unauthorized - Expired Refresh Token

{
"detail": "Refresh token has expired. Please login again."
}

403 Forbidden - User Deactivated

{
"detail": "User account is no longer active"
}

Examples

Using cURL

curl -X POST https://sandbox.api.jpay.africa/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}'

Using Python

import requests
import json
from datetime import datetime, timedelta
import jwt

class JPayTokenManager:
"""Manages token refresh for JPay API"""

def __init__(self, access_token, refresh_token):
self.access_token = access_token
self.refresh_token = refresh_token
self.token_expiry = None
self._decode_token_expiry()

def _decode_token_expiry(self):
"""Decode JWT to get expiry time"""
try:
decoded = jwt.decode(
self.access_token,
options={"verify_signature": False}
)
exp_timestamp = decoded.get('exp')
if exp_timestamp:
self.token_expiry = datetime.fromtimestamp(exp_timestamp)
except:
# If decoding fails, assume expired
self.token_expiry = datetime.now()

def is_token_expired(self):
"""Check if token is expired or about to expire"""
if self.token_expiry is None:
return True
# Refresh if less than 2 minutes remaining
return datetime.now() >= (self.token_expiry - timedelta(minutes=2))

def refresh_access_token(self):
"""Get new access token using refresh token"""
try:
response = requests.post(
'https://sandbox.api.jpay.africa/api/v1/auth/refresh',
json={'refresh': self.refresh_token},
timeout=10
)

if response.status_code == 200:
data = response.json()
self.access_token = data['access']
self.refresh_token = data['refresh']
self._decode_token_expiry()
print("Token refreshed successfully")
return True
elif response.status_code == 401:
print("Refresh token expired. Please login again.")
return False
else:
print(f"Token refresh failed: {response.status_code}")
print(response.json())
return False

except requests.RequestException as e:
print(f"Token refresh error: {e}")
return False

def get_valid_access_token(self):
"""Get valid access token, refreshing if necessary"""
if self.is_token_expired():
if not self.refresh_access_token():
raise RuntimeError("Failed to refresh token")
return self.access_token

def get_auth_headers(self):
"""Get authorization headers with valid token"""
token = self.get_valid_access_token()
return {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}

# Usage Example
def api_request_with_auto_refresh(token_manager, endpoint, method='GET', data=None):
"""Make API request with automatic token refresh"""
headers = token_manager.get_auth_headers()

if method == 'GET':
response = requests.get(endpoint, headers=headers)
elif method == 'POST':
response = requests.post(endpoint, headers=headers, json=data)
else:
raise ValueError(f"Unsupported method: {method}")

return response

# Initialize token manager
token_manager = JPayTokenManager(
access_token='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...',
refresh_token='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...'
)

# Make request - will auto-refresh if token is expired
response = api_request_with_auto_refresh(
token_manager,
'https://sandbox.api.jpay.africa/api/v1/payments/collections/list',
method='GET'
)

print(response.json())

Using JavaScript

class JPayTokenManager {
constructor(accessToken, refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.tokenExpiry = this.decodeTokenExpiry(accessToken);
}

decodeTokenExpiry(token) {
try {
const parts = token.split('.');
const decoded = JSON.parse(atob(parts[1]));
return new Date(decoded.exp * 1000);
} catch (e) {
return new Date(0); // If decode fails, assume expired
}
}

isTokenExpired() {
// Refresh if less than 2 minutes remaining
const bufferTime = 2 * 60 * 1000; // 2 minutes
return new Date() >= new Date(this.tokenExpiry.getTime() - bufferTime);
}

async refreshAccessToken() {
try {
const response = await fetch(
'https://sandbox.api.jpay.africa/api/v1/auth/refresh',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
refresh: this.refreshToken
})
}
);

if (!response.ok) {
if (response.status === 401) {
console.error('Refresh token expired. Please login again.');
return false;
}
console.error(`Token refresh failed: ${response.status}`);
return false;
}

const data = await response.json();
this.accessToken = data.access;
this.refreshToken = data.refresh;
this.tokenExpiry = this.decodeTokenExpiry(data.access);
console.log('Token refreshed successfully');
return true;
} catch (error) {
console.error('Token refresh error:', error);
return false;
}
}

async getValidAccessToken() {
if (this.isTokenExpired()) {
const success = await this.refreshAccessToken();
if (!success) {
throw new Error('Failed to refresh token');
}
}
return this.accessToken;
}

async getAuthHeaders() {
const token = await this.getValidAccessToken();
return {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
};
}

async makeRequest(endpoint, options = {}) {
const headers = await this.getAuthHeaders();
const response = await fetch(endpoint, {
...options,
headers: {
...headers,
...options.headers
}
});

if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'API request failed');
}

return response.json();
}
}

// Usage Example
const tokenManager = new JPayTokenManager(
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...',
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...'
);

// Make request - will auto-refresh if token is expired
try {
const collections = await tokenManager.makeRequest(
'https://sandbox.api.jpay.africa/api/v1/payments/collections/list'
);
console.log('Collections:', collections);
} catch (error) {
console.error('Failed to fetch collections:', error);
}

Token Refresh Best Practices

1. Proactive Refresh

Refresh tokens before they expire rather than waiting for a 401 error:

# Good: Refresh when 2 minutes remaining
if token_manager.is_token_expired():
token_manager.refresh_access_token()

# Bad: Wait for request to fail
try:
response = api_request() # 401 Unauthorized
except UnauthorizedError:
refresh_token() # Too late

2. Centralized Token Management

Use a token manager class to handle all token operations:

# Good: Use token manager for all requests
token_manager = JPayTokenManager(access, refresh)
headers = token_manager.get_auth_headers()
response = requests.get(endpoint, headers=headers)

# Bad: Manual token management scattered throughout code
if token_expired():
refresh()
headers = {'Authorization': f'Bearer {token}'}

3. Handle Refresh Token Expiry

Implement logic to redirect to login when refresh token expires:

def ensure_valid_token(token_manager):
if token_manager.is_token_expired():
success = token_manager.refresh_access_token()
if not success:
# Refresh token also expired
redirect_to_login()

4. Secure Token Storage

Store tokens securely based on your environment:

Browser (Web Application):

  • Use httpOnly cookies (prevents XSS attacks)
  • Set Secure flag (HTTPS only)
  • Set SameSite=Strict (CSRF protection)

Server (Backend Application):

  • Use environment variables
  • Use secrets manager (AWS Secrets Manager, Vault, etc.)
  • Never commit tokens to version control

5. Token Refresh on Network Errors

Implement retry logic with token refresh:

def api_request_with_retry(token_manager, endpoint, max_retries=3):
for attempt in range(max_retries):
try:
headers = token_manager.get_auth_headers()
response = requests.get(endpoint, headers=headers, timeout=10)

if response.status_code == 401:
# Unauthorized, try refresh
if attempt < max_retries - 1:
token_manager.refresh_access_token()
continue

return response

except requests.RequestException as e:
if attempt < max_retries - 1:
wait_time = 2 ** attempt
time.sleep(wait_time)
continue
raise

Next Steps

After refreshing your token:

  1. Update your stored token with the new access token
  2. Continue making API requests with the new token
  3. Monitor token expiry and refresh proactively
  4. Implement automatic refresh in your token manager