Webhooks
Register HTTPS endpoints that receive a signed POST whenever something happens to your sandboxes, disks, networks, or templates. Instead of polling GET /v1/sandboxes/{id}, let CreateOS push lifecycle events to your service in real time.
Get your API key from https://createos.nodeops.network/profile. Pass it as X-Api-Key: <token> on every request below.
Base URL: https://api.sb.createos.sh
At a glance
- Base URL:
https://api.sb.createos.sh - Auth:
X-Api-Key: <token>header. Get a token - Response envelope: JSend,
{"status": "...", "data": ...} - Delivery:
POSTto your URL, HMAC-SHA256 signed, retried with backoff.
An endpoint subscribes to a set of event actions (or all of them). When a matching action occurs, every active endpoint whose filter includes that action receives a delivery. Endpoints are scoped to the authenticated user.
Event actions
An endpoint's events filter is a list of action strings. Subscribe to all current actions available for filtering:
GET /v1/webhook-endpoints/actions
Auth required: Yes
Bash1curl -s https://api.sb.createos.sh/v1/webhook-endpoints/actions \2 -H "X-Api-Key: $TOKEN"
JSON1{2 "status": "success",3 "data": [4 "sandbox.create", "sandbox.destroy", "sandbox.pause", "sandbox.resume",5 "sandbox.fork", "sandbox.patch", "sandbox.resize", "sandbox.egress.set",6 "sandbox.ssh_pubkey.update", "sandbox.bandwidth.update",7 "disk.create", "disk.delete", "disk.attach", "disk.detach",8 "network.create", "network.delete", "network.attach", "network.detach",9 "template.submit", "template.delete", "template.build.start", "template.build.complete"10 ]11}
| Category | Actions |
|---|---|
| Sandbox | sandbox.create, sandbox.destroy, sandbox.pause, sandbox.resume, sandbox.fork, sandbox.patch, sandbox.resize, sandbox.egress.set, sandbox.ssh_pubkey.update, sandbox.bandwidth.update |
| Disk | disk.create, disk.delete, disk.attach, disk.detach |
| Network | network.create, network.delete, network.attach, network.detach |
| Template | template.submit, template.delete, template.build.start, template.build.complete |
Always fetch this list at runtime rather than hard-coding it — new actions are added over time.
Create an endpoint
POST /v1/webhook-endpoints
Auth required: Yes
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Your HTTPS receiver. Must be http(s)://…. |
events | string[] | No | Actions to subscribe to. Omit or pass [] to receive every action. |
Bash1# Subscribe to everything2curl -s -X POST https://api.sb.createos.sh/v1/webhook-endpoints \3 -H "X-Api-Key: $TOKEN" \4 -H "Content-Type: application/json" \5 -d '{"url": "https://example.com/webhook"}'67# Subscribe to specific actions only8curl -s -X POST https://api.sb.createos.sh/v1/webhook-endpoints \9 -H "X-Api-Key: $TOKEN" \10 -H "Content-Type: application/json" \11 -d '{"url": "https://example.com/webhook", "events": ["sandbox.create", "sandbox.destroy"]}'
JSON1{2 "status": "success",3 "data": {4 "id": "eb081e4d-...",5 "url": "https://example.com/webhook",6 "secret": "whsec_a6ed6066...",7 "events": [],8 "active": true9 }10}
Save the
secret. It is returned only once, at creation, and is required to verify delivery signatures. If you lose it, delete the endpoint and create a new one.
List endpoints
GET /v1/webhook-endpoints
Auth required: Yes
Bash1curl -s https://api.sb.createos.sh/v1/webhook-endpoints \2 -H "X-Api-Key: $TOKEN"
JSON1{2 "status": "success",3 "data": [4 {5 "id": "eb081e4d-...",6 "url": "https://example.com/webhook",7 "events": [],8 "active": true,9 "failureCount": 0,10 "createdAt": "2026-06-25T10:26:39Z"11 }12 ]13}
The secret is never returned by list or get — only at creation.
Get an endpoint (with recent deliveries)
GET /v1/webhook-endpoints/{id}
Auth required: Yes
Bash1curl -s https://api.sb.createos.sh/v1/webhook-endpoints/$ENDPOINT_ID \2 -H "X-Api-Key: $TOKEN"
JSON1{2 "status": "success",3 "data": {4 "endpoint": {5 "id": "eb081e4d-...",6 "url": "https://example.com/webhook",7 "active": true,8 "failureCount": 09 },10 "deliveries": [11 {12 "id": "2423f39d-...",13 "eventAction": "sandbox.create",14 "status": "delivered",15 "attempts": 1,16 "deliveredAt": "2026-06-25T10:32:48Z"17 }18 ]19 }20}
Delivery status is one of pending, delivered, or failed. Delivery records are short-lived — see Retention.
Suspend / resume an endpoint
Suspending stops deliveries without deleting the endpoint. Resuming reactivates it and resets its failure count.
POST /v1/webhook-endpoints/{id}/suspend
POST /v1/webhook-endpoints/{id}/resume
Auth required: Yes
Bash1curl -s -X POST https://api.sb.createos.sh/v1/webhook-endpoints/$ENDPOINT_ID/suspend \2 -H "X-Api-Key: $TOKEN"34curl -s -X POST https://api.sb.createos.sh/v1/webhook-endpoints/$ENDPOINT_ID/resume \5 -H "X-Api-Key: $TOKEN"
An endpoint that accumulates too many delivery failures is auto-suspended (active: false) — see Retry policy. Call resume once your receiver is healthy again.
Delete an endpoint
DELETE /v1/webhook-endpoints/{id}
Auth required: Yes
Deletes the endpoint and all of its pending deliveries.
Bash1curl -s -X DELETE https://api.sb.createos.sh/v1/webhook-endpoints/$ENDPOINT_ID \2 -H "X-Api-Key: $TOKEN"
Delivery
When a subscribed action occurs, a POST is sent to each matching active endpoint:
POST https://example.com/webhook
Content-Type: application/json
User-Agent: CreateOS-Webhook/1.0
X-Webhook-Timestamp: 1750780801
X-Webhook-Signature: sha256=def789...
JSON1{2 "event": "sandbox.create",3 "occurredAt": "2026-06-25T04:57:22Z",4 "data": {5 "shape": "s-1vcpu-1gb",6 "disk_mib": 10240,7 "region": "eu"8 }9}
event— the action string (matches one of the event actions).occurredAt— RFC 3339 timestamp of when the event happened.data— action-specific metadata. Shape varies by action; treat unknown fields as additive.
Respond with any 2xx status to acknowledge. Any non-2xx (or a timeout) is treated as a failed delivery and retried.
Verifying signatures
Every delivery is signed. Compute HMAC-SHA256 over <X-Webhook-Timestamp>.<raw-body> using your endpoint secret, prefix with sha256=, and compare against X-Webhook-Signature in constant time. Verify against the raw request body — do not re-serialize the parsed JSON.
Python1import hmac, hashlib23def verify(secret: str, headers, raw_body: bytes) -> bool:4 ts = headers["X-Webhook-Timestamp"]5 expected = "sha256=" + hmac.new(6 secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha2567 ).hexdigest()8 return hmac.compare_digest(expected, headers["X-Webhook-Signature"])
JavaScript1import crypto from "node:crypto";23function verify(secret, headers, rawBody) {4 const ts = headers["x-webhook-timestamp"];5 const mac = crypto.createHmac("sha256", secret);6 mac.update(`${ts}.`);7 mac.update(rawBody); // Buffer of the raw request body8 const expected = "sha256=" + mac.digest("hex");9 return crypto.timingSafeEqual(10 Buffer.from(expected),11 Buffer.from(headers["x-webhook-signature"]),12 );13}
Go1mac := hmac.New(sha256.New, []byte(secret))2fmt.Fprintf(mac, "%s.", timestamp)3mac.Write(body)4expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))5// hmac.Equal([]byte(expected), []byte(signatureHeader))
The timestamp is part of the signed material — reject deliveries whose X-Webhook-Timestamp is outside your tolerated skew (e.g. more than a few minutes old) to defend against replay.
Retry policy
A failed delivery is retried up to 5 times with increasing delays:
| Attempt | Delay |
|---|---|
| 1st retry | 5 seconds |
| 2nd retry | 30 seconds |
| 3rd retry | 2 minutes |
| 4th retry | 10 minutes |
| 5th retry | 1 hour |
After 5 failed attempts the delivery is marked permanently failed and the endpoint's failureCount is incremented. Once an endpoint reaches 50 cumulative failures it is auto-suspended (active: false); resume it after fixing your receiver. Make your handler idempotent — a delivery may arrive more than once (retry after a slow 2xx).
Retention
Delivery records are ephemeral — they exist for observability, not durable storage:
- Delivered and failed deliveries are removed after 30 minutes.
- All deliveries are removed after 72 hours regardless of status.
- Deleting an endpoint immediately removes all of its deliveries.
Persist anything you need to keep in your own system when the delivery arrives.
Plan limits
The number of webhook endpoints you can register depends on your plan:
| Plan | Max endpoints |
|---|---|
| Free | 2 |
| Beginner | 4 |
| Pro | 6 |
| Enterprise | Unlimited |
Creating an endpoint beyond your plan limit returns an error; delete an unused endpoint or upgrade your plan.