Everything in Python: install → create invoice → get pay link → receive payment → redirect customer → verify webhooks.
# Python 3.10+
python -m venv .venv && source .venv/bin/activate
pip install requests flask python-dotenvGet 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.
# .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 webhookSave as client.py. Every snippet below does from client import api.
# 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.
Run this once — it pings the API and prints whether your key works.
# 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.
The response includes pay_url — the hosted payment page with QR code + crypto address. Send it to your customer.
# 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" initiallyTip: put {id} in success_redirect_url and we substitute it after payment.
# 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"])# 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"))Polling works if you can't run a public webhook server (e.g. local script, CLI tool). For production, prefer webhooks (next section).
# 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"])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.
# 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.
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.
# 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.
Fires a fake invoice.paid event at your endpoint with a valid signature — handy for unit tests.
# 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.