stealthDOCS
Automation

Webhooks

Signed callbacks for run and browser lifecycle events, verified with the Standard Webhooks scheme.

What is sent

A callback per subscribed event, containing ids and status only: fetch anything private through the resource API.

Callback body
{  "id": "evt_…",  "type": "run.succeeded",  "timestamp": "2026-09-22T10:14:02.000Z",  "project_id": "prj_…",  "data": { "id": "run_…", "status": "succeeded", "session_id": "as_…", "browser_id": "br_…", "workspace_id": "ws_…", "profile_id": null }}

Subscribable events: browser.starting, browser.running, browser.stopping, browser.stopped, browser.failed, browser.cleanup_failed, run.queued, run.running, run.succeeded, run.failed, run.cancelled. Scheduled runs produce the same events. A test delivery (webhook.test, data: { "id": "wh_…", "test": true }) is sent only on request.

Verify a delivery

Requests follow Standard Webhooks v1: headers webhook-id, webhook-timestamp (Unix seconds) and webhook-signature (v1,<Base64 HMAC-SHA256>). The signed message is the exact UTF-8 bytes of id.timestamp.raw_body, keyed with the Base64 part of the secret after its whsec_ prefix. Verify before parsing; compare in constant time; reject timestamps outside five minutes; deduplicate by event id in the same transaction as your own write.

from stealth_browser import verify_webhook # in your HTTP handlerevent = verify_webhook(raw_body, request.headers, os.environ["STEALTH_WEBHOOK_SECRET"])   # raises StealthApiError("webhook_signature")print(event["type"], event["data"]["id"])

The extra x-stealth-delivery-id and x-stealth-attempt-id headers are diagnostics; only the signed message establishes trust.

Delivery

A 2xx acknowledges. Connection, DNS and timeout errors, 408, 425, 429 and 5xx are retried up to six attempts at 30 s, 2 m, 10 m, 1 h and 6 h (Retry-After can extend a delay to 24 h); any other response, redirects included, fails permanently. Attempts have a ten-second budget and keep 4 KiB of the response. Events can arrive more than once or out of order, particularly after a crash; a retried notification never re-executes its run.

Callback URLs must be public HTTPS without userinfo, query or fragment; every DNS answer is checked and private, loopback, link-local and metadata addresses are refused. Redirects are not followed.

Managing endpoints

Endpoints are managed by owners and admins with an account session in the console (Configuration → Webhooks); keys cannot manage or inspect them.

Method and pathResult
GET /v1/webhooks/policySupported events and limits
GET /v1/webhooks, GET /v1/webhooks/{id}Endpoints with delivery counters; one endpoint (never its secret)
POST /v1/webhooks{ "name", "url", "events", "enabled" } → the endpoint with signing_secret, once
PATCH /v1/webhooks/{id}Changed fields plus revision; edits and pauses cancel pending deliveries
DELETE /v1/webhooks/{id}{ "revision" }
POST /v1/webhooks/{id}/rotate{ "revision" } → a new secret, once; already dispatched requests may still use the old key
POST /v1/webhooks/{id}/test{} with Idempotency-Key → a real webhook.test delivery
GET /v1/webhooks/{id}/deliveries?before=The newest 50 records and a cursor
GET /v1/webhooks/{id}/deliveries/{delivery}Payload, attempts, HTTP status, timings and the bounded response text
POST /v1/webhooks/{id}/deliveries/{delivery}/replay{} with Idempotency-Key → a new delivery with the same event id and body

Limits: 20 live endpoints per project; pending deliveries expire after seven days and history is kept seven days after completion.