Izzi FinanceDocumentation
Webhooks
Webhooks allow you to receive real-time HTTP notifications when events occur in your IZZIPAY account.
Webhook Setup
bash
curl -X POST https://api.izzipay.com/v1/developer/webhooks \
-H "Authorization: Bearer sk_test_your_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/izzipay",
"events": ["payment.succeeded", "payment.failed"],
"secret": "whsec_your_signing_secret"
}'Events
| Event | Description |
|---|---|
payment.succeeded | A checkout payment or charge was captured. |
payment.failed | The payment could not be completed. |
payment.refunded | A charge refund was recorded. |
refund.rejected | The institution rejected a refund. |
checkout.session.completed | The checkout session reached SUCCEEDED. |
checkout.session.expired | The checkout session expired without a payment. |
wallet.credited | The merchant wallet balance increased. |
payout.created | A payout was created. |
payout.awaiting_approval | The payout is waiting for institution approval. |
payout.completed | Funds were sent to the beneficiary. |
payout.failed | Payout execution failed. |
payout.rejected | A reviewer rejected the payout. |
settlement.created | A settlement batch was created. |
settlement.completed | The settlement batch was closed. |
Payload Format
json
{
"id": "evt_abc123",
"type": "payment.succeeded",
"created_at": "2024-01-15T10:30:00Z",
"data": {
"id": "pay_xyz789",
"amount": 5000,
"currency": "USD",
"status": "SUCCEEDED",
"method": "mobile_money",
"metadata": {}
}
}Signature Verification
Each delivery includes X-Izzi-Signature (HMAC-SHA256, hex) and X-Izzi-Timestamp (Unix seconds). Sign timestamp, a literal dot, then the raw body. Reject timestamps older than 300 seconds, and verify the signature before you parse the JSON.
javascript
import { createHmac, timingSafeEqual } from "node:crypto";
const TOLERANCE_SECONDS = 300;
function verifyIzziWebhook({ secret, rawBody, signature, timestampHeader }) {
if (!signature || !timestampHeader) return false;
const timestamp = Number(timestampHeader);
if (!Number.isFinite(timestamp)) return false;
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > TOLERANCE_SECONDS) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
if (expected.length !== signature.length) return false;
return timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(signature, "hex"),
);
}
app.post(
"/webhooks/izzipay",
express.raw({ type: "application/json" }),
(req, res) => {
const rawBody = req.body.toString("utf8");
const ok = verifyIzziWebhook({
secret: process.env.IZZI_WEBHOOK_SECRET,
rawBody,
signature: req.headers["x-izzi-signature"],
timestampHeader: req.headers["x-izzi-timestamp"],
});
if (!ok) return res.status(401).send("Invalid signature");
const event = JSON.parse(rawBody);
console.log(`Received: ${event.type}`);
res.status(200).send("OK");
},
);Best Practices
- •Always return a 200 response quickly to avoid timeouts
- •Process webhook payloads asynchronously
- •Implement idempotency: you may receive the same event multiple times
- •Use the event ID to deduplicate
- •Verify signatures before processing any event