Integrations
Webhook events
Subscribe your own endpoint to Lineman cost events, verify every delivery with an HMAC signature, and re-send anything that failed.
The event envelope
Every event, whatever its type, arrives in the same JSON shape. type comes from the published taxonomy and schema_version tells you which envelope you are looking at. Call GET /v1/event-types for the current list.
{
"id": "6f1c0e6e-6d1a-4f8e-9a0e-9b7d2f5c1a33",
"type": "cost_alert.budget_day",
"schema_version": 1,
"company_id": "5b0f...",
"occurred_at": "2026-07-25T09:30:00.000Z",
"data": {
"alert_type": "budget_day",
"title": "Daily budget exceeded",
"subject_user_id": null,
"dedup_key": "co1:budget_day::2026-07-25",
"detail": { "spend_usd_micro": 2500000, "spend_usd": 2.5 }
}
}Amounts are micro-USD integers in _usd_micro fields, each with a decimal _usd sibling alongside it. Times are ISO-8601 UTC.
De-duplicate on the event id
Treat id as an idempotency key and ignore an id you have already handled. This is not optional: the same event can reach you more than once, by design.
- Retries. A failed delivery is retried with backoff, and every retry carries the same
id. - Replays. Re-sending a delivery from the log sends the original body again, unchanged, so the
idis identical. Only the timestamp and the signature are fresh. - Several endpoints. One event delivered to two of your endpoints is one event with one
id, not two events.
Verify the signature
Each request carries X-Lineman-Timestamp in Unix seconds and X-Lineman-Signature, which is sha256= followed by the HMAC-SHA256 of the literal string timestamp.body under your signing secret. Sign the raw body, before any JSON parsing.
const expected =
"sha256=" +
crypto
.createHmac("sha256", process.env.LINEMAN_WEBHOOK_SECRET)
.update(timestampHeader + "." + rawBody)
.digest("hex");
// Reject anything older than five minutes, then compare in constant time.
if (Math.abs(Date.now() / 1000 - Number(timestampHeader)) > 300) return reject();
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader)))
return reject();During a secret rotation a second header, X-Lineman-Signature-Previous, carries the same body signed with your previous secret. Accept either one while you roll the new secret out.
Add an endpoint
An endpoint is a URL, a signing secret and a list of event types. Leave the list empty to receive every type, including ones published later. Only a company administrator can add one, because an endpoint receives the whole company's cost events.
- 1Pick the events you want from
GET /v1/event-types. - 2Create the endpoint with
POST /v1/webhooks/endpoints, passing an https URL and a signing secret of 16 to 256 characters. - 3Send yourself a test with
POST /v1/webhooks/endpoints/:id/testand confirm your signature check passes. - 4Watch the first real deliveries land in
GET /v1/webhooks/deliveries.
curl -X POST https://api.lineman.io/v1/webhooks/endpoints \
-H "Authorization: Bearer $LINEMAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/lineman",
"label": "ops service",
"event_types": "cost_alert.budget_day,cost_alert.budget_month",
"secret": "a-long-random-signing-secret"
}'When a delivery fails
Every attempt is recorded, including what your endpoint answered. Nothing is dropped quietly.
- Retried after roughly 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, for a 5xx, a 408, a 429 or a network error.
- Given up on straight away for any other 4xx, since re-sending the same body would fail the same way, and after the final retry otherwise. The row is stamped with
gave_up_at. - Readable at
GET /v1/webhooks/deliveries, with the status code and a bounded snippet of your endpoint's response. - Re-sendable with
POST /v1/webhooks/deliveries/:id/replayonce you have fixed the receiver.
Rotate the signing secret without downtime
Rotating a secret used to mean the old one stopped verifying the instant the new one was saved, so your deployment had to land at the same second. It no longer does.
- 1Call
PUT /v1/webhooks/endpoints/:id/secretwith the new secret. The previous secret stays valid for 24 hours by default, or for whatever you pass asoverlap_seconds, up to seven days. - 2During the overlap, verify against either
X-Lineman-SignatureorX-Lineman-Signature-Previous. Deploy the new secret whenever suits you. - 3There is nothing to do at the end: the previous secret expires on its own. Pass
overlap_secondsof 0 if you would rather cut over instantly.
Who can do what
Reading endpoints and the delivery log needs an administrator or an auditor. Adding, changing, testing, rotating and replaying all need an administrator.
The single alert webhook configured under Cost Explorer settings still works exactly as before and is untouched. Endpoints here are separate, so adding one does not duplicate anything you already receive.