Webhooks
SandBase supports webhooks for receiving real-time notifications about sandbox lifecycle events. Webhooks are E2B-compatible and deliver HTTP POST requests to your configured endpoint whenever a sandbox state changes.
Webhook Endpoint
SandBase receives E2B webhook callbacks at:
POST https://api.sandbase.ai/api/webhooks/e2bThis endpoint is used by the E2B platform to notify SandBase of sandbox state changes. For your own applications, you can register custom webhook URLs to receive these events forwarded from SandBase.
Registering Webhooks
Via API
Register a webhook endpoint to receive sandbox lifecycle events:
POST /webhooksRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Human-readable webhook name |
url | string | Yes | HTTPS URL to receive webhook events |
events | string[] | Yes | Event types to subscribe to |
enabled | boolean | No | Whether the webhook is active (default: true) |
signatureSecret | string | No | Secret for signature verification |
Example Request
curl -X POST https://api.sandbase.ai/webhooks \
-H "Authorization: Bearer sk-sb-your-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Sandbox Monitor",
"url": "https://your-app.com/webhooks/sandbox",
"events": [
"sandbox.lifecycle.created",
"sandbox.lifecycle.killed",
"sandbox.lifecycle.paused",
"sandbox.lifecycle.resumed"
],
"enabled": true,
"signatureSecret": "whsec_your-secret-key"
}'import requests
response = requests.post(
"https://api.sandbase.ai/webhooks",
headers={"Authorization": "Bearer sk-sb-your-key"},
json={
"name": "Production Sandbox Monitor",
"url": "https://your-app.com/webhooks/sandbox",
"events": [
"sandbox.lifecycle.created",
"sandbox.lifecycle.killed",
"sandbox.lifecycle.paused",
"sandbox.lifecycle.resumed"
],
"enabled": True,
"signatureSecret": "whsec_your-secret-key"
}
)
webhook = response.json()["data"]
print(f"Webhook ID: {webhook['id']}")Via Dashboard
You can also manage webhooks through the SandBase Dashboard under Settings → Webhooks.
Event Types
| Event Type | Description |
|---|---|
sandbox.lifecycle.created | A new sandbox has been created and is running |
sandbox.lifecycle.killed | A sandbox has been destroyed (manually or by timeout) |
sandbox.lifecycle.paused | A sandbox has been paused |
sandbox.lifecycle.resumed | A paused sandbox has been resumed |
sandbox.lifecycle.updated | Sandbox metadata or configuration was updated |
Payload Format
All webhook events are delivered as HTTP POST requests with a JSON body:
{
"id": "evt_01H9X2K3M4N5P6Q7R8S9T0",
"version": "1.0",
"type": "sandbox.lifecycle.created",
"timestamp": "2026-07-01T10:00:00.000Z",
"event_category": "sandbox",
"event_label": "lifecycle",
"sandbox_id": "sbx_a1b2c3d4e5f6",
"sandbox_execution_id": "exec_x1y2z3",
"sandbox_template_id": "code_interpreter",
"sandbox_build_id": "build_m1n2o3",
"sandbox_team_id": "team_p4q5r6",
"event_data": {
"status": "running",
"timeout": 300
}
}Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique event identifier |
version | string | Payload schema version |
type | string | Event type (see table above) |
timestamp | string | ISO 8601 timestamp of the event |
event_category | string | Event category (e.g., "sandbox") |
event_label | string | Event label (e.g., "lifecycle") |
sandbox_id | string | ID of the affected sandbox |
sandbox_execution_id | string | Execution ID (E2B internal) |
sandbox_template_id | string | Template used to create the sandbox |
sandbox_build_id | string | Build ID of the template |
sandbox_team_id | string | Team/organization ID |
event_data | object | Additional event-specific data |
Event Data by Type
sandbox.lifecycle.created
{
"event_data": {
"status": "running",
"timeout": 300,
"template_id": "code_interpreter"
}
}sandbox.lifecycle.killed
{
"event_data": {
"reason": "timeout",
"duration_seconds": 300
}
}sandbox.lifecycle.paused
{
"event_data": {
"previous_status": "running"
}
}sandbox.lifecycle.resumed
{
"event_data": {
"previous_status": "paused",
"new_timeout": 300
}
}Signature Verification
Webhook payloads are signed using SHA-256 to verify authenticity. The signature is included in the e2b-signature header.
Verification Algorithm
The signature is computed as:
signature = base64_encode(SHA256(secret + raw_body))Trailing = padding characters are stripped from the base64 output.
Verification Example
import hashlib
import base64
def verify_webhook_signature(raw_body: bytes, signature: str, secret: str) -> bool:
"""Verify an E2B webhook signature."""
# Compute expected signature
payload = secret.encode() + raw_body
hash_bytes = hashlib.sha256(payload).digest()
expected = base64.b64encode(hash_bytes).decode().rstrip("=")
return expected == signatureimport crypto from 'crypto';
function verifyWebhookSignature(rawBody, signature, secret) {
// Compute expected signature
const payload = secret + rawBody;
const hash = crypto.createHash('sha256').update(payload).digest('base64');
const expected = hash.replace(/=+$/, '');
return expected === signature;
}Using in a Request Handler
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "whsec_your-secret-key"
@app.route("/webhooks/sandbox", methods=["POST"])
def handle_webhook():
raw_body = request.get_data()
signature = request.headers.get("e2b-signature", "")
if not verify_webhook_signature(raw_body, signature, WEBHOOK_SECRET):
abort(401, "Invalid signature")
payload = request.get_json()
event_type = payload["type"]
if event_type == "sandbox.lifecycle.killed":
sandbox_id = payload["sandbox_id"]
print(f"Sandbox {sandbox_id} was destroyed")
# Handle cleanup...
return {"received": True}, 200import express from 'express';
const app = express();
const WEBHOOK_SECRET = 'whsec_your-secret-key';
app.post('/webhooks/sandbox', express.raw({ type: 'application/json' }), (req, res) => {
const rawBody = req.body.toString();
const signature = req.headers['e2b-signature'] || '';
if (!verifyWebhookSignature(rawBody, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const payload = JSON.parse(rawBody);
if (payload.type === 'sandbox.lifecycle.killed') {
console.log(`Sandbox ${payload.sandbox_id} was destroyed`);
// Handle cleanup...
}
res.json({ received: true });
});Delivery and Retry Policy
Delivery
- Webhooks are delivered via HTTP POST with
Content-Type: application/json - Your endpoint must respond with a
2xxstatus code within 30 seconds - Any non-2xx response or timeout is treated as a delivery failure
Retry Schedule
Failed deliveries are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 12 hours |
After 5 failed attempts, the event is marked as permanently failed. You can view failed deliveries in the Dashboard.
Idempotency
Webhook events may be delivered more than once. Use the id field to deduplicate:
processed_events = set()
def handle_webhook(payload):
event_id = payload["id"]
if event_id in processed_events:
return # Already processed
processed_events.add(event_id)
# Process the event...Ordering
Events are delivered in approximate chronological order, but strict ordering is not guaranteed. Use the timestamp field to determine event sequence.
Managing Webhooks
List Webhooks
GET /webhooksReturns all webhooks for the authenticated organization.
Delete Webhook
DELETE /webhooks/:idPermanently removes a webhook registration.
Best Practices
- Always verify signatures — Never process webhook payloads without verifying the
e2b-signatureheader. - Respond quickly — Return a 200 response immediately, then process the event asynchronously.
- Handle duplicates — Use the event
idfor idempotent processing. - Use HTTPS — Webhook URLs must use HTTPS for security.
- Monitor failures — Check the Dashboard for failed deliveries and fix endpoint issues promptly.

