Developers

Webhooks

Lumipact can push signed HTTP notifications to your systems when contracts change and when renewals come due — so your ERP, ticketing, or internal tooling reacts the moment something happens instead of polling. Available on the Growth plan and above.

Overview

  • Register up to 10 endpoint URLs per workspace, each subscribed to the events it cares about.
  • Every delivery is a POST with a JSON body, signed with HMAC-SHA256 via the X-Lumipact-Signature header.
  • Failed deliveries are retried automatically with exponential backoff (1m, 5m, 30m, 2h, 12h).
  • Endpoints that keep failing are disabled automatically; re-enable them from settings after fixing your receiver.
  • Endpoints must be HTTPS and respond with a 2xx status within 10 seconds.

Quick start

  1. Go to Settings → Developer → Webhooks (owner or admin role required).
  2. Add your endpoint URL and pick the events to subscribe to.
  3. Copy the signing secret — it is shown once, at creation.
  4. Press Send test event: Lumipact POSTs a signed ping event and shows you the response status.

Events

EventFires whenData
contract.createdA contract is added (manually, by import, or by email ingestion).Contract snapshot
contract.updatedA contract's fields change.Contract snapshot + changed_fields[]
contract.deletedA contract is deleted.id, title
renewal.upcomingA renewal or notice-period alert fires (90/60/30/14/7/3/1 days, per your alert schedule).alert_id, alert_type, window_days, contract snapshot
alert.acknowledgedA team member acknowledges an alert.alert_id, alert_type, contract_id, acknowledged_by, acknowledged_at
pingYou press “Send test event” in settings. Not subscribable — always deliverable.message

renewal.upcoming is the one to listen to if you want to act before a contract auto-renews: it fires for each alert window on your schedule and carries the full contract snapshot, the alert type (renewal_due_30d, notice_deadline_14d, …) and window_days.

Payload format

Every delivery has the same envelope. id is stable across retries — use it to de-duplicate if your receiver sees the same event twice.

POST body
{
  "id": "5f0f0a3e-9a2b-4c1d-8e7f-2b6c9d1a4e5b",
  "event": "renewal.upcoming",
  "created_at": "2026-07-14T08:00:00.000Z",
  "data": {
    "alert_id": "…",
    "alert_type": "renewal_due_30d",
    "window_days": 30,
    "contract": {
      "id": "…",
      "title": "Acme CRM subscription",
      "counterparty_name": "Acme Inc",
      "status": "active",
      "next_renewal_date": "2026-08-13",
      "notice_period_days": 30,
      "annual_value": 12000,
      "currency": "USD",
      "auto_renews": true,
      "…": "…"
    }
  }
}

Headers sent with each delivery: X-Lumipact-Event (event type), X-Lumipact-Delivery (unique per attempt group), and X-Lumipact-Signature.

Verifying signatures

The signature header has the form t=<unix seconds>,v1=<hex> where v1 is HMAC-SHA256 of `${t}.${rawBody}` using your endpoint's signing secret. Verify against the raw request bytes — parsing and re-serializing the JSON will break the HMAC. Reject signatures older than 5 minutes to prevent replays.

Node.js / Express
const crypto = require("crypto");

function verifyLumipactSignature(secret, rawBody, header) {
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.split("=", 2))
  );
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(parts.v1 ?? "", "hex")
  );
}

// Express: capture the raw body
app.post("/webhooks/lumipact", express.raw({ type: "application/json" }), (req, res) => {
  const ok = verifyLumipactSignature(
    process.env.LUMIPACT_WEBHOOK_SECRET,
    req.body.toString("utf8"),
    req.get("X-Lumipact-Signature") ?? ""
  );
  if (!ok) return res.status(401).end();

  const event = JSON.parse(req.body.toString("utf8"));
  // handle event.event / event.data …
  res.status(200).end(); // respond 2xx quickly; process async
});

Retries & failures

  • A delivery is considered failed on any non-2xx response, redirect, or a timeout after 10 seconds.
  • Failed deliveries retry up to 5 times: after 1 minute, 5 minutes, 30 minutes, 2 hours, and 12 hours. After that the delivery is marked dead.
  • After 25 consecutive failed attempts an endpoint is disabled automatically. Fix your receiver, then re-enable it in Settings → Developer → Webhooks (this resets the failure counter).
  • Delivery history is visible per endpoint in settings for 30 days.
  • Respond 2xx as fast as possible and do your processing asynchronously — slow receivers hit the 10-second timeout and get retried, which causes duplicate processing.

    We use privacy-friendly analytics to understand which pages are useful. No ads, no cross-site tracking. Read our cookie policy.