Skip to main content

nylonpay-py

Server-side SDK for integrating Nylon Pay payment operations into your Python application.

Full documentation

Install

pip install nylonpay-py

Requires Python 3.10+.

Quick Start

Create a client, initiate a payment, subscribe to events, and wait for completion.

from nylonpay import create_nylon_pay
import secrets

nylonpay = create_nylon_pay(
    api_key="npk_test_...",
    api_secret="nps_test_...",
)

payment = nylonpay.collect_payment(
    amount=10000,
    currency="UGX",
    customer={"name": "Jane", "phone_number": "+256700000000"},
    description="Order #1234",
    reference=secrets.token_hex(7),
)

def on_success(data):
    print(f"Paid: {data.transaction.reference}")

def on_failed(data):
    print(f"Failed: {data.error}")

payment.on("success", on_success)
payment.on("failed", on_failed)

tx = payment.wait()
if tx is not None:
    print(f"Transaction {tx.reference} completed")

Configuration

Create the SDK instance with create_nylon_pay(). All options are keyword arguments.

Your API key determines the mode — npk_sandbox_... for test mode, npk_live_... for production. There is no separate mode option.

Field Required Default Description
api_key Yes Must start with npk_
api_secret Yes Must start with nps_
base_url No https://api.nylonpay.nilesquad.com/api/services Override for a custom endpoint
timeout_ms No 30000 Request timeout in milliseconds
max_retries No 3 Retry count for failed requests
max_poll_interval_ms No 2000 Interval between status checks
max_poll_duration_ms No (none) Optional cap on total wait time. Omit to wait until terminal.
max_poll_attempts No (none) Optional cap on status checks. Omit to wait until terminal.
on_delayed No "wait" "return" hands back a delayed still-pending payment; "wait" keeps polling
force No False Bypass instance cache and create a fresh instance
hooks No None Lifecycle hooks (SdkHooks) for cross-cutting concerns
http_client No None httpx.Client for testing injection
nylonpay = create_nylon_pay(
    api_key="npk_test_...",
    api_secret="nps_test_...",
    timeout_ms=15000,
    max_retries=5,
)

The factory caches instances by api_key + base_url + sha256(api_secret). Rotating the secret produces a different cache key and a fresh instance. Pass force=True to bypass caching.

Operations

All operations accept keyword arguments. Nested types (customer, destination, items) accept plain dicts — no need to import dataclasses.

collect_payment

Initiate a payment collection. Returns a PaymentInstance with event-driven updates.

payment = nylonpay.collect_payment(
    amount=10000,
    currency="UGX",
    customer={"name": "Jane", "phone_number": "+256700000000"},
    description="Order #1234",
    method="mobileMoney",
    reference="ORDER-2026-001",
)

def on_success(data):
    print(f"Paid: {data.transaction.reference}")

def on_failed(data):
    print(f"Failed: {data.error}")

payment.on("success", on_success)
payment.on("failed", on_failed)

reference is optional and auto-generated if omitted. A supplied reference must be 13 to 15 characters; the SDK raises SdkException with category validation otherwise. A raw UUID is 36 characters and will be rejected — use a short id of your own or omit the field.

collect_payment_and_resolve

Block until the collection reaches a terminal state. Single request/response — the server checks status internally, no client-side waiting.

result = nylonpay.collect_payment_and_resolve(
    amount=5000,
    currency="UGX",
    customer={"name": "Jane", "phone_number": "+256700000000"},
    description="Quick payment",
)

if result.is_ok:
    print("Paid:", result.value.reference)

make_payout

Disburse funds to a destination account. Returns a PaymentInstance with event-driven updates.

payout = nylonpay.make_payout(
    amount=50000,
    currency="UGX",
    customer={"name": "Jane", "phone_number": "+256700000000"},
    destination={
        "account_holder_name": "Jane Doe",
        "account_number": "123456",
    },
    description="Refund for order #1234",
)

tx = payout.wait()

make_payout_and_resolve

Block until the payout reaches a terminal state. Single request/response.

result = nylonpay.make_payout_and_resolve(
    amount=50000,
    currency="UGX",
    customer={"name": "Jane", "phone_number": "+256700000000"},
    destination={
        "account_holder_name": "Jane Doe",
        "account_number": "123456",
    },
    description="Refund",
)

if result.is_ok:
    print("Payout completed:", result.value.reference)

Payout Lifecycle

make_payout returns immediately with a reference for tracking and idempotent retries. The payout status flows through several stages:

  • pending — Payout accepted and queued for processing
  • processing — Provider is actively handling the disbursement
  • on_hold — Payout is under review (liquidity or compliance checks). Non-terminal; will complete to successful, failed, or cancelled.
  • successful — Payout completed; funds sent to destination
  • failed — Payout failed; funds refunded to merchant account
  • cancelled — Payout was cancelled by the merchant

Polling and webhooks: Monitor payout progress by:

  1. Subscribing to "processing" events (covers pending, processing, and on_hold states)
  2. Listening for terminal events: "success", "failed", "cancelled"
  3. Receiving webhook notifications at your configured endpoint

The SDK treats on_hold as a non-terminal status — polling continues automatically until the payout reaches a terminal state. Use the status_text field for human-readable details about review holds.

payout = nylonpay.make_payout(
    amount=50000,
    currency="UGX",
    customer={"name": "Jane", "phone_number": "+256700000000"},
    destination={
        "account_holder_name": "Jane Doe",
        "account_number": "123456",
    },
    description="Refund for order #1234",
)

def on_processing(data):
    # Payout is in flight (pending, processing, or on_hold)
    if data.transaction and data.transaction.status == "on_hold":
        print("Payout under review:", data.transaction.status_text)
        # "Payout is being reviewed and will complete shortly"

payout.on("processing", on_processing)

def on_success(data):
    print("Payout complete:", data.transaction.reference)

payout.on("success", on_success)

# Or wait for terminal state
tx = payout.wait()
if tx is not None:
    print("Final status:", tx.status)

get_status

One-shot status check for a transaction. Does not wait — returns the current server-side state.

result = nylonpay.get_status(reference="ORDER-2026-001")
if result.is_ok:
    print(result.value.status)

get_transaction

Look up a full transaction record by id or reference. At least one must be provided.

result = nylonpay.get_transaction(reference="ORDER-2026-001")
if result.is_ok:
    print(result.value.failure_reason)

verify_phone

Pre-validate a phone number and get the registered name.

result = nylonpay.verify_phone(phone_number="+256700000000")
if result.is_ok and result.value.verified:
    print("Registered to:", result.value.customer_name)

Phone numbers are normalized automatically — any common format works: +256 700 000 000, 0700000000, 256700000000 are all accepted.

create_invoice

Generate a hosted payment link. Card payments are only supported via this hosted flow — card details never reach your servers.

result = nylonpay.create_invoice(
    amount=25000,
    currency="UGX",
    description="Monthly subscription",
    items=[{"name": "Pro Plan", "quantity": 1, "unit_price": 25000}],
    redirect_url="https://myapp.com/thank-you",
)

if result.is_ok:
    print("Invoice URL:", result.value.url)

verify_webhook_signature

Verify incoming webhook payloads before processing. Operates on raw payload bytes or string — never re-serialize parsed JSON, which would alter the signed content.

from nylonpay import VerifyWebhookInput, verify_webhook_signature

is_valid = verify_webhook_signature(
    VerifyWebhookInput(
        payload=raw_payload_bytes,
        signature=signature_header,
        secret="nps_...",
    )
)

if not is_valid:
    # Reject — payload did not originate from Nylon Pay
    ...

The standalone function takes a single VerifyWebhookInput. The client method is the keyword-argument variant of the same check:

is_valid = nylonpay.verify_webhook_signature(
    payload=raw_payload_bytes,
    signature=signature_header,
    secret="nps_...",
)

The verification checks authenticity and freshness. Pass tolerance_seconds=0 to disable the freshness check. Returns True only when both checks pass. Never raises.

PaymentInstance Events

collect_payment and make_payout return a PaymentInstance with event-driven updates.

Event Description
processing Transaction is being processed (covers pending, processing, and on_hold states)
success Transaction completed successfully
failed Transaction failed
cancelled Transaction was cancelled
error Network or server error

For payouts specifically, on_hold indicates the payout is under review (liquidity or compliance checks). Polling continues automatically; use transaction?.status_text for a human-readable explanation.

def on_success(data):
    print(f"Paid: {data.transaction.reference}")

payment.on("success", on_success)
payment.once("success", on_success)  # fires at most once
payment.off("success", on_success)   # remove handler

tx = payment.wait()

wait()

Block until terminal state. Returns Transaction on success, None on failure, cancellation, or error. Never raises. Default: waits until the payment finishes with no built-in time limit.

tx = payment.wait()
if tx is not None:
    print("paid:", tx.reference)
else:
    print("failed or timed out")

Delayed payments (v0.4+): After about three minutes in flight, responses may include delayed=True. Use on_delayed="return" to get the still-pending payment back and rely on webhooks:

nylonpay = create_nylon_pay(
    api_key="npk_test_...",
    api_secret="nps_test_...",
    on_delayed="return",
)

result = nylonpay.collect_payment_and_resolve(...)
if result.is_ok and result.value.delayed and result.value.status == "pending":
    # Still in flight — handle via webhooks
    ...

Set max_poll_duration_ms to restore a bounded wait (~5 minutes previously).

Error Handling

Operations that return Result[T, str] use the SDK's Result type. Check .is_ok / .is_err before accessing .value / .error.

from nylonpay import parse_error

result = nylonpay.get_status(reference="ORDER-2026-001")
if result.is_err:
    error = parse_error(result.error)
    if error.retryable:
        # Retry the operation
        ...
    print(f"Category: {error.category}, Message: {error.message}")

SdkException

Operations that throw on initiation failure (invalid input, missing fields) raise SdkException. It carries structured error information.

from nylonpay import SdkException

try:
    payment = nylonpay.collect_payment(
        amount=100,
        currency="UGX",
        customer={"name": "Jane", "phone_number": "+256700000000"},
        description="Test",
    )
except SdkException as e:
    print(f"Category: {e.category}")
    print(f"Retryable: {e.retryable}")
    print(f"Message: {e}")

Error Categories

Category Description Retryable
auth Invalid credentials No
validation Invalid input No
limit Account limit exceeded No
rate_limit Rate limited Yes
account Account issue (suspended, etc.) No
provider Provider rejected the transaction No
duplicate Reference already used No
not_found Resource not found No
internal Server error Varies
network Network error Yes
timeout Request timed out Yes

Hooks

Lifecycle hooks fire on every matching operation. Use them for cross-cutting concerns like logging, audit trails, and payload enrichment.

Each hook is wrapped in SdkHook which provides a safe boundary — if the hook's fn raises an exception, it is routed to on_error instead of crashing the payment flow.

from nylonpay import create_nylon_pay, SdkHook, SdkHooks

def log_before_collect(input):
    print(f"About to collect: {input.reference}")
    return input  # can mutate and return, or return None to skip

def log_after_collect(result, after_input):
    if result.is_ok:
        print(f"Collected: {result.value.reference}")
    else:
        print(f"Failed: {result.error}")

def on_hook_error(exc):
    print(f"Hook failed: {exc}")

hooks = SdkHooks(
    before_collect=SdkHook(fn=log_before_collect, on_error=on_hook_error),
    after_collect=SdkHook(fn=log_after_collect, on_error=on_hook_error),
)

nylonpay = create_nylon_pay(
    api_key="npk_test_...",
    api_secret="nps_test_...",
    hooks=hooks,
)

Available hooks: before_collect, after_collect, before_payout, after_payout. Each can be None (no-op).

Supported Currencies

USD, EUR, GBP, KES, UGX, TZS, RWF

Links

License

MIT

Metadata

Release files for nylonpay-py 0.5.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 nylonpay-py 0.5.0
File Size Uploaded
nylonpay_py-0.5.0.tar.gz 102.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nylonpay-py 0.5.0
File Interpreter ABI Platform
nylonpay_py-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 146.6 kB

Release files / nylonpay_py-0.5.0.tar.gz

Download URL nylonpay_py-0.5.0.tar.gz
Size 102.0 kB
Tags Source
SHA-256 checksum
How to use checksums
06538a4885bd6f0948f8941576d3db22ce04a6605b41f26b1d581a99fb9488fa
BLAKE2b-256 checksum
How to use checksums
44da6ce3fac21a274dc97a0b9a6970d7137197f2689a255e6dfc52c7f8f3e8ba
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release files / nylonpay_py-0.5.0-py3-none-any.whl

Download URL nylonpay_py-0.5.0-py3-none-any.whl
Size 44.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
457fa470ba89d975ceaf0cb71ff3387c85549b6caf30dbfdfea7009cde58100c
BLAKE2b-256 checksum
How to use checksums
c5756f36fd06ee263334c3321d12a802bd74630fdf47a120dac44364c8875b9f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release history Release notifications | RSS feed

0.5.1

2 release files

This release

0.5.0 This release

2 release files

0.4.0

2 release files

0.2.0

2 release files

0.1.0

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