Official Python SDK for Paylode Services Limited — CBN Licensed PSSP
Project description
paylode-python
Official Python SDK for Paylode Services Limited — CBN Licensed Payment Solution Service Provider (PSSP).
Requirements
- Python 3.7+
- A Paylode secret key (
sk_live_...orsk_test_...) — obtain from your merchant dashboard
Installation
pip install paylode-python
Zero external dependencies — uses Python standard library only (urllib, hmac, hashlib).
Quick start
from paylode import Paylode
client = Paylode("sk_live_xxxxxxxxxxxxxxxxxxxx")
# 1. Initialize a payment — redirect your customer to authorization_url
txn = client.transaction.initialize(
email="customer@example.com",
amount=500_000, # ₦5,000 in kobo (1 kobo = 0.01 naira)
callback_url="https://yoursite.com/payment/complete",
channels=["card", "bank_transfer"],
metadata={"order_id": "ORD-9812", "customer_name": "Ada Obi"},
)
redirect_url = txn["data"]["authorization_url"]
# 2. Verify server-side when the customer returns — ALWAYS do this
result = client.transaction.verify("ORD-9812")
if result["data"]["status"] == "success":
fulfill_order(result["data"]["metadata"]["order_id"])
API reference
Paylode(secret_key, *, sandbox=None)
| Parameter | Type | Description |
|---|---|---|
secret_key |
str | Your sk_live_... or sk_test_... key (required) |
sandbox |
bool, optional | Force sandbox mode (auto-detected from key prefix) |
client = Paylode("sk_live_xxxxxxxxxxxxxxxxxxxx")
print(client.sandbox) # False — auto-detected from live key
print(client.version) # '1.0.0'
print(repr(client)) # Paylode(mode='live', version='1.0.0')
client.transaction
.initialize(*, email, amount, ...) → dict
Initialize a new payment. Redirect your customer to data.authorization_url.
| Parameter | Type | Required | Description |
|---|---|---|---|
email |
str | Yes | Customer email address |
amount |
int | Yes | Amount in kobo (minimum ₦100 = 10_000 kobo) |
reference |
str | No | Unique transaction reference (auto-generated if omitted) |
currency |
str | No | 'NGN' (default) |
callback_url |
str | No | URL to redirect customer after payment |
channels |
list[str] | No | ['card', 'bank_transfer', 'ussd', 'direct_debit'] |
metadata |
dict | No | Arbitrary key-value pairs — returned in webhooks |
txn = client.transaction.initialize(
email="customer@example.com",
amount=250_000, # ₦2,500
reference="ORD-2026-00142", # optional, must be unique
callback_url="https://myshop.com/confirm",
channels=["card", "bank_transfer"],
metadata={"order_id": "ORD-9812", "plan": "premium"},
)
redirect_url = txn["data"]["authorization_url"] # send customer here
reference = txn["data"]["reference"] # store this
.verify(reference) → dict
Verify a transaction. Always call this server-side before fulfilling any order.
result = client.transaction.verify("ORD-2026-00142")
if result["data"]["status"] == "success":
amount_paid = result["data"]["amount"] # in kobo
fees = result["data"]["fees"] # platform fees
order_id = result["data"]["metadata"]["order_id"]
fulfill_order(order_id)
.list(...) → dict
transactions = client.transaction.list(
page=1,
per_page=50,
status="success", # 'success' | 'failed' | 'pending'
from_date="2026-01-01",
to_date="2026-01-31",
)
for txn in transactions["data"]:
print(txn["reference"], txn["amount"])
.fetch(transaction_id) → dict
txn = client.transaction.fetch("txn_01JXYZ123456")
.refund(reference, amount=None, reason='') → dict
# Full refund
client.transaction.refund("ORD-2026-00142")
# Partial refund — ₦2,000
client.transaction.refund(
"ORD-2026-00142",
amount=200_000,
reason="Item out of stock",
)
client.customer
# Create
customer = client.customer.create(
email="ada@example.com",
first_name="Ada",
last_name="Obi",
phone="+2348012345678",
metadata={"plan": "gold"},
)
# Fetch by email or customer code
c = client.customer.fetch("ada@example.com")
c = client.customer.fetch("CUS_abc123")
# List
customers = client.customer.list(page=1, per_page=50)
# Update
client.customer.update("CUS_abc123", phone="+2349011112222")
client.subaccount — for aggregators
Subaccounts represent merchants under an aggregator. Paylode uses them to automatically split settlement at the point of transaction.
# Register a merchant under your aggregator account
sub = client.subaccount.create(
business_name="Shoprite Nigeria",
settlement_bank="GTB",
account_number="0123456789",
percentage_charge=70, # merchant receives 70% of each transaction
description="Shoprite Lagos Island branch",
)
subaccount_code = sub["data"]["subaccount_code"] # SUB_xxxx
# Include when initializing a split transaction
txn = client.transaction.initialize(
email="buyer@example.com",
amount=100_000,
metadata={"order_id": "ORD-9812"},
)
client.subaccount.fetch("SUB_abc123")
client.subaccount.list(page=1, per_page=50)
client.subaccount.update("SUB_abc123", percentage_charge=75)
client.settlement
# List settlements
settlements = client.settlement.list(
page=1,
per_page=50,
from_date="2026-01-01",
to_date="2026-01-31",
)
# Fetch one by ID
s = client.settlement.fetch("STL_01JX123456")
Paylode.verify_webhook(raw_body, signature, secret) → bool
Verify a webhook signature. Call at the top of every webhook handler.
import hashlib, hmac
# Django example
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
@csrf_exempt
def paylode_webhook(request):
sig = request.META.get("HTTP_X_PAYLODE_SIGNATURE", "")
secret = settings.PAYLODE_WEBHOOK_SECRET
if not Paylode.verify_webhook(request.body, sig, secret):
return HttpResponse(status=401)
import json
event = json.loads(request.body)
if event["event"] == "payment.success":
fulfill_order(event["data"]["metadata"]["order_id"])
elif event["event"] == "refund.processed":
update_order_status(event["data"]["reference"], "refunded")
return HttpResponse(status=200)
# Flask example
from flask import Flask, request, abort
@app.route('/webhook/paylode', methods=['POST'])
def paylode_webhook():
if not Paylode.verify_webhook(
request.get_data(),
request.headers.get("X-Paylode-Signature", ""),
app.config["PAYLODE_WEBHOOK_SECRET"],
):
abort(401)
event = request.get_json(force=True)
if event["event"] == "payment.success":
fulfill_order(event["data"]["metadata"]["order_id"])
return "", 200
Static helpers
Paylode.generate_ref("ORD") # 'ORD-M6X2K1-A3F9B2C1'
Paylode.kobo_to_naira(500_000) # 5000.0
Paylode.naira_to_kobo(5000) # 500000
KYC tier limits
limits = client.kyc_limits
limits["tier_1"]["single_txn"] # 5_000_000 kobo (₦50,000)
limits["tier_1"]["daily"] # 30_000_000 kobo (₦300,000)
limits["tier_2"]["single_txn"] # 100_000_000 kobo (₦1,000,000)
limits["tier_3"]["single_txn"] # 500_000_000 kobo (₦5,000,000)
Error handling
from paylode.exceptions import (
PaylodeValidationError, # Invalid params — client-side
PaylodeAPIError, # Non-2xx API response
PaylodeAuthError, # 401 — invalid or missing key
PaylodeNetworkError, # Could not reach Paylode API
PaylodeError, # Base class — catches all of the above
)
try:
txn = client.transaction.initialize(email="x", amount=100)
except PaylodeValidationError as e:
print(e.message) # 'amount must be an integer in kobo, minimum ₦100'
print(e.field) # 'amount'
except PaylodeAuthError:
print("Invalid API key")
except PaylodeAPIError as e:
print(e.status_code) # e.g. 422
print(e.error_code) # e.g. 'DUPLICATE_REFERENCE'
print(e.raw) # full API response dict
except PaylodeNetworkError:
print("Could not reach Paylode API — retry later")
Sandbox / test mode
# sk_test_ key auto-sets sandbox=True
client = Paylode("sk_test_xxxxxxxxxxxxxxxxxxxx")
print(client.sandbox) # True
Test cards for the checkout page:
| Card number | Result |
|---|---|
4084 0841 1111 1111 |
Success |
4084 0841 1111 1112 |
Insufficient funds |
4084 0841 1111 1113 |
Declined |
Complete integration example (Django)
# views.py
import json
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from paylode import Paylode
from paylode.exceptions import PaylodeError
client = Paylode(settings.PAYLODE_SECRET_KEY)
@require_POST
def checkout(request):
body = json.loads(request.body)
try:
txn = client.transaction.initialize(
email=body["email"],
amount=body["amount"],
callback_url=request.build_absolute_uri("/confirm/"),
metadata={"order_id": body["order_id"]},
)
return JsonResponse({"authorization_url": txn["data"]["authorization_url"]})
except PaylodeError as e:
return JsonResponse({"error": e.message}, status=400)
def confirm(request):
result = client.transaction.verify(request.GET["reference"])
if result["data"]["status"] == "success":
fulfill_order(result["data"]["metadata"]["order_id"])
return redirect("/order/complete/")
return redirect("/order/failed/")
@csrf_exempt
@require_POST
def webhook(request):
if not Paylode.verify_webhook(
request.body,
request.META.get("HTTP_X_PAYLODE_SIGNATURE", ""),
settings.PAYLODE_WEBHOOK_SECRET,
):
return HttpResponse(status=401)
event = json.loads(request.body)
if event["event"] == "payment.success":
fulfill_order(event["data"]["metadata"]["order_id"])
return HttpResponse(status=200)
Paylode Services Limited · CBN/PAY/2024/001847 · docs.paylodeservices.com
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file paylode_python-1.0.0.tar.gz.
File metadata
- Download URL: paylode_python-1.0.0.tar.gz
- Upload date:
- Size: 17.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
334f1bd9221d9ca79274ff787fd752f9ad119b5b3d10af7906ccbf4526518768
|
|
| MD5 |
c426f66e27dbfeb9d84573896b054349
|
|
| BLAKE2b-256 |
442353303950475778a17c44e0539ae92b9029ba861202491983307fea21f0f8
|
File details
Details for the file paylode_python-1.0.0-py3-none-any.whl.
File metadata
- Download URL: paylode_python-1.0.0-py3-none-any.whl
- Upload date:
- Size: 14.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
14716343567300ceb312ca78800cb55c334ea329bb87615ee76f2f1318510275
|
|
| MD5 |
be7e63e3fcc7148a36c94090d0734f81
|
|
| BLAKE2b-256 |
d75e3c69332f0195184ac393fb89cea0e8e68ab6aea70e66713170a223967190
|