# Sandbox lifecycle webhooks (/docs/sandbox/lifecycle-events-webhooks)

<!-- agent-signals: reading_time_min: 6 · est_tokens: 3768 · updated: 2026-07-30 -->
Related: [Auto-resume on request](/docs/sandbox/auto-resume.md), [Connect to running sandbox](/docs/sandbox/connect.md), [Environment variables](/docs/sandbox/environment-variables.md), [Filesystem-only snapshots](/docs/sandbox/filesystem-only-snapshots.md), [Sandbox forking](/docs/sandbox/fork.md), [Git integration](/docs/sandbox/git-integration.md)

Webhooks provide a way for notifications to be delivered to an external web server whenever certain sandbox lifecycle events occur.
This allows you to receive real-time updates about sandbox creation, updates, and termination without having to poll the API.
All webhook requests require authentication using your project [API key](/docs/api-key#where-to-find-api-key).

## Webhook management [#webhook-management]

### Register webhook [#register-webhook]

Register a new webhook to receive sandbox lifecycle events.
The webhook will be registered for the project associated with your API key.
All events specified during webhook creation will be sent to URL provided during registration with [following payload](#webhook-payload).

<CodeGroup>
  <CodeBlockTabs defaultValue="JavaScript & TypeScript" groupId="javascript-typescript+python">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="JavaScript & TypeScript">
        JavaScript & TypeScript
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="Python">
        Python
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="JavaScript & TypeScript">
      ```js  
      // Register a new webhook
      const resp = await fetch(
        'https://api.e2b.app/events/webhooks',
        {
          method: 'POST',
          headers: {
            'X-API-Key': E2B_API_KEY,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            name: 'My Sandbox Webhook',
            url: 'https://your-webhook-endpoint.com/webhook',
            enabled: true,
            events: ['sandbox.lifecycle.created', 'sandbox.lifecycle.updated', 'sandbox.lifecycle.killed'],
            signatureSecret: 'secret-for-event-signature-verification'
          }),
        }
      )

      if (resp.status === 201) {
        console.log('Webhook registered successfully')
      }
      ```
    </CodeBlockTab>

    <CodeBlockTab value="Python">
      ```python  
      import requests

      # Register a new webhook
      resp = requests.post(
          "https://api.e2b.app/events/webhooks",
          headers={
              "X-API-Key": E2B_API_KEY,
              "Content-Type": "application/json",
          },
          json={
              "name": "My Sandbox Webhook",
              "url": "https://your-webhook-endpoint.com/webhook",
              "enabled": True,
              "events": ["sandbox.lifecycle.created", "sandbox.lifecycle.updated", "sandbox.lifecycle.killed"],
              "signatureSecret": "secret-for-event-signature-verification"
          }
      )

      if resp.status_code == 201:
          print("Webhook registered successfully")
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

### List webhooks [#list-webhooks]

List all registered webhooks for your project.

<CodeGroup>
  <CodeBlockTabs defaultValue="JavaScript & TypeScript" groupId="javascript-typescript+python">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="JavaScript & TypeScript">
        JavaScript & TypeScript
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="Python">
        Python
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="JavaScript & TypeScript">
      ```js  
      // List webhooks
      const resp = await fetch(
        'https://api.e2b.app/events/webhooks',
        {
          method: 'GET',
          headers: {
            'X-API-Key': E2B_API_KEY
          },
        },
      )

      if (resp.status === 200) {
        console.log('Webhooks listed successfully')
        console.log(await resp.json())
      }
      ```
    </CodeBlockTab>

    <CodeBlockTab value="Python">
      ```python  
      import requests

      # List webhooks
      resp = requests.get(
          "https://api.e2b.app/events/webhooks",
          headers={
              "X-API-Key": E2B_API_KEY
          },
      )

      if resp.status_code == 200:
          print("Webhooks listed successfully")
          print(resp.json())
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

### Get webhook configuration [#get-webhook-configuration]

Retrieve the current webhook configuration for your project.

<CodeGroup>
  <CodeBlockTabs defaultValue="JavaScript & TypeScript" groupId="javascript-typescript+python">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="JavaScript & TypeScript">
        JavaScript & TypeScript
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="Python">
        Python
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="JavaScript & TypeScript">
      ```js  
      // Get webhook configuration
      const resp = await fetch(
        `https://api.e2b.app/events/webhooks/${webhookID}`,
        {
          method: 'GET',
          headers: {
            'X-API-Key': E2B_API_KEY,
          },
        }
      )
      const webhookConfig = await resp.json()
      console.log(webhookConfig)
      // {
      //   "id": "<webhook-id>",
      //   "teamId": "<your-team-id>",
      //   "name": "My Sandbox Webhook",
      //   "createdAt": "2025-08-06T21:00:00Z",
      //   "enabled": true,
      //   "url": "https://your-webhook-endpoint.com/webhook",
      //   "events": ["sandbox.lifecycle.created", "sandbox.lifecycle.killed"]
      // }

      ```
    </CodeBlockTab>

    <CodeBlockTab value="Python">
      ```python  
      import requests

      # Get webhook configuration
      resp = requests.get(
          "https://api.e2b.app/events/webhooks/{webhookID}",
          headers={
              "X-API-Key": E2B_API_KEY,
          }
      )

      webhook_config = resp.json()
      print(webhook_config)
      # {
      #   "id": "<webhook-id>",
      #   "teamId": "<your-team-id>",
      #   "name": "My Sandbox Webhook",
      #   "createdAt": "2025-08-06T21:00:00Z",
      #   "enabled": true,
      #   "url": "https://your-webhook-endpoint.com/webhook",
      #   "events": ["sandbox.lifecycle.created", "sandbox.lifecycle.killed"]
      # }
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

### Update webhook configuration [#update-webhook-configuration]

Update an existing webhook configuration. The update will replace the previous configuration fields with provided fields.

<CodeGroup>
  <CodeBlockTabs defaultValue="JavaScript & TypeScript" groupId="javascript-typescript+python">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="JavaScript & TypeScript">
        JavaScript & TypeScript
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="Python">
        Python
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="JavaScript & TypeScript">
      ```js  
      // Update webhook configuration
      const resp = await fetch(
        `https://api.e2b.app/events/webhooks/${webhookID}`,
        {
          method: 'PATCH',
          headers: {
            'X-API-Key': E2B_API_KEY,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            url: 'https://your-updated-webhook-endpoint.com/webhook',
            enabled: false,
            events: ['sandbox.lifecycle.created']
          }),
        }
      )

      if (resp.status === 200) {
        console.log('Webhook updated successfully')
      }
      ```
    </CodeBlockTab>

    <CodeBlockTab value="Python">
      ```python  
      import requests

      # Update webhook configuration
      resp = requests.patch(
          "https://api.e2b.app/events/webhooks/{webhookID}",
          headers={
              "X-API-Key": E2B_API_KEY,
              "Content-Type": "application/json",
          },
          json={
              "url": "https://your-updated-webhook-endpoint.com/webhook",
              "enabled": False,
              "events": ["sandbox.lifecycle.created"]
          }
      )

      if resp.status_code == 200:
          print("Webhook updated successfully")
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

