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

Billing events

payment.succeeded, subscription.renewed, subscription.tier_changed, subscription.updated, subscription.past_due, subscription.canceled, entitlement.changed, namespace.suspended, and namespace.restored support the customer billing lifecycle. Register billing hooks from your backend using an admin credential. Reseller payment events carry the saved offer, application metadata, namespace allowance and wallet funding separately. See Billing and Build a SaaS or VM reseller.

Compute capacity, transfer and retained storage

EventsCustomer state
capacity.changed, capacity.renewedA paid or funded compute contract changed or renewed
capacity.expired, capacity.payment_required, capacity.revokedCompute coverage ended, requires funding or was revoked
network.topup_applied, network.allowance.low, network.allowance.depletedManaged proxy transfer was purchased, is low or has run out
network.allowance.graceCourtesy transfer grace began; internet remains available within its bytes/deadline
network.allowance.blocked, network.allowance.restoredManaged internet transfer is blocked after funding/grace, or has recovered; paid compute is not canceled
network.policy.updatedAn owner changed spending settings, or a provider policy revision was applied to the paid period (source: "provider"); re-read capacity.network
storage.retention.payment_required, storage.retention.paidRetained storage has an amount due or was settled

These use the existing durable, signed billing delivery flow and require owner billing permissions. Re-read billing.capacity(namespace) and billing.balance(namespace) after an event. Show compute coverage, transfer and retained-storage balances separately. A paid monthly server has no compute-credit exhaustion warning; a transfer alert alone does not revoke its compute coverage. See PAYG and monthly compute.

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

These describe existing credit allowances and credit-billed services. They do not replace the capacity lifecycle events above.

EventDescription
credits.usageFired on each billing cycle — includes namespace, workspace, and detailed resource breakdown
credits.lowA capped namespace entered configured credit grace
credits.depletedA namespace exhausted its allowance and grace; account-only workspaces retain account-wallet signals
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). Thresholds use the included allowance plus purchased top-ups. The debit, marker and alert commit together, and delivery retries after a failure or restart. A cycle reset or funding that lowers usage below a band re-arms it. Read quota.alert on billing entitlement before notifying, because delayed events may describe an older balance.

{
  "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"
  }
}

Mode A: your backend resets each new paid cycle. Mode B: verified paid subscription renewals reset usage and re-arm warnings automatically. Custom notification bands survive renewals. Deduplicate signed event IDs before queueing email or in-app notifications.

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) {
  if (typeof signature !== 'string' || !/^[a-f0-9]{64}$/i.test(signature)) return false;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest();
  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    expected
  );
}

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

Pass the original raw request bytes, not reserialized JSON. Always configure a secret for billing hooks. Verify the signature before parsing or acting; the JSON id must match X-Webhook-Id. Atomically deduplicate that ID and persist processing work before returning success. Re-fetch billing state to handle out-of-order events.

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

Reconcile pending payments and current entitlement through the Billing API when a delivery is missed. Stripe's incoming payment webhooks are a separate flow; Oblien's outgoing notifications do not have Stripe's automatic retry behavior.

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 token is pinned to its own namespace for operational hooks. It cannot manage billing hooks or modify/delete a sibling or account-wide hook. Keep billing hooks under your backend's admin credentials; a billing key alone does not grant webhook-management access.

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 — deduplicate the stable JSON id / X-Webhook-Id, not a timestamp or workspace ID
  • Monitor delivery status — check the dashboard for failed status codes