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/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

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}/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

  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