Concepts

Webhooks

Webhooks let you receive HTTP POST requests whenever events happen in your workspaces. Instead of polling for changes, you get notified the moment a VM stops, a workload fails, or your credit balance drops.

How it works

  1. You register a webhook URL and choose which events to subscribe to
  2. When a matching event fires, Oblien sends a JSON POST to your URL
  3. Your endpoint processes the payload and returns a 2xx status
  4. Oblien records the delivery status for debugging

Supported events

Workspace events

EventDescription
vm.stoppedWorkspace VM has shut down
vm.archivedVM removed on exit (ephemeral / temporary mode)

Workload events

EventDescription
workload.startedA process started inside the VM
workload.exitedA process finished cleanly
workload.failedA process exited with an error
workload.stoppedA process was force-stopped
workload.restart_loopA process is stuck in a crash loop

Credit & quota events

EventDescription
credits.usageFired on each billing cycle — includes namespace, workspace, and detailed resource breakdown
credits.lowAccount balance dropped below threshold — includes namespace and workspace context
credits.depletedAccount balance reached zero or overdraft limit hit — workspaces may be stopped
namespace.quota.thresholdA namespace quota crossed one of its configured notification thresholds (e.g. 80%, 95%). Idempotent per cycle — fires once per threshold per quota reset

Credit events include rich context so you can track per-namespace and per-workspace spend:

{
  "event": "credits.usage",
  "timestamp": "2026-03-12T14:30:00.000Z",
  "data": {
    "balance": 142.5,
    "credits_used": 3.25,
    "namespace": "production",
    "workspace_id": "ws_abc123",
    "workspace_name": "my-agent",
    "period_start": "2026-03-12T14:25:00.000Z",
    "period_end": "2026-03-12T14:30:00.000Z",
    "usage": {
      "cpu_time_minutes": 4.2,
      "memory_gb_minutes": 2.1,
      "disk_io_gb": 0.003,
      "network_gb": 0.0015
    }
  }
}

The namespace.quota.threshold event lets you notify your own customers as their workspaces approach their spending caps. Configure the percentages per quota via notificationThresholds (defaults to [80, 95], pass [] to disable). Each threshold fires at most once per cycle — call resetQuota (or update the quota's quotaLimit) to re-arm.

{
  "event": "namespace.quota.threshold",
  "timestamp": "2026-03-12T14:30:00.000Z",
  "data": {
    "namespace": "acme-corp",
    "service": "workspace_vm",
    "threshold": 80,
    "percent": 81.42,
    "used": 814.2,
    "limit": 1000,
    "workspace_id": "ws_abc123",
    "workspace_name": "my-agent"
  }
}

Quotas do not auto-reset — you own the renewal cycle for your tenants. Call resetQuota when you bill your customer (monthly, weekly, on contract renewal, etc.) and the thresholds re-arm for the next cycle.

Payload format

Every webhook delivery is a POST request with a JSON body:

{
  "event": "vm.stopped",
  "timestamp": "2026-03-12T14:30:00.000Z",
  "data": {
    "workspace_id": "ws_abc123",
    "reason": "ttl_expired"
  }
}

The payload shape under data varies by event type.

Signature verification

If you provide a secret when creating the webhook, every delivery includes an X-Webhook-Signature header containing an HMAC-SHA256 hex digest of the raw request body.

To verify:

import crypto from 'crypto';

function verifyWebhook(body, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Always use a timing-safe comparison to prevent timing attacks.

Delivery behavior

  • Timeout: 10 seconds per delivery attempt
  • Fire-and-forget: Failed deliveries are not retried automatically
  • Status tracking: The last HTTP status code and trigger time are recorded per webhook
  • Limit: Up to 10 webhooks per account

Scope a webhook to a namespace

A webhook can be bound to a single namespace so it only receives events attributable to that namespace — its credits.usage, namespace.quota.threshold, and the vm/workload events for workspaces in it. Omit namespace to make the webhook account-wide (every matching event, the default).

This is ideal for multi-tenant setups: give each tenant a webhook scoped to its namespace and it sees only its own usage.

import Oblien from 'oblien';
const client = new Oblien({ clientId, clientSecret });

// All usage events for one namespace only:
await client.webhooks.create({
  url: 'https://example.com/hooks/oblien',
  events: ['credits.usage', 'namespace.quota.threshold'],
  namespace: 'team-a',          // ← scope; omit for account-wide
  secret: process.env.WH_SECRET,
});

const { webhooks } = await client.webhooks.list({ namespace: 'team-a' });

A namespace-scoped API key is automatically pinned to its own namespace — it can only create and see webhooks for that namespace.

Managing webhooks

Create, update, toggle, and delete webhooks from the Dashboard, the SDK, or the API. Two equivalent surfaces:

SurfaceBaseAuth
SDK / API client/webhooksAPI client credentials (X-Client-ID / X-Client-Secret) or a scoped token
Dashboard/developer/webhooksLogged-in session (JWT)
MethodEndpointDescription
GET/webhooks[?namespace=]List webhooks (namespace-filterable)
GET/webhooks/eventsList subscribable event types
POST/webhooksCreate a webhook (url, events, optional secret, description, namespace)
PUT/webhooks/:idUpdate a webhook
DELETE/webhooks/:idDelete a webhook

Best practices

  • Always verify signatures if you set a secret — don't trust payloads blindly
  • Respond quickly — return a 200 within a few seconds, do heavy processing async
  • Use HTTPS endpoints — never send webhook traffic over plain HTTP
  • Handle duplicates — use the event timestamp + workspace ID to deduplicate if needed
  • Monitor delivery status — check the dashboard for failed status codes