Lifecycle events and webhooks

Coppice exposes the E2B lifecycle-events read API and lifecycle webhook management surface for sandbox creation, pause, resume, updates, snapshots, forks, and kills. Event history, webhook registrations, and delivery attempts are process-local rings: useful for SDK parity, operator UI, and smoke receipts, but not a durable audit database.

API shape

The gateway serves the same read paths E2B documents:

MethodPathNotes
GET/events/sandboxesrecent events across the gateway
GET/events/sandboxes/:idrecent events for one sandbox
GET/events/webhookslist registered lifecycle webhooks
POST/events/webhooksregister a lifecycle webhook
GET/events/webhooks/:idinspect one webhook
PATCH/events/webhooks/:idupdate URL, events, enabled state, or signing secret
DELETE/events/webhooks/:idunregister a webhook
GET/events/webhooks/deliveriesrecent delivery attempts across all webhooks
GET/events/webhooks/:id/deliveriesrecent delivery attempts for one webhook

Supported query parameters:

Event records use E2B’s field names:

{
  "version": "v1",
  "id": "597f6f48-6a45-4b66-a494-d4ea3e87aeaa",
  "type": "sandbox.lifecycle.paused",
  "eventData": null,
  "sandboxBuildId": "python",
  "sandboxExecutionId": "8b9ef69bf2354692b19a31d4ecde60c3",
  "sandboxId": "8b9ef69bf2354692b19a31d4ecde60c3",
  "sandboxTeamId": "gateway-client-id",
  "sandboxTemplateId": "python",
  "timestamp": "2026-04-26T18:41:15Z"
}

sandboxBuildId, sandboxExecutionId, and sandboxTeamId are local equivalents because a single-node Coppice gateway does not have E2B’s hosted team/build control plane.

Emission points

The event producer is deliberately colocated with the lifecycle mutation sites:

This means the event stream tracks what the gateway actually did, not what a sidecar guessed by scraping logs.

Webhook delivery

Webhook registration accepts the same core fields E2B documents:

{
  "name": "operator sink",
  "url": "https://example.invalid/coppice-events",
  "enabled": true,
  "events": ["sandbox.lifecycle.created", "sandbox.lifecycle.killed"],
  "signatureSecret": "secret-for-event-signature-verification"
}

For each matching event the gateway POSTs the event JSON to the target URL with E2B-compatible headers:

The signature matches E2B’s documented verifier shape: base64(sha256(signatureSecret + rawPayload)) with trailing = padding stripped. Failed deliveries retry three times with short backoff, and each attempt is recorded in /events/webhooks/:id/deliveries so the admin dashboard can show successful and failed delivery churn.

Receipt

Run on honor:

mise run lifecycle:events-honor
mise run lifecycle:webhooks-honor

The smoke creates a fresh jail sandbox, pauses it, resumes it, updates its timeout, kills it, then queries events after teardown. It also proves the repeated types= filter:

GET /events/sandboxes?types=sandbox.lifecycle.paused&types=sandbox.lifecycle.killed

Latest receipt:

benchmarks/results/lifecycle-events/latest.txt
benchmarks/results/lifecycle-webhooks/latest.txt

Remaining work

The parity shape is closed for the single-node gateway. Remaining production hardening is persistence: store webhook registrations and delivery attempts under /var/lib/coppice, add operator-triggered replay, and age out old delivery rows by policy.