Skip to content

feat(cubeapi): add sandbox lifecycle webhooks#690

Open
ihanzh wants to merge 1 commit into
TencentCloud:masterfrom
ihanzh:master
Open

feat(cubeapi): add sandbox lifecycle webhooks#690
ihanzh wants to merge 1 commit into
TencentCloud:masterfrom
ihanzh:master

Conversation

@ihanzh

@ihanzh ihanzh commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add CubeAPI webhook delivery for sandbox lifecycle events: sandbox.created, sandbox.deleted, sandbox.paused, and sandbox.resumed.
  • Support endpoint configuration through CUBE_API_WEBHOOKS, including event subscriptions, optional HMAC-SHA256 signatures, per-attempt timeout, and retries.
  • Deliver webhook events asynchronously through a background queue so lifecycle API paths are not blocked by slow or unreachable receivers.
  • Add a runnable webhook receiver example under examples/webhook-receiver/.
  • Add English and Chinese integration docs under docs/guide/webhooks.md and docs/zh/guide/webhooks.md.

Validation

Format check - CubeAPI

cargo fmt --manifest-path CubeAPI/Cargo.toml -- --check
<no output>

Unit tests - CubeAPI webhook delivery

cargo test --manifest-path CubeAPI/Cargo.toml logging::http -- --nocapture
running 4 tests
test logging::http::tests::unreachable_endpoint_does_not_block_log_call ... ok
test logging::http::tests::sends_subscribed_event_with_signature ... ok
test logging::http::tests::ignores_unsubscribed_event ... ok
test logging::http::tests::retries_failed_delivery ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 67 filtered out; finished in 0.21s

Note: the same test command first failed inside the Codex sandbox because the sandbox disallows binding temporary localhost test servers. Re-running it in the normal local environment passed as shown above.

Receiver syntax check

python3 -m py_compile examples/webhook-receiver/receiver.py
<no output>

CubeAPI build used for dev-env verification

cargo build --manifest-path CubeAPI/Cargo.toml
Finished `dev` profile [unoptimized + debuginfo] target(s) in 12.22s

Docs build

cd docs
npm ci
npm run docs:build
added 258 packages, and audited 259 packages in 19s

14 vulnerabilities (7 moderate, 7 high)

> project-docs@1.0.0 docs:build
> vitepress build

vitepress v1.6.4

- building client + server bundles...
The language 'env' is not loaded, falling back to 'txt' for syntax highlighting.
(!) Some chunks are larger than 500 kB after minification.
✓ building client + server bundles...
- rendering pages...
✓ rendering pages...
build complete in 12.67s.

Local End-to-End Verification

  • Started/reused the dev-env VM and verified CubeAPI health through http://127.0.0.1:13000/health.
  • Ran the example receiver on the host with WEBHOOK_SECRET=dev-secret.
  • Configured VM CubeAPI to deliver callbacks to http://10.0.2.2:9000/webhook.
  • Created, paused, resumed, and deleted a sandbox using the prepared local template.
  • Verified the receiver accepted signed callbacks for all four lifecycle events.

Receiver startup and CubeAPI configuration

WEBHOOK_HOST=0.0.0.0 WEBHOOK_PORT=9000 WEBHOOK_SECRET=dev-secret \
  python3 examples/webhook-receiver/receiver.py
listening on http://0.0.0.0:9000/webhook

CubeAPI in the dev VM was started with:

CUBE_API_WEBHOOKS=[{"url":"http://10.0.2.2:9000/webhook","events":["sandbox.created","sandbox.deleted","sandbox.paused","sandbox.resumed"],"secret":"dev-secret","timeout_secs":2,"max_retries":1}]

CubeAPI startup log:

INFO cube_api: webhook event delivery enabled endpoints=1
INFO cube_api: cube-api listening on 0.0.0.0:3000

Lifecycle API calls

curl --noproxy '*' -sS -i --max-time 10 http://127.0.0.1:13000/health
HTTP/1.1 200 OK

{"status":"ok","sandboxes":0}
curl --noproxy '*' -sS -i --max-time 120 -X POST http://127.0.0.1:13000/sandboxes \
  -H 'Content-Type: application/json' \
  -d '{"templateID":"tpl-944f0efeeeb849429243662c","timeout":300,"allowInternetAccess":true}'
HTTP/1.1 201 Created

{"templateID":"tpl-944f0efeeeb849429243662c","sandboxID":"020ad9175e93433e8e3aca7eb03f7fbf","clientID":"e388dbee-6c38-49b5-a022-93d249a65028","envdVersion":"0.5.11","domain":"cube.app"}
curl --noproxy '*' -sS -i --max-time 120 -X POST http://127.0.0.1:13000/sandboxes/020ad9175e93433e8e3aca7eb03f7fbf/pause
HTTP/1.1 204 No Content
curl --noproxy '*' -sS -i --max-time 120 -X POST http://127.0.0.1:13000/sandboxes/020ad9175e93433e8e3aca7eb03f7fbf/resume \
  -H 'Content-Type: application/json' \
  -d '{"timeout":300}'
