sable network
Documentation · all sections

Webhooks

Subscribe to lifecycle events out-of-band from the inference path. Create subscriptions from the dashboard.

Event types

  • key_created: a new API key was minted
  • key_revoked: an API key was revoked
  • webhook_created: this webhook (or a sibling) was just created
  • webhook_revoked: a webhook was disabled
  • deposit_credited: a USDT deposit confirmed and credited your balance
  • balance_low: a debit took the balance below your threshold. Fires once per downward crossing, not on every debit below it; set your own threshold on the portal's Billing page or via PUT /v1/billing/alerts (GET reads it back), else the deployment default applies
  • spend_cap_reached: a key hit its monthly spend cap
  • auto_topup_credited / auto_topup_failed / auto_topup_exhausted: what happened to an automatic top-up. These fire only on a deployment that has switched the feature on (SABLE_AUTO_TOPUP_ENABLED; it is off by default). auto_topup_credited: one of your pre-signed payments was presented and accepted — {auto_topup_id, status, network, transaction, amount_micro_usd, credited_micro_usd, payments_remaining}, where status is credited or pending (still confirming on-chain), amount_micro_usd is what you signed and credited_micro_usd is the net amount Sable read arriving. auto_topup_failed: a top-up was refused or could not be verified, or the paying wallet was unlinked — {auto_topup_id, error_class, disabled: true}; the authorization is now off and does not retry. auto_topup_exhausted: nothing is left to use — {auto_topup_id, reason, pulled_micro_usd, cap_micro_usd}, with reason one of no_payments_left, cap_reached or expired. None of them carries a signature
  • guardrail_triggered: a guardrail detector whose action is block matched, whether or not enforcement acted on it. Carries the request id, rule set id, rule, category, direction, the findings (counts only) and the decision (blocked or observed). Events from the MCP Gateway, Sable Services and sandboxes also carry surface (mcp_call, service, sandbox); one with no surface is from the inference path. Never the matched text, which is not stored
  • sandbox_failed: a sandbox run ended in failure
  • sandbox_session_stopped: a sandbox session ended — {session_id, reason}, where the reason is requested, idle_timeout, max_lifetime, insufficient_credit, or node_unreachable. An agent holding a workspace needs to know it is gone; the next command against it returns 409
  • attestation_failed: a confidential-tier request was refused because no backend could be attested
  • receipt_minted: a signed receipt was minted for a metered call (inference, embeddings, /v1/messages, or a sandbox run). Fires once per receipt and carries the full receipt, its signature, and signer, so it doubles as an audit stream: see below
  • vault_transfer_sent / vault_transfer_received: a Sable Vault position moved. Deliberately amount-free: vault amounts are sealed, and webhook payloads travel to third-party URLs; fetch details through your session
  • vault_distribution: an issuer paid out to holders of a vault asset
  • vault_attestation: a third-party attester recorded a statement on a vault asset (the statement itself is sealed, so the event names the fact, not the text) (interest, dividend, principal). Also amount-free; holders read their share through their own session
  • service_called: a buyer paid for and received one call to a service you published. Carries the service and call ids, the price, the amount actually paid, the settlement chain and transaction, the status and the latency — never the request or the response
  • batch_completed / batch_failed: a batch reached a terminal state. Carries the batch id, endpoint, status, and request counts — and only those: batch content lives in the sealed output file, never in a payload sent to a third-party URL. batch_failed adds a fixed error_class; a cancelled batch fires neither, since you asked for it
  • *: subscribe to everything

The receipt stream

receipt_minted is the one to point a collector at. It fires once for every signed receipt, the moment it is minted, across inference, embeddings, /v1/messages, and sandbox runs. The payload carries the receipt, signature, and signer, plus the kind, status, and cost_micro_usd, so every metered call lands in your own observability or audit stack as it happens, already signed and independently verifiable through POST /v1/receipts/verify.

It is metadata only by construction, the same as the receipt itself: no prompt, completion, or code ever travels in the event. That makes it safe to route to a SIEM, a data warehouse, or a spend dashboard without opening a hole in the privacy contract.

Delivery

Each event POSTs JSON to your URL with two headers:

  • X-Sable-Event: the event type, e.g. key_revoked
  • X-Sable-Signature: sha256=<hex> HMAC of the raw body using your webhook secret

Verifying signatures

import { createHmac, timingSafeEqual } from "node:crypto";

function verifySable(req: Request, secret: string): boolean {
const sig = req.headers.get("x-sable-signature") ?? "";
const expected = "sha256=" + createHmac("sha256", secret)
  .update(req.body)
  .digest("hex");
return timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

Retries & auto-disable

Delivery is retried with exponential backoff: an immediate attempt, then retries after 30s, 5m, and 1h, up to four attempts per event. Any failed attempt retries, whether it was a non-2xx response or a network error, until that budget is spent. Events queue in a durable outbox, so the retry schedule survives gateway restarts and deploys rather than dying with the process. Each event records exactly one webhook_deliveries row capturing the number of attempts and the last status seen.

After 5 consecutive events fail every attempt, the webhook is automatically disabled (disabled_at is set) and stops receiving events until you re-create it. A single delivered event resets the counter.

Inspecting and testing deliveries

Two session-authed endpoints make a misbehaving receiver debuggable without guessing:

  • GET /v1/webhooks/:id/deliveries: the webhook's last 50 delivery attempts: event_type, response_status, attempts, delivered_at. Delivery-log rows are pruned after 30 days.
  • POST /v1/webhooks/:id/test: fires a synthetic webhook_test event at that webhook immediately, signed like any real event, so you can verify your endpoint and HMAC check end-to-end without waiting for something to happen.
Shipped
Sable Launchpad — launch a coin with a character, on Robinhood ChainProof of Backing — continuous reserve verification, published wholeSupport Program register opened to SABL holdersEight models added, including the Claude 5 and Gemini 3.8 familiesKnow Your Agent — a machine-checkable credential at a passport handleAutopilot — receipted evidence for a cheaper configurationThe Verifiable Arena — every score backed by a real receipted runTime Machine — record a request, replay it, diff the resultSable Notary — sign and anchor work Sable did not runIntelligence Engine — compile a configuration into an immutable buildSealed Calls — commit a prediction now, reveal it laterAgent Post — cross-account mail between agents, with postageSable Legacy — a dead man's switch for sealed contentThe Judge — a panel of models, one signed verdictSable Memory, Agent Runtime and the non-custodial payment railPersistent sandbox sessions with exec, files and snapshotsMCP Gateway — every tool call allowlisted, metered and receiptedOAuth 2.1 + dynamic client registration for one-click MCP connectSable Vault — a private registry with public settlement