import hashlib
import hmac
import json
import requests
# 1. Build the body ONCE
payload = json.dumps(request_data)
# 2. Sign that exact string -> lowercase hex
signature = hmac.new(
sign_key.encode("utf-8"),
payload.encode("utf-8"),
hashlib.sha256,
).hexdigest()
# 3. Send the SAME string you signed.
# Use data=..., NOT json=... — json= re-serializes and breaks the signature.
response = requests.post(
"https://api.napoleonpay.com/api/v1/payments",
data=payload.encode("utf-8"),
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
"Signature": signature,
},
)json.dumps(..., indent=4) matches on indentation and \uXXXX escaping, but renders 100.0 instead of 100 and keeps {} — both are corrected below.
import hashlib
import hmac
import json
def _canonicalise(value):
if isinstance(value, bool):
return value
if isinstance(value, float) and value.is_integer():
return int(value) # 100.0 -> 100
if isinstance(value, dict):
return {k: _canonicalise(v) for k, v in value.items()} if value else []
if isinstance(value, list):
return [_canonicalise(v) for v in value]
return value
def verify_callback(raw_body: str, signature_header: str, sign_key: str) -> bool:
canonical = json.dumps(_canonicalise(json.loads(raw_body)), indent=4)
expected = hmac.new(
sign_key.encode("utf-8"),
canonical.encode("utf-8"),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature_header or "")