Skip to content

Console

Webhooks

Signed HTTP callbacks, retried three times and logged. 4 event types are configurable and 1 of them still has an emitter.

/dashboard/webhooks registers endpoints for the runtime selected in the sidebar. Webhooks are per-runtime, not per-organization: two runtimes need two registrations, and up to ten endpoints can be attached to each.

Available on paid plansWebhooks are available on every paid plan — BYOK, Developer, Studio and Scale — and on the retired tiers still carried by legacy subscriptions. Free does not have them. Where they are not available the surface renders an explanation instead of a form and the API returns 403. The same predicate gates the surface, the API and the dispatcher, so an endpoint you can register is an endpoint that will be delivered to.

Events

4 event types can be subscribed to. An endpoint receives only the ones on its list, and the column on the right says whether anything still sends it.

EventFires whendata carriesDispatched
message.receivedA message reached the runtime through the chat route, which no longer exists.message, channel, session_idNo
message.sentThe runtime answered, through that same removed route.response, channel, session_id, token_usageNo
agent.status_changedThe status column moves — in practice only when a row left mid-provision is closed out.previous_status, new_status, and reason on a failureYes
agent.health_checkNothing dispatches this, and nothing ever did.—No
3 of the 4 events no longer fireagent.health_check never had a dispatcher. The two message events fired from the platform chat route; that route went with the agent-VM runtime. agent.status_changed is the one that still fires, and only when /api/agent/status closes out a row left mid-provision by that same removal — so an account created since will not see it either. Subscriptions to the dormant events are still accepted and stored, because an endpoint subscribed to one is not broken, just never called. Watch the request traces on the API monitor instead.

Gateway traffic has never produced webhook events, and with the chat route gone nothing else does either. A Gateway integration should watch the request traces.


Payload

Every delivery is a POST with the same four top-level fields. Only data varies by event.

agent.status_changedjson
{
  "event": "agent.status_changed",
  "agent_id": "3f2a1c88-77b0-4c2d-9f11-6ac0d4e51b93",
  "timestamp": "2026-09-08T14:02:11.418Z",
  "data": {
    "previous_status": "provisioning",
    "new_status": "error",
    "reason": "Provisioning timed out after 11 minutes."
  }
}
HeaderValue
Content-Typeapplication/json
X-Lobstack-Signaturesha256=<hex HMAC of the raw body>
X-Lobstack-EventThe event name, so you can route before parsing.
X-Lobstack-Delivery-Attempt1, 2 or 3.
User-AgentLobstack-Webhooks/1.0

Signature verification

Signing is implemented. Each endpoint gets a 32-byte signing secret at creation, shown once and not retrievable afterwards, and every delivery carries an HMAC-SHA256 of the exact request body under that secret, hex-encoded, prefixed with sha256=.

Verify against the raw bytes you received, before any JSON parsing or re-serialisation, and compare in constant time.

Verifying a deliverytypescript
import { createHmac, timingSafeEqual } from "crypto";

function verify(rawBody: string, header: string | null, secret: string): boolean {
  if (!header?.startsWith("sha256=")) return false;
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(header.slice(7), "hex");
  const b = Buffer.from(expected, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}
What the signature does not give youThere is no timestamp inside the signed material and no nonce, so the signature proves the body came from us but not that it is fresh. A captured delivery replayed later verifies identically. Make your handler idempotent, and if replay matters to you, deduplicate on the timestamp field in the payload. The secret is also stored in plain text in our database, because computing an HMAC requires the original value.

Retries

A delivery is attempted at most three times: immediately, then after 5 seconds, then after 30. Each attempt has a 10-second timeout.

Your responseWhat happens
2xxSuccess. No further attempts.
4xx other than 429Treated as permanent. No retry — a malformed request will not become valid.
429Retried.
5xxRetried.
Timeout or connection failureRetried.

After three failed attempts the delivery is abandoned. There is no dead-letter queue and no manual redrive. Deliveries to different endpoints run concurrently, and one endpoint being down does not delay another.

Dispatch is fire-and-forget: it never blocks or fails the operation that triggered it. An event that fires while your endpoint is unreachable is lost after its third attempt.

Delivery history

Every attempt sequence is written to a delivery table with the event type, the full payload, the final status, the response code and the attempt count. The Console does not display it. There is no delivery log, no replay button and no failure alert in the UI today — the rows exist, but reading them means querying the database.


Managing endpoints

The list shows each endpoint's URL and its subscribed events, with a toggle and a delete. The toggle pauses delivery without losing the registration or its secret; a paused endpoint is skipped entirely rather than queued. Delete is immediate and takes the secret with it.

A URL must be http or https. Creating an endpoint with no events selected is rejected; creating one through the API with the field omitted subscribes it to all 4.

Lobstack

One key over every frontier model, a receipt on every call, and an agent that waits before it changes anything.

© 2026 LobstackAll rights reserved  Status