PaySwitch is a wholly Ghanaian owned integrated payment solutions provider offering card hosting, POS, fraud prevention, payment gateway, and third-party processing (TPP) services. Integrate multiple payment methods including cards, mobile money, QR codes, and payment links for seamless transactions across C2C, C2B, B2C, and B2B flows.
PaySwitch is a FinTech specialist established in 2015 that provides integrated payment solutions for banks, non-banking financial institutions, telecommunications companies, and organizations across Ghana and West Africa. Their ecosystem is connected to telcos and banks through Ghana's national switch (GhIPSS - Ghana Interbank Payment and Settlement Systems Limited), enabling 360-degree transaction capabilities.
PaySwitch offers multiple convergent solutions including card issuance and hosting, third-party processing (TPP), fraud prevention and detection, service aggregation, customized payment solutions, and POS/MPOS capabilities. Their primary API product is TheTeller, a PCI DSS compliant payment platform designed for eCommerce integration.
Use PaySwitch/TheTeller when you need to:
PaySwitch uses OAuth 2 for API authentication. Obtain a token by POSTing to the OAuth endpoint with your credentials.
⚠️ Domain naming clarification: The PaySwitch sandbox/test environment uses the domain
try.payswitch.net. Despite the name looking like a staging URL,try.payswitch.netis PaySwitch's official sandbox environment — not an unofficial mirror. The production domain isapi.payswitch.net. Always usetry.payswitch.netfor testing andapi.payswitch.netfor live transactions.
⚠️ OAuth password grant: PaySwitch uses the OAuth 2.0 Resource Owner Password Credentials Grant (
grant_type: "password") — you submit your email and password directly to obtain a token. This grant type is deprecated in OAuth 2.1 and considered a security anti-pattern because it requires sending your credentials on every token request. However, it is PaySwitch's documented auth flow and there is currently no alternative. Store the token securely and do not log it. Token expiry is 3600 seconds (1 hour) — implement refresh logic before expiry.
Endpoint: POST https://try.payswitch.net/oauth/token
Request:
{
"email": "[email protected]",
"password": "your-password",
"grant_type": "password"
}
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
Using the Token:
Set the Authorization header in all subsequent API requests:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
For legacy integrations, you can use X-User-Email and X-User-Token headers:
X-User-Email: [email protected]
X-User-Token: your-api-token
Note: When using Bearer token authentication, the X-User-Email and X-User-Token headers are no longer required.
https://try.payswitch.nethttps://api.payswitch.net (contact PaySwitch for access)TheTeller is PaySwitch's main payment processing API for handling transactions and fund transfers.
Endpoint: POST /v1.1/transaction/process
Headers:
Content-Type: application/json
Authorization: Basic base64(api_username:api_key)
Request Body Example:
{
"merchant_id": "MCH123456",
"transaction_id": "TXN20240224001",
"processing_code": "000000",
"amount": 10000,
"currency": "GHS",
"account_number": "1234567890",
"account_issuer": "GCB",
"description": "Payment for Order #12345",
"customer_name": "John Doe",
"customer_phone": "+233501234567",
"customer_email": "[email protected]"
}
Response:
{
"status": "success",
"message": "Transaction processed successfully",
"transaction_reference": "TXN20240224001",
"timestamp": "2024-02-24T10:30:00Z",
"amount": 10000,
"currency": "GHS",
"merchant_id": "MCH123456"
}
Generate shareable payment links for customers to complete payments without requiring code integration.
Endpoint: POST /api/v1/payment-link/generate
Request:
{
"amount": 5000,
"currency": "GHS",
"description": "Payment for Invoice #789",
"customer_name": "Jane Smith",
"customer_email": "[email protected]",
"customer_phone": "+233501234567",
"redirect_url": "https://yoursite.com/success",
"metadata": {
"order_id": "ORD123",
"customer_ref": "CUST456"
}
}
Response:
{
"status": "success",
"payment_link": "https://pay.theteller.net/link/abc123def456",
"link_id": "abc123def456",
"expires_at": "2024-03-25T10:30:00Z",
"short_url": "https://teller.link/abc123"
}
Generate QR codes that customers can scan to initiate payments ("Scan, Pay and Go").
Endpoint: POST /api/v1/qr-code/generate
Request:
{
"amount": 2500,
"currency": "GHS",
"merchant_name": "Your Store",
"reference": "QR20240224001",
"description": "Payment for items"
}
Response:
{
"status": "success",
"qr_code_url": "https://cdn.theteller.net/qr/qr20240224001.png",
"qr_data": "https://pay.theteller.net/qr/qr20240224001",
"reference": "QR20240224001"
}
Retrieve information about the authenticated user/merchant.
Endpoint: GET /api/users/current
Response:
{
"id": "usr_123456",
"email": "[email protected]",
"merchant_name": "Your Business",
"merchant_id": "MCH123456",
"status": "active",
"created_at": "2023-01-15T00:00:00Z"
}
Retrieve available payment product categories.
Endpoint: GET /api/product_categories/main
Response:
{
"status": "success",
"categories": [
{
"id": "cat_001",
"name": "Cards",
"description": "Local and international card payments"
},
{
"id": "cat_002",
"name": "Mobile Money",
"description": "Mobile money service providers"
},
{
"id": "cat_003",
"name": "Bank Transfer",
"description": "Direct bank account transfers"
}
]
}
PaySwitch sends webhooks to notify your application of transaction events. Configure your webhook URL in the PaySwitch dashboard.
Common webhook events include:
transaction.completed - Transaction successfully processedtransaction.failed - Transaction failedtransaction.pending - Transaction waiting for approvalrefund.processed - Refund has been processedtransfer.completed - Fund transfer completedEvent Payload:
{
"event_id": "evt_abc123def456",
"event_type": "transaction.completed",
"timestamp": "2024-02-24T10:30:00Z",
"data": {
"transaction_id": "TXN20240224001",
"merchant_id": "MCH123456",
"amount": 10000,
"currency": "GHS",
"status": "completed",
"payment_method": "card",
"customer_email": "[email protected]",
"reference": "TXN20240224001"
}
}
PaySwitch includes security headers with each webhook:
X-PaySwitch-Signature: hmac-sha512-signature
X-PaySwitch-Event-ID: evt_abc123def456
X-PaySwitch-Timestamp: 1708774200
Verify webhook authenticity using the signature header with HMAC-SHA512:
import hmac
import hashlib
def verify_webhook(payload, signature, webhook_secret):
"""Verify PaySwitch webhook signature"""
expected_signature = hmac.new(
webhook_secret.encode(),
payload.encode(),
hashlib.sha512
).hexdigest()
return hmac.compare_digest(expected_signature, signature)
PaySwitch expects HTTP 2XX responses for successful webhook delivery. If your endpoint does not return a 2XX status:
event_id field to detect and handle duplicatesRecommended Retry Detection:
# Store processed event_ids in database
processed_events = set()
def handle_webhook(event_payload):
event_id = event_payload['event_id']
if event_id in processed_events:
return {"status": "ok"} # Already processed
# Process the event
process_transaction(event_payload)
processed_events.add(event_id)
return {"status": "ok"}
Integrate card payments for an eCommerce checkout:
import requests
import base64
from datetime import datetime
class PaySwitchClient:
def __init__(self, api_username, api_key, environment='test'):
self.environment = environment
self.base_url = "https://try.payswitch.net" if environment == 'test' else "https://api.payswitch.net"
self.auth = base64.b64encode(f"{api_username}:{api_key}".encode()).decode()
def process_payment(self, amount, currency, customer_email, order_id):
"""Process a card payment"""
payload = {
"merchant_id": "MCH123456",
"transaction_id": f"TXN{datetime.now().strftime('%Y%m%d%H%M%S')}",
"processing_code": "000000",
"amount": int(amount * 100), # Convert to smallest currency unit
"currency": currency,
"description": f"Payment for Order #{order_id}",
"customer_email": customer_email,
"redirect_url": "https://yoursite.com/payment-confirmation"
}
headers = {
"Content-Type": "application/json",
"Authorization": f"Basic {self.auth}"
}
response = requests.post(
f"{self.base_url}/v1.1/transaction/process",
json=payload,
headers=headers
)
return response.json()
Create a shareable payment link for invoice collection:
def generate_invoice_payment_link(client, invoice_data):
"""Generate a payment link for an invoice"""
payload = {
"amount": invoice_data['total_amount'],
"currency": "GHS",
"description": f"Invoice #{invoice_data['invoice_number']}",
"customer_name": invoice_data['customer_name'],
"customer_email": invoice_data['customer_email'],
"redirect_url": f"https://yoursite.com/invoice/{invoice_data['invoice_id']}/paid",
"metadata": {
"invoice_id": invoice_data['invoice_id'],
"customer_ref": invoice_data['customer_id']
}
}
# Use OAuth bearer token
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {oauth_token}"
}
response = requests.post(
f"{client.base_url}/api/v1/payment-link/generate",
json=payload,
headers=headers
)
result = response.json()
if result['status'] == 'success':
# Send payment link to customer
send_email_to_customer(
invoice_data['customer_email'],
f"Payment Link: {result['payment_link']}"
)
return result
Enable "Scan, Pay and Go" checkout with QR codes:
def create_pos_transaction_qr(client, transaction_amount, merchant_reference):
"""Create a QR code for in-store payment"""
payload = {
"amount": transaction_amount,
"currency": "GHS",
"merchant_name": "Your Store Name",
"reference": merchant_reference,
"description": "In-store purchase"
}
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {oauth_token}"
}
response = requests.post(
f"{client.base_url}/api/v1/qr-code/generate",
json=payload,
headers=headers
)
result = response.json()
# Display QR code URL on POS terminal
print(f"QR Code: {result['qr_code_url']}")
return result
Process incoming webhook events securely:
from flask import Flask, request, jsonify
import json
app = Flask(__name__)
# Store your webhook secret from PaySwitch dashboard
WEBHOOK_SECRET = "your_webhook_secret_key"
@app.route('/webhooks/payswitch', methods=['POST'])
def handle_payswitch_webhook():
"""Handle incoming PaySwitch webhooks"""
# Verify signature
payload = request.get_data(as_text=True)
signature = request.headers.get('X-PaySwitch-Signature')
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
return {"error": "Invalid signature"}, 401
event_data = request.get_json()
event_id = event_data['event_id']
# Check for duplicate processing
if is_event_processed(event_id):
return {"status": "ok"}, 200
# Handle different event types
if event_data['event_type'] == 'transaction.completed':
process_successful_payment(event_data['data'])
elif event_data['event_type'] == 'transaction.failed':
process_failed_payment(event_data['data'])
# Mark event as processed
mark_event_processed(event_id)
return {"status": "ok"}, 200
PaySwitch API uses standard HTTP status codes and provides descriptive error messages.
| Status | Meaning | Action |
|---|---|---|
| 200 | OK | Request successful |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Invalid request parameters |
| 401 | Unauthorized | Invalid/missing authentication |
| 403 | Forbidden | Access denied to resource |
| 404 | Not Found | Resource does not exist |
| 409 | Conflict | Duplicate transaction or conflict |
| 422 | Unprocessable Entity | Validation error in payload |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Server Error | Internal server error |
| 503 | Service Unavailable | Service temporarily down |
{
"status": "error",
"code": "INVALID_AMOUNT",
"message": "Amount must be greater than 0",
"details": {
"field": "amount",
"value": -100,
"constraint": "min_value:1"
},
"timestamp": "2024-02-24T10:30:00Z"
}
INVALID_AMOUNT - Amount is zero or negativeINVALID_CURRENCY - Currency code not supportedINSUFFICIENT_BALANCE - Merchant account balance insufficientDUPLICATE_TRANSACTION - Transaction ID already existsINVALID_MERCHANT_ID - Merchant ID not validAUTH_FAILED - Authentication failed or token expiredRATE_LIMIT_EXCEEDED - Too many requests in time windowINVALID_WEBHOOK_URL - Webhook URL format invalidPAYMENT_METHOD_NOT_SUPPORTED - Selected payment method unavailableNETWORK_ERROR - Unable to reach payment processordef make_api_request(endpoint, payload):
"""Make API request with comprehensive error handling"""
try:
response = requests.post(endpoint, json=payload, timeout=30)
response.raise_for_status()
return response.json()
except requests.exceptions.Timeout:
# Handle timeout - retry with exponential backoff
return retry_with_backoff(endpoint, payload)
except requests.exceptions.HTTPError as e:
error_data = e.response.json()
if e.response.status_code == 401:
# Refresh authentication token
refresh_auth_token()
return make_api_request(endpoint, payload) # Retry
elif e.response.status_code == 429:
# Rate limited - wait and retry
time.sleep(60)
return make_api_request(endpoint, payload)
else:
log_error(error_data)
raise
except Exception as e:
log_error(str(e))
raise
PaySwitch APIs handle amounts in the smallest currency unit (pesewas for GHS). Always multiply amounts by 100:
Each transaction_id must be globally unique across all time. Reusing transaction IDs will result in DUPLICATE_TRANSACTION errors. Recommended practice:
transaction_id = f"TXN{datetime.utcnow().strftime('%Y%m%d%H%M%S')}{random_suffix}"
OAuth tokens have a limited lifetime (typically 3600 seconds). Implement token refresh logic:
Always verify webhook signatures to prevent spoofed events. Use HMAC-SHA512 with your webhook secret. Unverified webhooks could represent fraudulent or replay attacks.
Mobile money integration requires:
https://try.payswitch.nethttps://api.payswitch.netPaySwitch implements rate limiting per merchant account:
Your PaySwitch merchant account status affects API availability:
active - Full API accesssuspended - Limited/no API accessclosed - No API accessNote: This documentation is based on available research as of February 2024. PaySwitch API specifications may change. Always consult the official documentation at https://docs.payswitch.net/ for the most current information. Contact PaySwitch developer support at [email protected] for clarifications on API behavior and integration specifics.