Python client for the Airtel Money Uganda Open APIs (Collections, Disbursements, KYC, Account) with built-in request signing and PIN encryption.
Project description
airtelmoney-ug
Python client for the Airtel Money Uganda Open APIs — Collections, Disbursements,
KYC and Account — with built-in request signing (x-signature / x-key) and
PIN encryption, automatic OAuth2 token management and friendly error-code lookups.
Features
- OAuth2 client-credentials token handling with caching/auto-refresh.
- Automatic message signing for write APIs (AES-256-CBC payload + RSA-encrypted key/IV).
- Automatic RSA PIN encryption for disbursements.
- Encryption-key fetching and caching.
- Typed exceptions and a complete map of documented
response_codevalues. - Fully covered by offline unit tests.
Installation
pip install airtelmoney-ug
For local development:
pip install -e ".[dev]"
Quick start
from airtelmoney import AirtelMoney
client = AirtelMoney(
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET",
environment="staging", # or "production"
)
# Or load credentials from a JSON file (airtel_client_id / airtel_client_secret):
client = AirtelMoney.from_credentials_file("airtel_credentials.json")
# Or load from environment variables / a .env file (recommended):
client = AirtelMoney.from_env()
Default base URLs are
https://openapiuat.airtel.ug(staging) andhttps://openapi.airtel.ug(production). Passbase_url=...to override, e.g. for the pan-African endpointshttps://openapi.airtel.africa.
Configuration via .env
Install the optional dependency and copy .env.example to .env:
pip install "airtelmoney-py[env]"
cp .env.example .env
| Variable | Description | Default |
|---|---|---|
AIRTEL_CLIENT_ID |
Application client id (required) | — |
AIRTEL_CLIENT_SECRET |
Application client secret (required) | — |
AIRTEL_ENVIRONMENT |
staging or production |
staging |
AIRTEL_BASE_URL |
Explicit base URL (overrides environment) | — |
AIRTEL_COUNTRY |
X-Country header |
UG |
AIRTEL_CURRENCY |
X-Currency header |
UGX |
from airtelmoney import AirtelMoney
client = AirtelMoney.from_env() # reads .env if python-dotenv is installed
Usage
Collections
# USSD push payment (payload signed automatically using the consumer RSA key)
res = client.collection.payment(
reference="Order #123",
msisdn="752604392",
amount=1000,
)
txn_id = res["data"]["transaction"]["id"]
# Transaction enquiry
client.collection.enquiry(txn_id)
# Refund a successful transaction
client.collection.refund("CI210104.1105.C00018")
Disbursements
# PIN is RSA-encrypted with the consumer public key automatically
res = client.disbursement.payment(
msisdn="752604392",
amount=500,
reference="payout-1",
pin="1234",
)
client.disbursement.enquiry(res["data"]["transaction"]["id"])
# Already have an encrypted PIN? Pass it directly:
client.disbursement.payment(
msisdn="752604392", amount=500, reference="p-2",
encrypted_pin="KYJExln8rZwb14G1K5UE5YF/lD7KheNUM171MUEG3/f/QD8nmNKRsa44",
)
KYC and Account
client.kyc.user_enquiry("256752604392")
client.account.balance()
Callbacks
Airtel posts transaction updates to your configured callback URL. For authenticated callbacks you can verify the signature:
# payload is the decoded JSON body Airtel POSTed to your callback URL
valid = client.collection.verify_callback_hash(payload, secret="YOUR_CLIENT_SECRET")
Manual signing / encryption
The signing primitives are exposed if you need them directly:
from airtelmoney import sign_payload, encrypt_pin
public_key = client.get_encryption_key() # base64 DER RSA public key
signed = sign_payload(public_key, {"reference": "1234", "transaction": {"id": "x"}})
# signed.payload -> exact JSON body you MUST send
# signed.x_signature -> x-signature header
# signed.x_key -> x-key header
enc_pin = encrypt_pin(public_key, "1234")
How signing works
- Generate a random 256-bit AES key and 128-bit IV.
- Base64-encode the key and IV.
- Fetch the consumer RSA public key (
client.get_encryption_key()). - Encrypt the JSON payload with
AES/CBC/PKCS5Padding→x-signatureheader. - Concatenate
key:iv(both base64) and RSA-encrypt with the public key →x-keyheader.
The server decrypts key:iv with its private RSA key, re-encrypts the received payload
and compares it against x-signature. A mismatch yields a Forbidden
(DP00800001026 / DP00900001016).
Error handling
from airtelmoney import AirtelAPIError, describe, is_success
try:
res = client.account.balance()
except AirtelAPIError as exc:
print(exc.status_code, exc.response_code, exc.message)
# Interpret any documented response code
info = describe("DP00800001007") # ResponseCode(code=..., reason='Not enough balance', ...)
is_success("DP00800001001") # True
Response codes
All documented codes are available via airtelmoney.RESPONSE_CODES and describe(),
covering encryption, collection (DP008...), disbursement (DP009...), user
enquiry (DP022...) and balance (DP021...).
Testing
pip install -e ".[dev]"
pytest
License
MIT
Project details
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 airtelmoney_py-1.0.6.tar.gz.
File metadata
- Download URL: airtelmoney_py-1.0.6.tar.gz
- Upload date:
- Size: 20.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
983baa7e20c8e01c218b755f4408d53c0dbdb425552d7128058711e69626d46e
|
|
| MD5 |
00da7915506c31e94106400fe97b78a0
|
|
| BLAKE2b-256 |
c1b808538e2fd298d00b8f897501e94af4aaad226f4db5bdb81fae9a25334b64
|
Provenance
The following attestation bundles were made for airtelmoney_py-1.0.6.tar.gz:
Publisher:
publish.yml on robin-001/airtelmoney-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
airtelmoney_py-1.0.6.tar.gz -
Subject digest:
983baa7e20c8e01c218b755f4408d53c0dbdb425552d7128058711e69626d46e - Sigstore transparency entry: 2006892014
- Sigstore integration time:
-
Permalink:
robin-001/airtelmoney-py@083ce2199bf7c0faf39c97d5412187188fb07fd1 -
Branch / Tag:
refs/tags/v1.0.6 - Owner: https://github.com/robin-001
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@083ce2199bf7c0faf39c97d5412187188fb07fd1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file airtelmoney_py-1.0.6-py3-none-any.whl.
File metadata
- Download URL: airtelmoney_py-1.0.6-py3-none-any.whl
- Upload date:
- Size: 19.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e8e1f18b8470b5ac74e2a7c6753553d2434ab2ec2a4da7ffaf81bbd241695d55
|
|
| MD5 |
5832a9147370717ff2f1619e9f974579
|
|
| BLAKE2b-256 |
c5eb3ebc4d1cb5362995a7a8ffc590186f4031d9f6835fc23b3b805e4af08131
|
Provenance
The following attestation bundles were made for airtelmoney_py-1.0.6-py3-none-any.whl:
Publisher:
publish.yml on robin-001/airtelmoney-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
airtelmoney_py-1.0.6-py3-none-any.whl -
Subject digest:
e8e1f18b8470b5ac74e2a7c6753553d2434ab2ec2a4da7ffaf81bbd241695d55 - Sigstore transparency entry: 2006892129
- Sigstore integration time:
-
Permalink:
robin-001/airtelmoney-py@083ce2199bf7c0faf39c97d5412187188fb07fd1 -
Branch / Tag:
refs/tags/v1.0.6 - Owner: https://github.com/robin-001
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@083ce2199bf7c0faf39c97d5412187188fb07fd1 -
Trigger Event:
push
-
Statement type: