Webhooks
Receive real-time event notifications via HTTP POST to your endpoint.
Handle a webhook (Node.js)
const crypto = require("crypto");
app.post("/webhooks/cimplify", (req, res) => {
// 1. Verify signature
const signature = req.headers["x-cimplify-signature"];
const expected = crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send("Invalid signature");
}
// 2. Respond immediately
res.status(200).send("OK");
// 3. Process async
queue.add("webhook", req.body);
});Event types
| Event | Description |
|---|---|
order.created | New order placed |
order.updated | Order details changed |
order.completed | Order fulfilled |
order.cancelled | Order cancelled |
payment.completed | Payment successful |
payment.failed | Payment failed |
payment.refunded | Refund processed |
inventory.low_stock | Stock below threshold |
inventory.adjusted | Stock level changed |
customer.created | New customer registered |
Payload structure
JSON
{
"id": "evt_abc123",
"type": "order.created",
"created_at": "2024-01-15T10:30:00Z",
"data": {
"order_id": "ord_xyz789",
"business_id": "biz_123",
"status": "confirmed",
"total": "29.99",
"currency": "GHS"
}
}Signature verification
Every webhook includes an X-Cimplify-Signature header with an HMAC SHA-256 digest of the request body, signed with your webhook secret.
Verification function
const crypto = require("crypto");
function verifyWebhookSignature(payload, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Retry policy
Failed deliveries (non-2xx or timeout after 30s) are retried 3 times with exponential backoff.
| Retry | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
Best practices
| Practice | Why |
|---|---|
| Respond 200 immediately | Prevents retries. Process event async via a queue. |
Use event id for idempotency | Events may be delivered more than once. |
| Always verify signatures | Prevents spoofed webhook payloads. |
| Use HTTPS endpoints | Webhook URLs must use HTTPS in production. |
Idempotent processing
async function handleWebhook(event) {
const processed = await db.get(`webhook:${event.id}`);
if (processed) return; // Already handled
await processEvent(event);
await db.set(`webhook:${event.id}`, true);
}