cimplify

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

EventDescription
order.createdNew order placed
order.updatedOrder details changed
order.completedOrder fulfilled
order.cancelledOrder cancelled
payment.completedPayment successful
payment.failedPayment failed
payment.refundedRefund processed
inventory.low_stockStock below threshold
inventory.adjustedStock level changed
customer.createdNew 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.

RetryDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes

Best practices

PracticeWhy
Respond 200 immediatelyPrevents retries. Process event async via a queue.
Use event id for idempotencyEvents may be delivered more than once.
Always verify signaturesPrevents spoofed webhook payloads.
Use HTTPS endpointsWebhook 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);
}