Examples
Python — full example
Charge a customer and handle the webhook, end to end.
The same integration as the Node.js example, using Flask.
import hmac
import hashlib
import os
import time
import requests
from flask import Flask, request, abort, redirect
app = Flask(__name__)
TCHOKO_KEY = os.environ["TCHOKOPAY_API_KEY"]
WEBHOOK_SECRET = os.environ["TCHOKOPAY_WEBHOOK_SECRET"]
def charge_customer(order_id: str, amount_xaf: int) -> str:
response = requests.post(
"https://connect.tchokopay.com/v1/payments",
headers={
"Authorization": f"Bearer {TCHOKO_KEY}",
"Idempotency-Key": f"charge-{order_id}",
},
json={
"amount": amount_xaf,
"currency": "XAF",
"description": f"Order {order_id}",
"reference": order_id,
},
)
if not response.ok:
raise RuntimeError(f"TchokoPay error: {response.json()['message']}")
return response.json()["checkoutUrl"] # redirect your customer here
def verify_webhook(raw_body: bytes, signature_header: str, timestamp_header: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
f"{timestamp_header}.{raw_body.decode()}".encode(),
hashlib.sha256,
).hexdigest()
provided = signature_header.replace("sha256=", "")
valid = hmac.compare_digest(expected, provided)
age = abs(time.time() - float(timestamp_header))
if age > 300:
return False # stale — reject even if the signature matches
return valid
@app.get("/checkout/<order_id>")
def checkout(order_id):
checkout_url = charge_customer(order_id, 5000)
return redirect(checkout_url)
@app.post("/webhooks/tchokopay")
def tchokopay_webhook():
signature = request.headers.get("X-TchokoPay-Signature", "")
timestamp = request.headers.get("X-TchokoPay-Timestamp", "")
if not verify_webhook(request.get_data(), signature, timestamp, WEBHOOK_SECRET):
abort(400, "invalid signature")
event = request.get_json()
if event["status"] == "SUCCESS":
mark_order_paid(event["invoiceReference"], event["settledAmount"], event["settledCurrency"])
elif event["status"] == "FAILED":
mark_order_failed(event["invoiceReference"], event.get("failureReason"))
return "", 200request.get_data() returns the raw request bytes — don't call request.get_json()
first and re-serialize it for signature verification, that can reorder keys and break
the signature match.
