Console
Webhooks
Signed POSTs to your endpoint when a client nears its budget, a Gateway call fails, or a day of usage closes.
Endpoints belong to the organization, not to a key. Add them under Console › Webhooks: an https URL and the events it should receive. An organization can have up to 5. Owners and admins manage them; members can see them.
Sending to your Lobstack app
The Add endpoint form has a Send to my Lobstack app button. It lists the webhook triggers you made in the app and fills in one's address, so these events can start an agent on your computer. Picking a trigger gives it a fresh address, and any old copy of its address stops working. See Trigger an agent from any webhook.
Events
| Event | When |
|---|---|
budget.threshold | A client crossed 80% or 100% of its monthly budget. |
request.failed | A Gateway call failed on our side or the provider's (5xx). |
usage.daily | Yesterday's requests, tokens and cost, once a day. |
audit.event | Scale plan: every audit record (who did what), as it is written. |
ping | Sent to one endpoint when you press Send test in the Console. |
budget.threshold fires once per client, per month, per level (80 and 100). request.failed is rate-limited to one per error class per minute; its data.suppressed_since_last counts the failures folded into it. usage.daily arrives shortly after midnight UTC with the previous UTC day's requests, tokens and cost, split by mode and by client.
Envelope
{
"id": "9b2f7c1e-…", // delivery id, also in x-lobstack-delivery
"type": "budget.threshold",
"created": "2026-09-28T14:03:11.402Z",
"org_id": "…",
"data": { … } // event-specific, see below
}Headers
| Header | Value |
|---|---|
x-lobstack-event | The event type, same as type in the body. |
x-lobstack-delivery | The delivery id. Stays the same across retries: use it to deduplicate. |
x-lobstack-timestamp | Unix seconds when this attempt was signed. |
x-lobstack-signature | v1= and the hex HMAC-SHA256 described below. |
Verifying the signature
The signature is the HMAC-SHA256, keyed with the endpoint's signing secret (whsec_…, shown once when the endpoint is created), of the timestamp, a period, and the raw request body: <timestamp>.<body>. Compute it over the bytes you received, not over re-serialized JSON, and reject timestamps more than five minutes old.
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the request body exactly as received, before JSON.parse.
export function verifyLobstack(secret, headers, rawBody, toleranceSec = 300) {
const ts = headers["x-lobstack-timestamp"];
const header = headers["x-lobstack-signature"] ?? "";
if (!/^\d+$/.test(ts ?? "")) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - Number(ts)) > toleranceSec) return false;
const expected = createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest();
return header.split(",").some((part) => {
const [scheme, value] = part.trim().split("=", 2);
if (scheme !== "v1" || !/^[0-9a-f]+$/i.test(value ?? "")) return false;
const got = Buffer.from(value, "hex");
return got.length === expected.length && timingSafeEqual(got, expected);
});
}Retries
Any 2xx within 10 seconds counts as delivered. Anything else — another status, a timeout, a redirect (redirects are never followed) — is retried after 1m, 5m, 30m, 2h, 6h: 6 attempts in all, then the delivery is marked failed. Retries go out on a sweep that runs every 5 minutes, so each wait is a minimum and can run up to 5 minutes longer. Test pings are tried once. The Console shows the last 10 deliveries per endpoint: the event, whether it was delivered, the response code, how many attempts it took, the next retry, and the last error.
Which URLs are accepted
https only, on a public host. localhost, names ending in .local or .internal, and IP addresses in private, loopback or link-local ranges (10/8, 172.16/12, 192.168/16, 127/8, 169.254/16, ::1, fc00::/7, fe80::/10) are refused.