Event types
payment.succeededpayment.failedpayment.pendingrefund.succeededrefund.failedpayout.paidpayout.rejectedorder.paidinvoice.paid
Delivery
We POST JSON to your endpoint with headers FlyPay-Signature and FlyPay-Event-Id. Respond with any 2xx within 10 seconds. Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h and 6 h (6 attempts total). You can re-send any delivery from the dashboard.
Verifying signatures
The header looks like t=1760371200,v1=5f2c…. Compute HMAC-SHA256 of "{t}.{raw_body}" with your endpoint secret (whsec_…), compare in constant time, and reject timestamps older than 5 minutes.
Python
import hashlib, hmac, re, time
def verify_flypay_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
m = re.fullmatch(r"t=(\d+),v1=([a-f0-9]{64})", header or "")
if not m or abs(time.time() - int(m.group(1))) > tolerance:
return False
signed = m.group(1).encode() + b"." + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, m.group(2))Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyFlyPaySignature(rawBody, header, secret, toleranceSec = 300) {
const m = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(header ?? '');
if (!m) return false;
const t = Number(m[1]);
if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return timingSafeEqual(Buffer.from(expected), Buffer.from(m[2]));
}Idempotent processing
Store each processed FlyPay-Event-Id and ignore duplicates — retries and network issues can deliver the same event more than once.
Example payload
{
"id": "evt_…",
"type": "payment.succeeded",
"created": "2026-10-13T10:24:00.000Z",
"data": {
"id": "txn_…", "reference": "FPN-261013-AB12CD34", "status": "succeeded", "mode": "sandbox",
"amount_minor": 12000000, "currency": "TZS", "fee_minor": 300000, "net_minor": 11700000,
"method": "sandbox_mpesa", "provider_reference": "SBX-…"
}
}