← All docs

Python SDK — Full Guide

Everything in Python: install → create invoice → get pay link → receive payment → redirect customer → verify webhooks.

1. Install dependencies

bash
# Python 3.10+
python -m venv .venv && source .venv/bin/activate
pip install requests flask python-dotenv

2. Set credentials

Get your API key + secret from your API page. The webhook secret is shown once when you add a webhook URL — save it now, you cannot view it later.

bash
# .env  — keep this file out of git
API_KEY="ci_live_xxxxxxxxxxxxxxxx"
API_SECRET="sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
WEBHOOK_SECRET="whsec_xxxxxxxxxxxxxxxxxxxx"   # shown ONCE when you create the webhook

3. Shared client.py (used by every example)

Save as client.py. Every snippet below does from client import api.

python
# client.py — one shared Python client used by every example below
import os, requests
from dotenv import load_dotenv
load_dotenv()

API_BASE = "https://pay.itemodeverify.com"
KEY      = os.environ["API_KEY"]
SECRET   = os.environ["API_SECRET"]

# Auth is a single Bearer header: "<key>:<secret>"
AUTH = {"Authorization": f"Bearer {KEY}:{SECRET}"}

def api(method: str, path: str, **kw):
    r = requests.request(method, f"{API_BASE}{path}",
                         headers={**AUTH, "Content-Type": "application/json", **kw.pop("headers", {})},
                         timeout=15, **kw)
    if r.status_code >= 400:
        raise RuntimeError(f"{method} {path} -> {r.status_code} {r.text}")
    return r.json() if r.content else {}

Auth header format: Authorization: Bearer KEY:SECRET — the colon is required.

4. Check if your API key is active

Run this once — it pings the API and prints whether your key works.

python
# check_key.py — run once to confirm credentials work
from client import api

def check():
    try:
        api("GET", "/api/public/v1/invoices?limit=1")
        print("✅ API key is ACTIVE")
    except RuntimeError as e:
        msg = str(e)
        if "401" in msg and "invalid_key"   in msg: print("❌ Key not found or disabled")
        elif "401" in msg and "invalid_secret" in msg: print("❌ Secret does not match this key")
        elif "401" in msg: print("❌ Missing/garbled Authorization header")
        else: print("❌", msg)

if __name__ == "__main__":
    check()

200 = active  •  invalid_key = wrong/disabled  •  invalid_secret = secret doesn't match.

5. Create an invoice and get the pay link

The response includes pay_url — the hosted payment page with QR code + crypto address. Send it to your customer.

python
# create_invoice.py
from client import api

def create_invoice(amount, currency="USDT", *, description="Order",
                   customer_email=None, success_redirect_url=None,
                   tolerance_pct=None, expires_in_minutes=None):
    body = {
        "amount": amount,                       # number, e.g. 10 or 9.99
        "currency": currency,                   # "USDT" | "BTC" | "ETH" | ...
        "description": description,
        "customer_email": customer_email,
        "success_redirect_url": success_redirect_url,  # where to send buyer after pay
        "tolerance_pct": tolerance_pct,         # e.g. 1 = accept ±1% underpay
        "expires_in_minutes": expires_in_minutes,
    }
    body = {k: v for k, v in body.items() if v is not None}
    return api("POST", "/api/public/v1/invoices", json=body)

if __name__ == "__main__":
    inv = create_invoice(
        amount=10, currency="USDT",
        description="Pro plan – 1 month",
        customer_email="buyer@example.com",
        success_redirect_url="https://yoursite.com/thanks?inv={id}",
    )
    print("Invoice ID :", inv["id"])
    print("Pay link   :", inv["pay_url"])     # <-- send this to the customer
    print("Wallet     :", inv["wallet_id"])
    print("Expires at :", inv["expires_at"])
    print("Status     :", inv["status"])      # "pending" initially

Tip: put {id} in success_redirect_url and we substitute it after payment.

6. Fetch, list, and cancel invoices

python
# get_invoice.py — fetch one invoice
from client import api

def get_invoice(invoice_id: str):
    return api("GET", f"/api/public/v1/invoices/{invoice_id}")

if __name__ == "__main__":
    inv = get_invoice("inv_abc123")
    print(inv["status"], inv["amount"], inv["currency"])
python
# list_invoices.py
from client import api
print(api("GET", "/api/public/v1/invoices?limit=20&status=pending"))

# cancel_invoice.py
from client import api
print(api("POST", "/api/public/v1/invoices/inv_abc123/cancel"))

7. Poll until the invoice is paid

Polling works if you can't run a public webhook server (e.g. local script, CLI tool). For production, prefer webhooks (next section).

python
# poll_status.py — wait until the invoice is paid (or expires)
import time
from client import api

def wait_for_payment(invoice_id: str, timeout_s: int = 30 * 60, every: int = 10):
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        inv = api("GET", f"/api/public/v1/invoices/{invoice_id}")
        print("status:", inv["status"])
        if inv["status"] in ("paid", "expired", "cancelled"):
            return inv
        time.sleep(every)
    return {"status": "timeout"}

