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
POSTwith a JSON body, signed with HMAC-SHA256 via theX-Lumipact-Signatureheader. - 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
- Go to
Settings → Developer → Webhooks(owner or admin role required). - Add your endpoint URL and pick the events to subscribe to.
- Copy the signing secret — it is shown once, at creation.
- Press Send test event: Lumipact POSTs a signed
pingevent and shows you the response status.
Events
| Event | Fires when | Data |
|---|---|---|
contract.created | A contract is added (manually, by import, or by email ingestion). | Contract snapshot |
contract.updated | A contract's fields change. | Contract snapshot + changed_fields[] |
contract.deleted | A contract is deleted. | id, title |
renewal.upcoming | A 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.acknowledged | A team member acknowledges an alert. | alert_id, alert_type, contract_id, acknowledged_by, acknowledged_at |
ping | You 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.
{
"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.
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.