HTTP/1.1 201 Created

{"templateID":"tpl-944f0efeeeb849429243662c","sandboxID":"020ad9175e93433e8e3aca7eb03f7fbf","clientID":"10.0.2.15","envdVersion":"0.5.11","domain":"cube.app"}
curl --noproxy '*' -sS -i --max-time 120 -X DELETE http://127.0.0.1:13000/sandboxes/020ad9175e93433e8e3aca7eb03f7fbf
HTTP/1.1 204 No Content
curl --noproxy '*' -sS -i --max-time 30 http://127.0.0.1:13000/sandboxes
HTTP/1.1 200 OK

[]

Receiver logs

{"path": "/webhook", "event": "sandbox.created", "payload": {"event": "sandbox.created", "timestamp": "2026-07-10T23:38:59.529838575+00:00", "sandbox_id": "020ad9175e93433e8e3aca7eb03f7fbf", "template_id": "tpl-944f0efeeeb849429243662c"}}
127.0.0.1 - - [11/Jul/2026 07:38:57] "POST /webhook HTTP/1.1" 204 -
{"path": "/webhook", "event": "sandbox.paused", "payload": {"event": "sandbox.paused", "timestamp": "2026-07-10T23:39:06.843288236+00:00", "sandbox_id": "020ad9175e93433e8e3aca7eb03f7fbf"}}
127.0.0.1 - - [11/Jul/2026 07:39:04] "POST /webhook HTTP/1.1" 204 -
{"path": "/webhook", "event": "sandbox.resumed", "payload": {"event": "sandbox.resumed", "timestamp": "2026-07-10T23:39:08.065672717+00:00", "sandbox_id": "020ad9175e93433e8e3aca7eb03f7fbf"}}
127.0.0.1 - - [11/Jul/2026 07:39:05] "POST /webhook HTTP/1.1" 204 -
{"path": "/webhook", "event": "sandbox.deleted", "payload": {"event": "sandbox.deleted", "timestamp": "2026-07-10T23:39:10.204147207+00:00", "sandbox_id": "020ad9175e93433e8e3aca7eb03f7fbf"}}
127.0.0.1 - - [11/Jul/2026 07:39:07] "POST /webhook HTTP/1.1" 204 -

Notes For Maintainers / Follow-ups

This first version intentionally keeps the implementation focused on CubeAPI lifecycle webhook delivery through CUBE_API_WEBHOOKS.

Maintainer confirmation:

  • Current defaults are timeout_secs=3, max_retries=3, with an initial exponential backoff of 200ms. Please confirm whether these defaults are appropriate for production workloads.
  • sandbox.resumed is emitted for the explicit /sandboxes/{sandboxID}/resume API. Please confirm whether /sandboxes/{sandboxID}/connect, when it auto-resumes a paused sandbox, should emit the same event.
  • Webhook configuration is environment-based only. API-managed or persisted webhook registry support is not included in this first version.
  • flush() drains the internal dispatch queue without waiting for all spawned HTTP delivery tasks. This keeps shutdown best-effort and avoids blocking on unreachable receivers; please confirm whether bounded shutdown waiting is desired.

Possible follow-up hardening:

  • Define lifecycle event names in a shared code-maintained enum/whitelist so handler emission and subscription validation cannot drift.
  • Use bounded delivery queues and fixed workers, potentially with per-endpoint isolation.
  • Add a stable event_id for receiver-side retry deduplication.
  • Add bounded graceful-shutdown waiting plus dropped/failed delivery metrics if the project adopts a metrics path.
  • Validate endpoint URLs at startup, limiting them to HTTP(S) while avoiding credential/path/query leakage in logs.

Related Issue

Assisted-by: OpenCode:gpt-5.5-pro

Signed-off-by: Han Zhang <ihanzhzh@gmail.com>
@fslongjin

Copy link
Copy Markdown
Member

Refs #642 (this is a multi-participation issue, please use Refs instead of Closes)

@fslongjin

Copy link
Copy Markdown
Member

Hello, thank you for your contribution! Since we have a large number of participants in this event, to speed up the PR review process, could you please attach some screenshots and logs (both are needed) showing the verification process and results for this PR? Thank you for your participation!

@ihanzh

ihanzh commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

I added end-to-end verification screenshots for the lifecycle webhook flow.

The verification was done with CubeAPI running in the dev VM and the example webhook receiver running locally with WEBHOOK_SECRET configured.

The screenshots cover:

  • CubeAPI lifecycle operations:
    • health check
    • create sandbox
    • pause sandbox
    • resume sandbox
    • delete sandbox
    • list sandboxes after deletion
  • Webhook receiver logs showing that the configured backend received:
    • sandbox.created
    • sandbox.paused
    • sandbox.resumed
    • sandbox.deleted

I also verified the receiver returned 204 for each webhook request.

11B1119647AF015E4F026FEA2560C6E9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants