Python SDK
The official PayKore Python SDK. Supports both synchronous and async/await usage.
Package: paykore
All API calls must be made server-side. Never embed your API key in client-facing code or any environment where it can be read by end users.
Installation
pip install paykore
Initialisation
import os
from paykore import PayKoreApi
client = PayKoreApi(
token=os.environ["PAYKORE_API_KEY"],
base_url="https://api.paykore.com", # or https://sandbox.paykore.com
)
For sandbox:
client = PayKoreApi(
token=os.environ["PAYKORE_SANDBOX_KEY"],
base_url="https://sandbox.paykore.com",
)
The token parameter is your API key (sk_live_* or sk_test_*), sent as Authorization: Bearer <key>. The parameter name accepts your secret key, not a JWT.
For async usage, use AsyncPayKoreApi with identical parameters, every method below has a 1:1 async equivalent:
from paykore import AsyncPayKoreApi
client = AsyncPayKoreApi(
token=os.environ["PAYKORE_API_KEY"],
base_url="https://api.paykore.com",
)
Wallets
# Create a wallet
wallet = client.wallets.create_wallet(
user_ref="user_123",
currency="NGN",
metadata={"plan": "premium"},
)
# wallet.status == "pending" initially
# wallet.account_number is empty until wallet.activated webhook fires
# Get a wallet and its current balance
result = client.wallets.get_wallet(id="wlt-uuid-here")
# result.wallet — the Wallet object
# result.balance_kobo — current posted balance in kobo
# List a wallet's transactions
page = client.wallets.list_wallet_transactions(
id="wlt-uuid-here",
limit=20,
cursor=None, # pass cursor from previous response to paginate
)
| Method | Returns |
|---|---|
client.wallets.create_wallet(user_ref, ...) | CreateWalletResponse |
client.wallets.get_wallet(id) | GetWalletResponse |
client.wallets.list_wallet_transactions(id, ...) | ListWalletTransactionsResponse |
create_wallet returns a 202 Accepted response — the wallet exists immediately in pending state, but account_number is empty until the MFB provisions it asynchronously. Listen for the wallet.activated webhook before showing the NUBAN to your user.
Transfers
import uuid
# P2P transfer — instant, no NIP fee, synchronous
transfer = client.transfers.p2p_transfer(
source_wallet="source-wallet-uuid",
dest_wallet="dest-wallet-uuid",
amount=500000, # kobo — ₦5,000
reference=str(uuid.uuid4()),
description="Payment for order #789",
)
# transfer.status == "completed"
# Bank transfer — async NIP, returns 202, settles via webhook
payout = client.transfers.bank_transfer(
source_wallet="source-wallet-uuid",
dest_account={
"bank_code": "058",
"account_number": "0123456789",
"account_name": "Ada Okafor",
},
amount=1000000, # kobo — ₦10,000
reference=str(uuid.uuid4()),
)
# payout.status == "processing"
# Final status arrives via transaction.completed or transaction.failed webhook
| Method | Returns |
|---|---|
client.transfers.p2p_transfer(source_wallet, dest_wallet, amount, reference, ...) | Transaction |
client.transfers.bank_transfer(source_wallet, dest_account, amount, reference, ...) | Transaction |
Payments
import uuid
# Create a QR payment request (merchant side)
qr = client.payments.create_qr_payment(
wallet_id="merchant-wallet-uuid",
amount=500000,
reference="order_789",
expires_in_seconds=300, # default 5 minutes
)
# qr.qr_code — base64-encoded QR image or payload string
# qr.expires_at — show countdown to user
# Pay a QR request (payer side)
payment = client.payments.pay_qr_payment(
qr_reference=qr.reference,
source_wallet="payer-wallet-uuid",
)
# Initiate a USSD payment
ussd = client.payments.initiate_ussd_payment(
wallet_id="wallet-uuid",
amount=500000,
reference="order_789",
phone="+2348012345678", # E.164 format
)
# ussd.ussd_code — display to user e.g. "*123*456#"
# ussd.session_id — use to poll status if needed
# ussd.expires_at — 3-minute TTL
# Split payment across multiple wallets
splits = client.payments.split_payment(
source_wallet="source-wallet-uuid",
reference="order_456",
splits=[
{"wallet_id": "seller-wallet-uuid", "amount": 824000},
{"wallet_id": "platform-wallet-uuid", "amount": 176000},
],
)
# splits — list of Transaction objects, one per destination
| Method | Returns |
|---|---|
client.payments.create_qr_payment(wallet_id, amount, reference, ...) | CreateQRPaymentResponse |
client.payments.pay_qr_payment(qr_reference, source_wallet) | Transaction |
client.payments.initiate_ussd_payment(wallet_id, amount, reference, phone) | InitiateUSSDPaymentResponse |
client.payments.split_payment(source_wallet, reference, splits) | list[Transaction] |
KYC
# Submit BVN — async, result arrives via kyc.verified or kyc.failed webhook
bvn_result = client.kyc.submit_bvn(
user_ref="user_123",
bvn="22190000000",
)
# bvn_result.status == "pending"
# Submit NIN
nin_result = client.kyc.submit_nin(
user_ref="user_123",
nin="12345678901",
)
# Get current KYC status for a user
status = client.kyc.get_kyc_status("user_123")
for verification in status.verifications:
print(f"type={verification.type} status={verification.status}")
| Method | Returns |
|---|---|
client.kyc.submit_bvn(user_ref, bvn) | SubmitVerificationResponse |
client.kyc.submit_nin(user_ref, nin) | SubmitVerificationResponse |
client.kyc.get_kyc_status(user_ref) | KYCStatusResponse |
Webhooks
# Register an endpoint
endpoint = client.webhooks.create_webhook(
url="https://yourapp.com/webhooks/paykore",
events=[
"transaction.completed",
"transaction.failed",
"wallet.activated",
"kyc.verified",
"kyc.failed",
],
)
# List registered endpoints
endpoints = client.webhooks.list_webhooks()
# Delete an endpoint
client.webhooks.delete_webhook(id="webhook-uuid")
| Method | Returns |
|---|---|
client.webhooks.create_webhook(url, events) | WebhookEndpoint |
client.webhooks.list_webhooks() | list[WebhookEndpoint] |
client.webhooks.delete_webhook(id) | None |
Error handling
All methods raise a subclass of ApiError on failure. Import it from the SDK's core module and branch on err.body["error"]:
from paykore.core import ApiError
try:
transfer = client.transfers.p2p_transfer(
source_wallet="source-wallet-uuid",
dest_wallet="dest-wallet-uuid",
amount=500000,
reference=str(uuid.uuid4()),
)
except ApiError as err:
match err.body.get("error"):
case "INSUFFICIENT_FUNDS":
# show user their balance
pass
case "VALIDATION_ERROR":
print(f"Bad request: {err.body.get('message')}")
case _:
# unexpected — log for investigation
print(f"PayKore error {err.status_code}: {err.body}")
raise
| Attribute | Type | Description |
|---|---|---|
err.status_code | int | HTTP status code (e.g. 422). |
err.body["error"] | str | Machine-readable code. Branch on this — it's stable across SDK versions. |
err.body["message"] | str | Human-readable description. For logging only. |
Async usage
Every method has an identical async counterpart via AsyncPayKoreApi:
import asyncio
from paykore import AsyncPayKoreApi
client = AsyncPayKoreApi(
token=os.environ["PAYKORE_API_KEY"],
base_url="https://api.paykore.com",
)
async def main() -> None:
wallet = await client.wallets.create_wallet(user_ref="user_123")
print(wallet.status) # "pending"
transfer = await client.transfers.p2p_transfer(
source_wallet="source-wallet-uuid",
dest_wallet="dest-wallet-uuid",
amount=500000,
reference=str(uuid.uuid4()),
)
print(transfer.status) # "completed"
asyncio.run(main())
The async client is a drop-in replacement — all method signatures are identical, every call just needs to be await-ed.
Raw response access
To read response headers (e.g. Retry-After on a 429), use .with_raw_response:
raw = client.wallets.with_raw_response.create_wallet(user_ref="user_123")
retry_after = raw.headers.get("Retry-After")
wallet = raw.data # CreateWalletResponse
Next steps
- Go SDK → — The Go equivalent.
- Webhooks: Verifying Signatures → — Python signature verification example.
- API Reference → — Full endpoint documentation.