Skip to main content

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​

FieldTypeRequiredDescription
app_keystringYesYour app's unique identifier
app_secretstringYesYour 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​

FieldTypeDescription
accessstringJWT access token (expires in 15 minutes)
refreshstringJWT refresh token (expires in 7 days)
merchant_idintegerYour merchant ID
app_codeintegerYour app's numeric code
productsarrayList 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/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​

FieldTypeRequiredDescription
refreshstringYesThe 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}/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('/collections/checkouts');
const data = await response.json();

Security Considerations​

danger

Security Best Practices

  1. Store credentials securely

    • Use environment variables
    • Never hardcode in source code
    • Use secrets management systems
  2. Keep tokens safe

    • Don't log tokens
    • Don't expose in error messages
    • Store only in secure backend storage
  3. Use HTTPS only

    • All requests must be over HTTPS
    • Validate SSL certificates
  4. Rotate credentials

    • Change app secret periodically
    • Regenerate if compromised
    • Monitor usage patterns

Next Steps​