### Delete webhook [#delete-webhook]

Unregister the webhook.

<CodeGroup>
  <CodeBlockTabs defaultValue="JavaScript & TypeScript" groupId="javascript-typescript+python">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="JavaScript & TypeScript">
        JavaScript & TypeScript
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="Python">
        Python
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="JavaScript & TypeScript">
      ```js  
      // Delete webhook configuration
      const resp = await fetch(
        `https://api.e2b.app/events/webhooks/${webhookID}`,
        {
          method: 'DELETE',
          headers: {
            'X-API-Key': E2B_API_KEY,
          },
        }
      )

      if (resp.status === 200) {
        console.log('Webhook deleted successfully')
      }
      ```
    </CodeBlockTab>

    <CodeBlockTab value="Python">
      ```python  
      import requests

      # Delete webhook configuration
      resp = requests.delete(
          "https://api.e2b.app/events/webhooks/{webhookID}",
          headers={
              "X-API-Key": E2B_API_KEY,
          }
      )

      if resp.status_code == 200:
          print("Webhook deleted successfully")
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

## Webhook payload [#webhook-payload]

When a webhook is triggered, your endpoint will receive a POST request with a JSON payload containing the sandbox event data.
The payload structure matches the event format from the API:

```json
{
  "id": "<event-id>",
  "version": "v2",
  "type": "<sandbox-event-type>",
  "timestamp": "2025-08-06T20:59:24Z",
  "event_category": "lifecycle",
  "event_label": "kill",
  "event_data": {
    "sandbox_metadata": {
      "<custom-key>": "<custom-value>"
    },
    "execution": {
      "started_at": "2025-08-06T20:58:24Z",
      "vcpu_count": 2,
      "memory_mb": 512,
      "execution_time": 1000
    }
  },
  "sandbox_id": "<your-sandbox-id>",
  "sandbox_execution_id": "<sandbox-unique-execution-id>",
  "sandbox_template_id": "<your-template-id>",
  "sandbox_build_id": "<template-build-id>",
  "sandbox_team_id": "<your-team-id>"
}
```

