Skip to main content

django-vtpass

The complete VTpass integration for Django. Everything VTpass sells, from airtime, data, TV, electricity, exam PINs, insurance, international airtime and bank transfers to bulk SMS, wired end to end with the plumbing a real fintech app needs: customer wallets, fees, discounts, cashback, webhooks, automatic requery, notifications, Celery, a REST API and an admin.

Building an OPay/PalmPay-style bills app? Install this, add your keys, and spend your time on the product instead of the integration.

pip install django-vtpass            # core
pip install "django-vtpass[all]"     # + REST API (DRF) + Celery

Why this package

Complete Every documented VTpass endpoint: pay, requery, verify, balance, categories, services, variations, options, Smile email lookup, international airtime (countries, product types, operators), bank transfer (bank list, account-name lookup, transfer), Universal Insurance lookups, and all 7 Messaging routes (normal, DND, DND-fallback, SIMHOST, SIMHOST-fallback, v1 and v2) plus SMS balance. Products VTpass adds later work through the generic purchase.
Money-safe Decimal everywhere. Wallet rows are locked during debits and every ledger entry has a unique reference, so purchases can't overdraw and refunds or cashback can't be applied twice. Purchases are never retried blindly and a timeout is never treated as failure: unclear outcomes stay pending until requery or the webhook settles them.
Secure Webhook secret token (constant-time compare), optional IP allowlist, and webhook payloads re-verified with VTpass before any money moves. Price-tampering protection, idempotency keys, per-user throttles and daily limits. Secrets, tokens and PINs are masked in logs, and merchant-side errors are never shown to customers.
Optional by design Use only what you need: the raw HTTP client, the high-level services, the built-in wallet (or your own), pricing rules, notifications, Celery, the REST API, the admin. Each one is opt-in.
Pluggable Extension points are Django signals (pre_purchase can veto, e.g. for PIN or KYC checks) and swappable backends for wallet, pricing and notifications.
Scales Thread-local pooled HTTP sessions, no DB locks held during network I/O, indexed requery queue, cached catalogue, and Celery tasks with backoff.

Quick start

# settings.py
INSTALLED_APPS = [..., "vtpass"]           # add "rest_framework" too if you use the API

VTPASS = {
    "API_KEY": env("VTPASS_API_KEY"),
    "PUBLIC_KEY": env("VTPASS_PUBLIC_KEY"),
    "SECRET_KEY": env("VTPASS_SECRET_KEY"),
    "SANDBOX": True,                                        # False in production
    "WALLET_BACKEND": "vtpass.wallets.ModelWalletBackend",  # optional built-in wallet
    "WEBHOOK": {"TOKEN": env("VTPASS_WEBHOOK_TOKEN")},
}

# urls.py
urlpatterns = [
    path("vtpass/", include("vtpass.urls")),             # webhook (no DRF needed)
    path("api/vtpass/", include("vtpass.api.urls")),     # optional REST API
]
python manage.py migrate
python manage.py vtpass_sync_catalog      # optional: cache categories, services and plans locally

Register https://your-domain/vtpass/webhook/<WEBHOOK TOKEN>/ as the callback URL in your VTpass dashboard, and whitelist your server IP and products there.

Buy things

from vtpass.services import VTpass

vt = VTpass()

# Airtime: the network is auto-detected from the number
txn = vt.airtime.buy("08031234567", 500, user=request.user)
txn.status            # "successful" | "pending" | "failed"

# Data
vt.data.plans("mtn")                                   # list plans
vt.data.buy("08031234567", "mtn-10mb-100", user=user)

# Electricity (prepaid token comes back in txn.token)
vt.electricity.verify("ikedc", "1111111111111", "prepaid")
txn = vt.electricity.buy("ikedc", "1111111111111", "prepaid", 5000, phone="08031234567", user=user)
txn.token, txn.vend_details["units"]

# TV
vt.tv.verify("dstv", "1212121212")                     # name, bouquet, Renewal_Amount
vt.tv.renew("dstv", "1212121212", phone="08031234567", user=user)
vt.tv.change("gotv", "1212121212", "gotv-max", phone="08031234567", user=user)
vt.tv.showmax("08031234567", "full", user=user)

# Education
vt.education.waec_result_checker("08031234567", quantity=2, user=user)   # vend_details["cards"]
vt.education.waec_registration("08031234567", user=user)
vt.education.verify_jamb_profile("0123456789", "utme")
vt.education.jamb("0123456789", "utme", "08031234567", user=user)

# Internet
vt.internet.verify_smile_email("tester@sandbox.com")
vt.internet.buy_smile("08011111111", "516", "08031234567", user=user)
vt.internet.buy_spectranet("08031234567", "spectranet-1000", quantity=1, user=user)

# Insurance (third-party motor)
vt.insurance.options("brand"); vt.insurance.options("model", parent="TOY")
vt.insurance.third_party_motor(plate_number="AAA123BB", variation_code="1", ..., user=user)

# Personal accident insurance
vt.insurance.personal_accident(variation_code="...", phone="0803...", full_name="Ada Obi",
                               address="Lagos", dob="1990-05-01", next_kin_name="Ngozi Obi",
                               next_kin_phone="0802...", business_occupation="Trader", user=user)

