App Token Authentication
App Token Authentication is the recommended method for third-party applications to access the JPay Africa API. This guide explains how to authenticate using your app credentials.
Overview
App Token Authentication uses your App Key and App Secret to obtain JWT tokens that grant access to the JPay Africa API.
Authentication Flow
┌─────────────────┐
│ Your App │
│ (Client) │
└────────┬────────┘
│ 1. Send App Key + Secret
▼
┌─────────────────┐
│ JPay API │
│ /app/token │
└────────┬────────┘
│ 2. Validate credentials
│ 3. Generate JWT tokens
▼
┌─────────────────┐
│ Your App │
│ Gets Tokens │
└─────────────────┘
Obtaining Tokens
Request
Endpoint: POST /auth/app/token
curl -X POST https://sandbox.api.jpay.africa/api/v1/auth/app/token \
-H "Content-Type: application/json" \
-d '{
"app_key": "your_app_key_here",
"app_secret": "your_app_secret_here"
}'
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
app_key | string | Yes | Your app's unique identifier |
app_secret | string | Yes | Your app's secret key |
Response
Status Code: 200 OK
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMjMsImFwcF9jb2RlIjoxMjM0NSwicHJvZHVjdHMiOlsiY29sbGVjdGlvbnMiLCJwYXlvdXRzIl19.abc123xyz",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMjMsInJlZnJlc2giOnRydWV9.def456uvw",
"merchant_id": 123,
"app_code": 12345,
"products": ["collections", "payouts"]
}
Response Fields
| Field | Type | Description |
|---|---|---|
access | string | JWT access token (expires in 15 minutes) |
refresh | string | JWT refresh token (expires in 7 days) |
merchant_id | integer | Your merchant ID |
app_code | integer | Your app's numeric code |
products | array | List of enabled products for this app |
Error Responses
401 Unauthorized
Invalid or missing credentials:
{
"detail": "Invalid app credentials"
}
403 Forbidden
App or merchant account is not active:
{
"detail": "App is not active"
}
{
"detail": "Merchant account is not active"
}
400 Bad Request
Missing required fields:
{
"detail": "Invalid request"
}
Using Access Token
Once you have the access token, include it in all API requests:
Authorization Header
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
Example Request
curl -X POST https://sandbox.api.jpay.africa/api/v1/payments/collections/checkouts/initiate \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." \
-H "Content-Type: application/json" \
-d '{
"payfrom": "+254712345678",
"amount": "1000.00",
"ref_no": "ORDER-123",
"account_number": "1001",
"callback_url": "https://yourdomain.com/webhook"
}'
Token Lifecycle
Access Token
- Expiration: 15 minutes
- Refresh: Automatic via refresh token
- Purpose: Make API requests
Refresh Token
- Expiration: 7 days
- Refresh: Manual via /refresh endpoint
- Purpose: Obtain new access tokens
Refreshing Tokens
When your access token expires, use the refresh token to get a new access token:
Request
Endpoint: POST /auth/refresh
curl -X POST https://sandbox.api.jpay.africa/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}'
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
refresh | string | Yes | The refresh token from previous authentication |
Response
Status Code: 200 OK
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"merchant_code": 123,
"name": "Your Merchant Name",
"email": "merchant@example.com",
"phone_number": "+254712345678",
"requires_verification": false,
"message": "Token refreshed successfully"
}
Implementation Example
Python
import requests
from datetime import datetime, timedelta
class JPayAuthClient:
BASE_URL = "https://sandbox.api.jpay.africa/api/v1"
def __init__(self, app_key, app_secret):
self.app_key = app_key
self.app_secret = app_secret
self.access_token = None
self.refresh_token = None
self.expires_at = None
def authenticate(self):
"""Get initial tokens using app credentials"""
response = requests.post(
f"{self.BASE_URL}/auth/app/token",
json={
"app_key": self.app_key,
"app_secret": self.app_secret
}
)
response.raise_for_status()
data = response.json()
self.access_token = data['access']
self.refresh_token = data['refresh']
self.expires_at = datetime.now() + timedelta(minutes=14)
return data
def refresh(self):
"""Refresh access token using refresh token"""
response = requests.post(
f"{self.BASE_URL}/auth/refresh",
json={"refresh": self.refresh_token}
)
response.raise_for_status()
data = response.json()
self.access_token = data['access']
self.refresh_token = data['refresh']
self.expires_at = datetime.now() + timedelta(minutes=14)
return data
def is_token_expired(self):
"""Check if access token is expired"""
return datetime.now() >= self.expires_at
def ensure_valid_token(self):
"""Ensure we have a valid access token"""
if not self.access_token:
self.authenticate()
elif self.is_token_expired():
self.refresh()
def get_headers(self):
"""Get headers with authorization"""
self.ensure_valid_token()
return {
"Authorization": f"Bearer {self.access_token}",
"Content-Type": "application/json"
}
# Usage
client = JPayAuthClient("your_app_key", "your_app_secret")
client.authenticate()
# Make API requests
headers = client.get_headers()
response = requests.get(
f"{client.BASE_URL}/payments/collections/checkouts",
headers=headers
)
JavaScript
class JPayAuthClient {
constructor(appKey, appSecret) {
this.appKey = appKey;
this.appSecret = appSecret;
this.accessToken = null;
this.refreshToken = null;
this.expiresAt = null;
this.baseUrl = 'https://sandbox.api.jpay.africa/api/v1';
}
async authenticate() {
const response = await fetch(`${this.baseUrl}/auth/app/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
app_key: this.appKey,
app_secret: this.appSecret
})
});
const data = await response.json();
this.accessToken = data.access;
this.refreshToken = data.refresh;
this.expiresAt = new Date(Date.now() + 14 * 60 * 1000);
return data;
}
async refresh() {
const response = await fetch(`${this.baseUrl}/auth/refresh`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh: this.refreshToken })
});
const data = await response.json();
this.accessToken = data.access;
this.refreshToken = data.refresh;
this.expiresAt = new Date(Date.now() + 14 * 60 * 1000);
return data;
}
isTokenExpired() {
return new Date() >= this.expiresAt;
}
async ensureValidToken() {
if (!this.accessToken) {
await this.authenticate();
} else if (this.isTokenExpired()) {
await this.refresh();
}
}
async request(endpoint, options = {}) {
await this.ensureValidToken();
const headers = {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
...options.headers
};
return fetch(`${this.baseUrl}${endpoint}`, {
...options,
headers
});
}
}
// Usage
const client = new JPayAuthClient('your_app_key', 'your_app_secret');
await client.authenticate();
// Make API requests
const response = await client.request('/payments/collections/checkouts');
const data = await response.json();
Security Considerations
danger
Security Best Practices
Store credentials securely
- Use environment variables
- Never hardcode in source code
- Use secrets management systems
Keep tokens safe
- Don't log tokens
- Don't expose in error messages
- Store only in secure backend storage
Use HTTPS only
- All requests must be over HTTPS
- Validate SSL certificates
Rotate credentials
- Change app secret periodically
- Regenerate if compromised
- Monitor usage patterns
Next Steps
- Initiate Checkout - Create a collection
- Initiate Payout - Create a payout
- Error Handling - Handle authentication errors