cimplify
Concepts

Webhooks

Cimplify pushes order, payment, and booking lifecycle events to your endpoint as JSON over HTTPS. Every delivery is HMAC-signed and replayed with exponential backoff if your server doesn't return a 2xx within 30 seconds.

Event types

EventFires when
order.createdA new order is placed.
order.updatedOrder line items, status, or totals change.
order.completedOrder has been fulfilled and closed.
order.cancelledOrder is cancelled by the customer or business.
payment.completedProvider settles a charge for an order.
payment.failedProvider rejects a charge.
payment.refundedA refund is processed against an order.
booking.createdA scheduled service is booked.
booking.cancelledA booking is cancelled.
booking.rescheduledA booking moves to a new slot.
inventory.low_stockA SKU drops below its threshold.
customer.createdA customer account is created.

Payload

Every delivery is the same envelope. data is the resource at the moment of the event; subscribe to order.updated if you need a continuous stream.

{
  "id": "evt_01HZ8XK4Q9F0J0Y7M2N3P4R5S6",
  "type": "order.created",
  "created_at": "2026-05-07T10:30:00Z",
  "business_id": "bus_currents_electronics",
  "environment": "live",
  "data": {
    "order_id": "ord_01HZ8…",
    "status": "confirmed",
    "currency": "GHS",
    "total": "299.99"
  }
}

Signature verification

Each request carries an X-Cimplify-Signature header of the form sha256=…. The digest is HMAC-SHA-256 over the raw request body, keyed by the secret you received when registering the endpoint. Use a constant-time compare.

Express / Node
import express from "express";
import crypto from "node:crypto";

const app = express();

app.post(
  "/webhooks/cimplify",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.header("x-cimplify-signature") ?? "";
    const expected =
      "sha256=" +
      crypto
        .createHmac("sha256", process.env.CIMPLIFY_WEBHOOK_SECRET!)
        .update(req.body)
        .digest("hex");

    const sigBuf = Buffer.from(signature);
    const expBuf = Buffer.from(expected);
    if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) {
      return res.status(401).send("invalid signature");
    }

    const event = JSON.parse(req.body.toString("utf8"));
    enqueue(event);            // process async
    res.status(200).end();
  },
);

Retry policy

Anything other than a 2xx in under 30 seconds is treated as a failure. Cimplify retries with exponential backoff up to 24 hours, then marks the event undeliverable.

AttemptDelay
1Immediate
2+1 minute
3+5 minutes
4+30 minutes
5+2 hours
6+8 hours, then dropped

Idempotency on your receiver

Events may be delivered more than once. Treat event.id as the idempotency key for whatever side effect the event triggers (a database write, a downstream API call, an email). Storing the ID for ~7 days is enough to cover the retry window.

async function handle(event: { id: string; type: string; data: unknown }) {
  const seen = await redis.set(`webhook:${event.id}`, "1", "EX", 7 * 24 * 3600, "NX");
  if (seen !== "OK") return; // already processed

  await processEvent(event);
}

Local testing with the mock

The CLI mock can fan out events to any URL you supply. Combine with a tunnel (or a localhost receiver) to wire up your handler before going live.

cimplify mock --seed retail \
  --webhook-url http://localhost:3000/webhooks/cimplify \
  --webhook-secret whsec_local_dev

Next

On this page