Skip to content
Runtime

How to receive webhooks for sandbox events

Call runtime.webhooks.create({ url, events }). Runtime sends each event as a POST request to your HTTPS URL. Each request is signed. Runtime retries for about three days.

On Runtime, you learn about a sandbox that stopped, failed to start, or could not wake immediately, as it happens. You do not need to run a polling loop. Thirteen event types cover sandboxes, snapshots and volumes. Runtime signs every delivery with HMAC-SHA256. If your endpoint is down, Runtime retries ten times over about three days. Each delivery is logged with your endpoint's status and timing. Webhooks cost nothing extra (metrics and webhooks).

Create a webhook

TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();const hook = await runtime.webhooks.create({  url: "https://example.com/hooks/runtime",  events: ["sandbox.stopped", "sandbox.start_failed", "sandbox.wake_failed"],});console.log(hook.id, hook.secret); // the secret is shown once: store it nowconst answer = await runtime.webhooks.test(hook.id); // a signed webhook.test, sent nowconsole.log(answer.lastStatus);
Pythonfrom withruntime import Runtimeruntime = Runtime()hook = runtime.webhooks.create(    url="https://example.com/hooks/runtime",    events=["sandbox.stopped", "sandbox.start_failed", "sandbox.wake_failed"],)print(hook["id"], hook["secret"])  # shown onceprint(runtime.webhooks.test(hook["id"]))
Terminalnpx withruntime webhooks create https://example.com/hooks/runtime --events sandbox.stopped,sandbox.start_failednpx withruntime webhooks test <id>

If you leave out events, you receive every type. A webhook belongs to the account. Every key and every owner sees the same webhooks. Owners can manage them on the Webhooks page. An account can have 16.

The events

Type Sent when
sandbox.created A sandbox is created and starting
sandbox.running It is ready
sandbox.paused It paused. Its memory and files are kept.
sandbox.woken A paused sandbox runs again
sandbox.stopped It stopped; stopReason says why
sandbox.start_failed It stopped before it ran. sandbox.stopped follows.
sandbox.wake_failed A wake did not complete
sandbox.host_unreachable Its server stopped responding
sandbox.host_reachable Its server responds again
sandbox.recovered Its server was lost, and the sandbox is back
sandbox.recovery_failed Its server was lost, and the sandbox cannot come back
sandbox.secret_not_applied A secret's placeholder was sent unreplaced, with the reason
sandbox.network.limited It reached an outbound connection limit, and which limit
snapshot.ready, snapshot.failed, snapshot.deleted A snapshot's lifecycle
volume.ready, volume.failed, volume.deleted, volume.recovered A volume's lifecycle
egress_secret.created, egress_secret.updated, egress_secret.deleted An egress secret was added, replaced or deleted
upstream_proxy.set, upstream_proxy.removed Your upstream proxy was set or removed
credit.low, credit.empty Credit lasts less than 5 days, or none is left
job.run.failed, job.recovered A job run failed, or a later run succeeded
agent.call.held An agent trigger held a call for you, with the reason

Each body is the event, with the resource as it was at the time of the event. It has the id, type and createdAt fields, and data.sandbox (or data.snapshot, data.volume). That object includes the state, previous state, labels, size, region and funding. Labels you set when you create a resource come back here. A handler can use them to route an event to the job that owns the sandbox.

Write the endpoint

Check the signature, act on the event, and return 204 quickly. Put heavy work in a queue, and do it after the response:

TypeScriptimport { verifyWebhook } from "withruntime";const seen = new Set<string>(); // use your database in productionexport async function POST(request: Request) {  let event;  try {    event = await verifyWebhook(      await request.text(),      request.headers.get("runtime-signature"),      process.env.RUNTIME_WEBHOOK_SECRET!,    );  } catch {    return new Response("bad signature", { status: 400 });  }  const id = request.headers.get("runtime-webhook-id") ?? event.id;  if (seen.has(id)) return new Response(null, { status: 204 }); // a retry of one already handled  seen.add(id);  if (event.type === "sandbox.stopped") {    const sandbox = event.data.sandbox as { id: string; stopReason?: string };    console.log("stopped", sandbox.id, sandbox.stopReason);  }  return new Response(null, { status: 204 });}

For how the signature check works, and how to check it without the SDK, see verify a webhook signature.

Answers, retries and the log

Rule What happens
Success Any 2xx within 10 seconds
Failure Any other status, a timeout or a redirect
Retries After 1 min, 5 min, 15 min, 1 h, 3 h, 6 h, 12 h, 24 h and 24 h: ten attempts in about three days
Same event, retried Same Runtime-Webhook-Id header on every attempt
Order Not guaranteed; order by createdAt
Delivery log Kept for 14 days. Includes each attempt, your endpoint's status, and the duration.
Where it may go HTTPS on the public internet; private, loopback and link-local addresses are refused
Terminalnpx withruntime webhooks deliveries <id>npx withruntime webhooks retry <deliveryId>npx withruntime webhooks update <id> --disable

The same actions are available in code: runtime.webhooks.deliveries(id) and runtime.webhooks.retry(deliveryId). While deliveries fail, the webhook shows failingSince. An agent manages webhooks with the runtime_webhooks_manage MCP tool.

Mistakes and how Runtime handles them

  • Parsing JSON before checking the signature. The signature covers the raw body. Read the body as text first, verify the signature, then use the data.
  • Doing slow work before responding. A response after 10 seconds counts as a failure, and Runtime sends the event again. Respond first, then do the work.
  • Handling a retry twice. A retry carries the same Runtime-Webhook-Id; skip one you have already handled.
  • An endpoint on a private address. A URL with a private address is refused when you create the webhook. Each delivery resolves the name again and connects only to the public address it checked.
  • Missing events while the endpoint was down. Nothing is lost for 14 days. runtime events (or runtime.events.list()) lists every lifecycle event, newest first, for the account or for one sandbox.

Start

Terminalnpx withruntime webhooks create https://example.com/hooks/runtime

The first command prints a link to approve in your browser. New accounts get 100 free hours of a 1 GB machine in its first 30 days, with no card.

Facts on this page were checked on 26 October 2026.

100 free hoursin your first 30 days.

of a 1 GB machine

  • No credit card
  • Four sandboxes at once, 2 vCPU and 4 GiB each
  • Then prepaid credit from $10, no plan fee
Start free, no card
Start free, no card