if __name__ == "__main__":
    final = wait_for_payment("inv_abc123")
    if final["status"] == "paid":
        print("✅ Paid:", final["paid_amount"], final["currency"], "tx:", final["tx_hash"])
    else:
        print("❌ Not paid:", final["status"])

8. Full checkout flow with redirect

End-to-end Flask example: customer fills a form → we create an invoice → redirect to the hosted pay page → after they pay we redirect them back to /thanks.

python
# checkout_app.py — minimal Flask checkout: customer -> pay link -> redirect back
from flask import Flask, request, redirect, render_template_string
from client import api

app = Flask(__name__)

CHECKOUT_HTML = """
<form method="POST" action="/checkout">
  <input name="email" type="email" placeholder="you@email.com" required>
  <input name="amount" type="number" step="0.01" value="10" required>
  <button>Pay with crypto</button>
</form>"""

@app.get("/")
def home():
    return render_template_string(CHECKOUT_HTML)

@app.post("/checkout")
def checkout():
    inv = api("POST", "/api/public/v1/invoices", json={
        "amount": float(request.form["amount"]),
        "currency": "USDT",
        "customer_email": request.form["email"],
        "description": "Website order",
        # After paying, our hosted pay page redirects the buyer here.
        # {id} is replaced with the invoice id automatically.
        "success_redirect_url": "http://localhost:5000/thanks?inv={id}",
    })
    # Send the customer to the hosted pay page (shows QR + crypto address)
    return redirect(inv["pay_url"], code=302)

@app.get("/thanks")
def thanks():
    inv_id = request.args.get("inv")
    if not inv_id:
        return "Missing invoice id", 400
    inv = api("GET", f"/api/public/v1/invoices/{inv_id}")
    if inv["status"] == "paid":
        return f"✅ Thanks! Received {inv['paid_amount']} {inv['currency']}. TX: {inv['tx_hash']}"
    return f"⌛ We haven't confirmed your payment yet (status: {inv['status']}). Refresh in a minute."

if __name__ == "__main__":
    app.run(port=5000, debug=True)

Run python checkout_app.py, then open http://localhost:5000.

9. Receive & verify webhooks (IPN)

Add your public URL as a webhook in API → Webhooks. Every delivery is signed with HMAC-SHA256 in the X-Webhook-Signature header. We retry failed deliveries, so your handler must be idempotent.

python
# webhook_server.py — receive paid/expired events from our servers
import os, hmac, hashlib, json
from flask import Flask, request, abort
from dotenv import load_dotenv
load_dotenv()

app = Flask(__name__)
WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"].encode()

def verify(raw: bytes, signature: str) -> bool:
    expected = hmac.new(WEBHOOK_SECRET, raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

@app.post("/ipn")
def ipn():
    raw = request.get_data()                            # MUST be the raw body
    sig = request.headers.get("X-Webhook-Signature", "")
    if not verify(raw, sig):
        abort(401, "bad signature")

    event = json.loads(raw)
    etype = event["type"]                               # invoice.paid, .expired, .cancelled, .created
    inv   = event["data"]
    print(etype, inv["id"], inv["status"])

    if etype == "invoice.paid":
        # ✅ Fulfil the order in your DB. This handler MUST be idempotent —
        # we may retry deliveries until you reply 2xx.
        mark_order_paid(inv["id"], inv["paid_amount"], inv["currency"], inv["tx_hash"])
    elif etype == "invoice.expired":
        mark_order_expired(inv["id"])

    return "", 200

def mark_order_paid(invoice_id, amount, currency, tx_hash): ...
def mark_order_expired(invoice_id): ...

if __name__ == "__main__":
    # Expose via:  ngrok http 8080   then paste the https URL in API > Webhooks
    app.run(port=8080)

⚠️ Always hash the raw request body. Re-serializing parsed JSON will break the signature.

10. Test your webhook locally

Fires a fake invoice.paid event at your endpoint with a valid signature — handy for unit tests.

python
# test_webhook.py — simulate a paid event with a valid signature
import hmac, hashlib, json, requests, os
from dotenv import load_dotenv
load_dotenv()

SECRET = os.environ["WEBHOOK_SECRET"].encode()
URL    = "http://localhost:8080/ipn"

body = json.dumps({
    "type": "invoice.paid",
    "data": {
        "id": "inv_test", "status": "paid",
        "amount": 10, "paid_amount": 10, "currency": "USDT",
        "tx_hash": "0xdeadbeef",
    },
}).encode()
sig = hmac.new(SECRET, body, hashlib.sha256).hexdigest()

r = requests.post(URL, data=body,
                  headers={"Content-Type": "application/json", "X-Webhook-Signature": sig})
print(r.status_code, r.text)

You can also click "Send test" on each webhook in the dashboard — it does the same thing from our servers.