Incoming Webhooks

Incoming webhooks let external systems (GitHub, Stripe, your own app) POST events to Islo. Each delivery is authenticated, mapped to a sandbox, and runs a sequence of actions — create the sandbox if needed, resume it, forward the payload to a port inside it, pause, or delete.

Create a webhook via the SDK or POST /webhooks/incoming. The response includes a receiver_url — paste that into GitHub, Stripe, or any HTTP client. External callers hit that URL; you manage the config through the authenticated API.

How a delivery flows

External service POST → receiver_url
├── Authenticate (HMAC, JWT, basic, etc.)
├── Dedupe (idempotency key)
├── Resolve target sandbox name (fixed or from event payload)
└── For each matching rule:
run actions in order
ensure_sandbox → resume_sandbox → deliver_to_port → …

Management endpoints (all under the compute plane Webhooks tag in API Reference):

OperationPurpose
CreateRegister a receiver; returns receiver_url
List / GetInspect existing receivers
UpdateReplace auth, target, rules, or status
DeleteSoft-delete a receiver

Set status to disabled to stop processing without deleting the config.

Target resolution

Every delivery targets exactly one sandbox. Choose how the name is determined:

target_typeUse when
fixed_sandbox_nameAll events go to one sandbox (sandbox_name: "staging-receiver")
sandbox_name_from_eventName is extracted from the request via a source (header, query, json_path, etc.)

For sandbox_name_from_event, optional guards restrict what names are allowed:

  • required_prefix — e.g. pr- so only pr-42 style names match
  • allowed_pattern — regex allowlist
  • allowed_names — explicit list

Example: derive a sandbox name from a GitHub PR number in the JSON body:

1target={
2 "target_type": "sandbox_name_from_event",
3 "source": {"source": "json_path", "path": "$.pull_request.number"},
4 "required_prefix": "pr-",
5}

Rules and actions

A webhook has one or more rules. Each rule has:

  • when (optional) — json_path + equals filter on the payload; omit to run on every delivery
  • actions — ordered list of steps to run against the resolved sandbox
action_typeWhat it does
ensure_sandboxCreate the sandbox from template if it does not exist; no-op if it already exists
resume_sandboxResume a paused sandbox
pause_sandboxPause a running sandbox
delete_sandboxDelete the sandbox
deliver_to_portForward the webhook payload to a TCP port inside the sandbox (port, optional path, payload, auto_resume)

ensure_sandbox is the create-if-missing step. It does not recreate or update an existing sandbox. The template mirrors a create request: image, vcpus, memory_mb, disk_gb, optional gateway_profile, workdir, and optional lifecycle for auto-pause and delete.

Typical preview-env chain: ensure_sandboxdeliver_to_port with auto_resume: on_activity so a cold sandbox wakes before the HTTP forward.

Authentication

auth verifies the caller before any rule runs. Supported auth_type values:

auth_typeNotes
noneNo verification (use only for testing)
hmacSignature over a signed payload (GitHub, Stripe-style)
header_equals / query_equalsStatic shared secret in a header or query param
basic / bearer_staticHTTP basic or static bearer token
jwtJWKS-based JWT validation
ip_allowlistSource IP restriction
all / anyCombine multiple verifiers

Secrets are passed inline at create time as {name, value} pairs inside the verifier config. Responses redact values and return secret_ref names only.

Idempotency

idempotency deduplicates retries. Extract a key from:

  • a header (source: header, name: X-GitHub-Delivery)
  • a header param (source: header_param)
  • a JSON path in the body
  • the raw body SHA-256 (source: body_sha256)

Duplicate keys within the retention window are acknowledged without re-running actions.

Example: GitHub PR preview

Per-PR sandbox with lifecycle policy, GitHub HMAC auth, and delivery to a dev server on port 3000:

1import os
2from islo import Islo
3
4client = Islo()
5
6webhook = client.webhooks.create_incoming_webhook(
7 name="github-pr-preview",
8 status="active",
9 auth={
10 "auth_type": "hmac",
11 "algorithm": "sha256",
12 "encoding": "hex",
13 "prefix": "sha256=",
14 "secret": {
15 "name": "github_webhook_secret",
16 "value": os.environ["GITHUB_WEBHOOK_SECRET"],
17 },
18 "signature": {"source": "header", "name": "X-Hub-Signature-256"},
19 "signed_payload": {"type": "raw_body"},
20 },
21 target={
22 "target_type": "sandbox_name_from_event",
23 "source": {"source": "json_path", "path": "$.pull_request.number"},
24 "required_prefix": "pr-",
25 },
26 idempotency={"source": "header", "name": "X-GitHub-Delivery"},
27 rules=[
28 {
29 "when": {"json_path": "$.action", "equals": "opened"},
30 "actions": [
31 {
32 "action_type": "ensure_sandbox",
33 "template": {
34 "image": "ghcr.io/islo-labs/islo-runner:latest",
35 "vcpus": 2,
36 "memory_mb": 4096,
37 "disk_gb": 20,
38 "lifecycle": {
39 "pause_after_idle": 900,
40 "delete_after": 604800, # 7 days
41 "auto_resume": "on_activity",
42 },
43 },
44 },
45 {
46 "action_type": "deliver_to_port",
47 "port": 3000,
48 "auto_resume": "on_activity",
49 },
50 ],
51 },
52 {
53 "when": {"json_path": "$.action", "equals": "closed"},
54 "actions": [{"action_type": "delete_sandbox"}],
55 },
56 ],
57)
58
59print(webhook.receiver_url) # paste into GitHub → Settings → Webhooks

deliver_to_port has its own auto_resume field, separate from the sandbox lifecycle.auto_resume. Set both to on_activity when you want webhook delivery to wake a paused sandbox before forwarding.

payload on deliver_to_port controls what gets forwarded (original sends the raw webhook body). Optional path appends a path segment on the upstream request.

Tips

  • PR previews: combine ensure_sandbox, a lifecycle policy, and deliver_to_port so each PR gets its own sandbox that pauses when idle and deletes on closed.
  • Cold starts: set auto_resume: on_activity on both the sandbox lifecycle and deliver_to_port so paused sandboxes wake before the payload is forwarded.
  • Deduping: always configure idempotency for providers that retry (GitHub, Stripe) to avoid duplicate sandbox actions.