`event_data.execution` contains sandbox execution details and is included on `sandbox.lifecycle.killed` and `sandbox.lifecycle.paused` events:

* `started_at` - UTC RFC3339 timestamp when the current sandbox execution started
* `vcpu_count` - Number of vCPUs assigned to the sandbox
* `memory_mb` - Memory assigned to the sandbox in MB
* `execution_time` - Sandbox runtime in milliseconds

# Webhook verification [#webhook-verification]

To ensure the authenticity of webhook requests, each request includes a signature in the `e2b-signature` header.
You can verify the signature using the signature secret you provided when registering the webhook.
This confirms that the request originated from E2B and has not been tampered with.

<CodeGroup>
  <CodeBlockTabs defaultValue="JavaScript & TypeScript" groupId="golang+javascript-typescript+python">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="JavaScript & TypeScript">
        JavaScript & TypeScript
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="Python">
        Python
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="Golang">
        Golang
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="JavaScript & TypeScript">
      ```js  
      function verifyWebhookSignature(secret : string, payload : string, payloadSignature : string) {
          const expectedSignatureRaw = crypto.createHash('sha256').update(secret + payload).digest('base64');
          const expectedSignature = expectedSignatureRaw.replace(/=+$/, '');
          return  expectedSignature == payloadSignature
      }

      const payloadValid = verifyWebhookSignature(secret, webhookBodyRaw, webhookSignatureHeader)
      if (payloadValid) {
          console.log("Payload signature is valid")
      } else {
          console.log("Payload signature is INVALID")
      }
      ```
    </CodeBlockTab>

    <CodeBlockTab value="Python">
      ```python  
      import hashlib
      import base64

      def verify_webhook_signature(secret: str, payload: str, payload_signature: str) -> bool:
          hash_bytes = hashlib.sha256((secret + payload).encode('utf-8')).digest()
          expected_signature = base64.b64encode(hash_bytes).decode('utf-8')
          expected_signature = expected_signature.rstrip('=')

          return expected_signature == payload_signature

      if verify_webhook_signature(secret, webhook_body_raw, webhook_signature_header):
          print("Payload signature is valid")
      else:
          print("Payload signature is INVALID")
      ```
    </CodeBlockTab>

    <CodeBlockTab value="Golang">
      ```go  
      import (
          "crypto/sha256"
          "encoding/base64"
          "fmt"
          "strings"
      )

      func verifyWebhookSignature(secret, payload, payloadSignature string) bool {
          hash := sha256.Sum256([]byte(secret + payload))
          expectedSignature := base64.StdEncoding.EncodeToString(hash[:])

          expectedSignature = strings.TrimRight(expectedSignature, "=")

          return expectedSignature == payloadSignature
      }

      if verifyWebhookSignature(secret, webhookBodyString, webhookSignatureHeaderString) {
          fmt.Println("Payload signature is valid")
      } else {
          fmt.Println("Payload signature is INVALID")
      }
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

## Webhook request headers [#webhook-request-headers]

When webhooks is send, we are adding headers to help you verify the authenticity of the request and make debugging easier.

* `e2b-webhook-id` - Webhook ID that triggered the event
* `e2b-delivery-id` - Unique ID for the delivery attempt
* `e2b-signature-version` - Currently always `v1`, reserved for future use
* `e2b-signature` - Signature for verifying the request authenticity\`

## Delivery and retries [#delivery-and-retries]

If a delivery fails, E2B retries it **up to 3 times, 10 seconds apart**. A delivery is treated as failed when your endpoint returns a 4xx or 5xx status code, the request times out, or it fails with a network error.

Because a delivery can be retried, the same event may be delivered more than once. Make your webhook handler idempotent and use the `e2b-delivery-id` header to deduplicate deliveries you've already processed.

## Available event types [#available-event-types]

The following event types can be subscribed to via webhooks, they are used as the `type` field in the [payload](#webhook-payload).

* `sandbox.lifecycle.created` - Sandbox creation
* `sandbox.lifecycle.killed` - Sandbox termination
* `sandbox.lifecycle.updated` - Sandbox configuration updates
* `sandbox.lifecycle.paused` - Sandbox pausing
* `sandbox.lifecycle.resumed` - Sandbox resuming
* `sandbox.lifecycle.checkpointed` - Sandbox [snapshot](/docs/sandbox/snapshots) created
