Python SDK
The official Python SDK — itpay-sdk — provides a fully typed client for the ItPay Protocol. It wraps the REST API with idiomatic Python, including Pydantic-validated models, automatic request signing, and built-in webhook verification.
Installation
pip install itpay-sdk
Requirements: Python 3.9+.
Quick Setup
Configure the client with your API key and optional defaults:
from itpay_sdk import ItPayClient
client = ItPayClient(
api_key='sk_liv...here',
channel='alipay',
)
The API key can also be loaded from the ITPAY_API_KEY environment variable:
import os
client = ItPayClient(
api_key=os.environ['ITPAY_API_KEY'],
channel='alipay',
)
Client Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key | str | (required) | Your secret API key |
channel | str | — | Default payment channel (e.g. alipay) |
base_url | str | https://api.itpay.ai/v1 | API base URL |
timeout | int | 30 | Request timeout in seconds |
webhook_secret | str | — | Secret for verifying webhooks |
Creating a PaymentIntent
The payment flow starts by creating a PaymentIntent, then generating a QR code so the user can scan and pay.
import uuid
from itpay_sdk import ItPayClient
client = ItPayClient(
api_key='sk_liv...here',
channel='alipay',
)
def create_payment():
# 1. Create the PaymentIntent
intent = client.payment_intents.create(
id=f'pi_{uuid.uuid4().hex}',
service_id='01J7XYKZ1A2B3C4D5E6F7G8H9I',
type='one_time',
amount={
'currency': 'CNY',
'value': 699, # 6.99 CNY in minor units
},
description='AI document summary (42 pages)',
payer={
'agent_id': 'agent_cli_a1b2c3d4',
'human_id': 'user_abc_789',
},
metadata={
'session_id': 'sess_xyz_456',
},
)
print(f"Intent created: {intent['id']} (status: {intent['status']})")
# → Intent created: pi_01J7XZ1A2B3C4D5E6F7G8H9IK (status: pending)
# 2. Generate the QR code
qr_charge = client.qr.generate(intent['id'])
print(f"QR scan URL: {qr_charge['scan_url']}")
print(f"QR expires at: {intent['expires_at']}")
# 3. Display the QR to the user
# In a web UI: <img src={qr_charge['scan_url']} alt="Pay with Alipay" />
# In a terminal: open the URL via webbrowser.open()
# In a chat: send the image inline
# 4. Poll for completion (useful for development)
result = client.payment_intents.wait_for_completion(
intent['id'],
timeout=300, # 5 minutes
poll_interval=2, # poll every 2 seconds
)
if result['status'] == 'succeeded':
print('✓ Payment received!')
else:
print(f'✗ Payment failed: {result["status"]}')
create_payment()
API Reference
client.payment_intents.create(params)
| Parameter | Type | Required | Description |
|---|---|---|---|
id | str | Yes | Unique UUIDv7 identifier (idempotency key) |
service_id | str | Yes | ServiceManifest ID from registration |
type | str | Yes | one_time, cumulative, or subscription |
amount | dict | Yes | { "currency": str, "value": int } |
description | str | No | Human-readable payment label |
payer | dict | Yes | { "agent_id": str, "human_id"?: str } |
metadata | dict | No | Arbitrary key-value data (max 4 KB) |
client.qr.generate(payment_intent_id)
Generates a QR charge for the given PaymentIntent. The PaymentIntent must be in pending status. Returns a dict with a scan_url for rendering.
client.payment_intents.wait_for_completion(id, timeout, poll_interval)
Polls the PaymentIntent until it reaches a terminal state (succeeded, expired, cancelled) or the timeout is hit.
Handling Webhooks
When a payment completes, the ItPay platform sends a signed webhook POST to your ServiceManifest's configured endpoint URL. Your handler must verify the HMAC-SHA256 signature and check for idempotency.
Here's a complete Flask example:
import os
import hmac
import hashlib
import json
import time
from flask import Flask, request, jsonify
app = Flask(__name__)
# Must match the webhook secret from your ItPay dashboard
WEBHOOK_SECRET = os.environ.get('ITPAY_WEBHOOK_SECRET', 'whsec_xxxxxxxxxxxx')
WEBHOOK_MAX_AGE = 300 # 5 minutes
# Track processed events (use Redis in production)
PROCESSED_EVENTS: set[str] = set()
def verify_webhook_signature(
raw_body: bytes,
signature_header: str,
webhook_secret: str,
max_age: int = 300,
) -> bool:
"""Verify the HMAC-SHA256 signature on a webhook payload."""
# Parse the signature header: t=<timestamp>,v1=<hex_sig>
parts = dict(
item.split('=', 1)
for item in signature_header.split(',')
)
timestamp = parts.get('t')
expected_sig = parts.get('v1')
if not timestamp or not expected_sig:
return False
# Reject webhooks older than max_age
if int(time.time()) - int(timestamp) > max_age:
return False
# Reconstruct the signed payload: "<timestamp>.<raw_body>"
signed_payload = f"{timestamp}.{raw_body.decode('utf-8')}"
computed_sig = hmac.new(
key=webhook_secret.encode('utf-8'),
msg=signed_payload.encode('utf-8'),
digestmod=hashlib.sha256,
).hexdigest()
# Constant-time comparison — prevents timing side-channel attacks
return hmac.compare_digest(computed_sig, expected_sig)
@app.route('/itpay/v1/webhook', methods=['POST'])
def webhook():
# 1. Read signature header
signature_header = request.headers.get('X-ItPay-Signature')
if not signature_header:
return jsonify({'error': 'Missing X-ItPay-Signature header'}), 401
# 2. Verify the HMAC signature
if not verify_webhook_signature(request.data, signature_header, WEBHOOK_SECRET, WEBHOOK_MAX_AGE):
return jsonify({'error': 'Invalid signature'}), 401
# 3. Parse the event
event = request.get_json(force=True)
event_id = event['id']
event_type = event['type']
# 4. Idempotency check — skip events already processed
if event_id in PROCESSED_EVENTS:
return jsonify({'status': 'already_processed'}), 200
PROCESSED_EVENTS.add(event_id)
# 5. Handle the event
if event_type == 'payment_intent.succeeded':
intent = event['data']
print(f"✓ Payment succeeded — Intent: {intent['id']}")
print(f" Amount: {intent['amount']['value']} {intent['amount']['currency']}")
print(f" Channel: {intent['channel']}")
print(f" Metadata: {intent.get('metadata', {})}")
# TODO: Deliver the service to the user
# - Look up session_id from intent['metadata']
# - Unlock content or grant access
elif event_type == 'payment_intent.failed':
intent = event['data']
print(f"✗ Payment failed — {intent['id']}: {intent.get('failure_reason', 'unknown')}")
elif event_type == 'payment_intent.expired':
intent = event['data']
print(f"⏰ Payment expired — {intent['id']}")
else:
print(f"ℹ Unhandled event type: {event_type}")
# 6. Acknowledge receipt
return jsonify({'status': 'received'}), 200
if __name__ == '__main__':
app.run(port=8080)
Testing the Webhook Locally
# Start your webhook handler
python webhook_handler.py
# Expose it to the internet with ngrok
ngrok http 8080
# → Forwarding https://abc123.ngrok.io → http://localhost:8080
# Update your ServiceManifest's endpoint to the ngrok URL
# https://abc123.ngrok.io/itpay/v1/webhook
Using the SDK's Built-in Verifier
The SDK includes a convenience function for webhook verification:
from itpay_sdk.webhooks import verify_signature
is_valid = verify_signature(
raw_body=request.data,
signature_header=request.headers['X-ItPay-Signature'],
webhook_secret=WEBHOOK_SECRET,
max_age=300,
)
Using the SDK with Django
For Django applications, use the @csrf_exempt decorator on the webhook view:
import json
import hmac
import hashlib
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse, HttpRequest
@csrf_exempt
def itpay_webhook(request: HttpRequest):
if request.method != 'POST':
return JsonResponse({'error': 'Method not allowed'}, status=405)
# Verify signature
signature_header = request.headers.get('X-ItPay-Signature')
if not signature_header:
return JsonResponse({'error': 'Missing signature'}, status=401)
raw_body = request.body
# ... verification and handling same as Flask example above
return JsonResponse({'status': 'received'})
Error Handling
The SDK raises exceptions for predictable error handling:
from itpay_sdk.exceptions import (
ItPayError,
AuthenticationError,
ValidationError,
RateLimitError,
)
try:
intent = client.payment_intents.create(...)
except AuthenticationError:
print('Invalid API key — check your credentials')
except ValidationError as e:
print(f'Validation failed: {e}')
except RateLimitError:
print('Rate limit exceeded — slow down requests')
except ItPayError as e:
print(f'ItPay API error: {e.status_code} — {e}')
Exception Classes
| Exception | HTTP Status | Description |
|---|---|---|
AuthenticationError | 401 | Invalid or missing API key |
ValidationError | 422 | Request validation failed |
RateLimitError | 429 | Rate limit exceeded |
ItPayError | 5xx | Base class for server-side errors |
Best Practices
- Use UUIDv4 or UUIDv7 for PaymentIntent
idvalues — they ensure idempotency. - Store secrets as environment variables — never hardcode
api_keyorwebhook_secret. - Always verify webhook signatures using
hmac.compare_digestfor constant-time comparison. - Use idempotency — the
idfield on PaymentIntent creation is the idempotency key. Reusing the sameidis safe. - Prefer webhooks over polling in production. The convenience poller is for development only.
- Use
request.data(raw body) for webhook verification, notrequest.json— the raw bytes are needed for HMAC computation.
Next Steps
- Core Objects: PaymentIntent — Complete state machine reference
- Security Model — Webhook verification and request signing details
- Channel Matrix — Supported payment channels and regions
- Quick Start — End-to-end payment flow walkthrough