# Bank transfer (account name is always verified before money is sent)
vt.bank.banks()
vt.bank.verify_account("gtb", "1234567890")          # {"account_name": "..."}
vt.bank.transfer("gtb", "1234567890", 5000, phone="08031234567", user=user)

# International airtime and data
vt.international.countries()
vt.international.operators("GH", product_type_id=1)
vt.international.buy(recipient="233241234567", country_code="GH", operator_id="5",
                     product_type_id="1", variation_code="...", email="a@b.com",
                     phone="08031234567", amount=2000, user=user)

# Bulk SMS
vt.sms.send(["08031234567", "08021234567"], "Your OTP is 123456", sender="MyBrand")
vt.sms.send(numbers, "Hello", route="dnd")          # reach DND numbers
vt.sms.balance()

# Anything else VTpass offers, by service ID
vt.purchase(service_id="some-new-service", phone="0803...", amount=1000, billers_code="...")

vt.balance()             # your VTpass merchant balance
vt.quote("mtn", 1000)    # fee / discount / cashback / amount payable, without buying

Every buy call accepts user, email, idempotency_key, metadata, charge_wallet, save_beneficiary, channel and request.


How a purchase flows

quote ─► pre_purchase signal (PIN / KYC / fraud veto) ─► [DB txn: lock wallet, create Transaction, debit]
      ─► POST /api/pay (no locks held) ─► state machine:
            delivered  → SUCCESSFUL → cashback credited (once)
            failed     → FAILED     → refund credited (once)
            timeout / 099 / 5xx      → PENDING → requery (beat/cron/webhook) → settles later
            reversed (even after success) → REVERSED → refund net of cashback (once)

Fees, commission and cashback

VTpass pays you a commission on each sale. Pricing rules (admin, DB, or settings) decide what you do with that margin:

  • Convenience fee: flat or %, with a minimum and a cap (e.g. ₦100 on electricity)
  • Instant discount: flat or %, capped (e.g. 2% off airtime)
  • Cashback: flat or %, capped, credited to the wallet after delivery (e.g. 3% back on data)

Rules can target a category, service, single plan, user group (agent/reseller tiers) and amount band, and can be time-boxed for promos. The most specific rule wins, then priority. Every transaction records amount (face value), fee, discount, amount_charged, cashback, VTpass cost and vtpass_commission, and exposes profit. Staff get an earnings report at GET reports/earnings/.

Optional pieces

Feature Turn on with
Built-in wallet "WALLET_BACKEND": "vtpass.wallets.ModelWalletBackend"; fund it with vtpass.wallets.fund_wallet(user, amount, reference) from your Paystack/Flutterwave webhook
Your own ledger Subclass vtpass.wallets.BaseWalletBackend
No wallet at all Leave WALLET_BACKEND as None and take payment yourself before calling buy
Notifications (SMS/email/custom) "NOTIFICATIONS": {"ENABLED": True, "BACKENDS": [...]}
Celery "USE_CELERY": True, then add the beat schedule from vtpass/tasks.py
REST API pip install django-vtpass[drf] and include vtpass.api.urls
Daily limits "LIMITS": {"DAILY_AMOUNT_PER_USER": "200000", "DAILY_COUNT_PER_USER": 50}
Low merchant balance alert "LOW_BALANCE_THRESHOLD": 50000 → merchant_balance_low signal

No Celery? Run python manage.py vtpass_requery_pending from cron every minute.

Documentation

What VTpass does not offer

Betting-wallet funding and water bills are not VTpass products. Neither appears in VTpass's API documentation, the live site or the sandbox product list, so this package doesn't pretend to support them. If VTpass adds them later, vt.purchase(service_id=...) works without a package update.

Requirements

Python 3.9+ and Django 4.2+. DRF 3.14+ and Celery 5.3+ are optional. Tested on Django 4.2, 5.2 and 6.1.

License

MIT

Metadata

Release files for django-vtpass 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-vtpass 1.0.0
File Size Uploaded
django_vtpass-1.0.0.tar.gz 112.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-vtpass 1.0.0
File Interpreter ABI Platform
django_vtpass-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 218.4 kB

Release files / django_vtpass-1.0.0.tar.gz

Download URL django_vtpass-1.0.0.tar.gz
Size 112.0 kB
Tags Source
SHA-256 checksum
How to use checksums
7c7fa29b11c1c33148551593dd10b9d1b9ee08f662a0ed758923f4e2886483e1
BLAKE2b-256 checksum
How to use checksums
f5f902fc39bce2ecc2f89b50a63afd4e82f51c92ce7971e6191080b94bd8bc90
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release files / django_vtpass-1.0.0-py3-none-any.whl

Download URL django_vtpass-1.0.0-py3-none-any.whl
Size 106.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
60010597fa6c3e86f178a42d8e98c95dd10db4322c6ecc7fb4935d802ce66fc6
BLAKE2b-256 checksum
How to use checksums
68b3f65dd5075313dc33a5ead3f781bce48c291df49052f3684b24b632781c59
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page