Skip to main content
Webhook

Webhook Overview and Callback Delivery

Webhooks push status-change events for sessions, agents, deployments, and more to a callback URL. This page describes delivery behavior, supported events, the callback request contract, and the signature verification algorithm.

A Webhook is a workspace-level resource, on par with sessions, agents, and environments. After you create an endpoint and subscribe to named events, the platform delivers a notification to the configured callback URL whenever an event occurs. For management endpoints, see Create Webhook and related pages.

Delivery behavior

PropertyDescription
Event contentContains only the event type, resource identifiers, and essential context; query the latest resource state via data.id
Subscription scopeOnly named events that were subscribed at the time the event occurred are delivered; new subscriptions do not backfill historical events
No ordering guaranteeEvents may arrive out of order; sort by created_at, and treat the resource query result as the source of truth for final state
Possible duplicatesThe same event may be delivered multiple times, always with the same event.id; deduplicate idempotently by that identifier
Retry on failure408, 425, 429, 5xx, and network errors are retried up to 3 times with 10s, 30s, and 1min intervals; only 2xx counts as success, and 3xx redirects are not followed
Auto-disableBy default, a Webhook is auto-disabled after 20 consecutive business events ultimately fail; 3xx, address security check failures, and HTTPS verification failures disable it immediately
Query retentionDelivered events are retained for 7 days, after which they can no longer be queried
QuotaUp to 20 Webhooks per workspace
The signing secret is returned only in the successful responses of Create and Reset; later queries do not return it. The Webhook resource primary key is uniformly id (with the wep_ prefix); webhook_id is used only for path parameters and query conditions.

Supported events

events supports the following 32 named events. Wildcard subscriptions such as * are not supported.
CategoryEvents
Session management planesession.created, session.updated, session.archived, session.deleted
Session run statussession.status_run_started, session.status_idled, session.status_terminated
Session Threadsession.thread_created, session.thread_run_started, session.thread_idled, session.thread_terminated
Agentagent.created, agent.updated, agent.archived
Deploymentdeployment.created, deployment.updated, deployment.archived, deployment.paused, deployment.unpaused
Deployment Rundeployment_run.started, deployment_run.failed, deployment_run.succeeded
Environmentenvironment.created, environment.updated, environment.archived, environment.deleted
Vaultvault.created, vault.archived, vault.deleted
Vault Credentialvault_credential.created, vault_credential.archived, vault_credential.deleted

Callback request

The platform sends a POST request to an HTTP or HTTPS address that passes the security check, and does not follow redirects. A public IP entered directly is allowed for delivery; private, loopback, link-local, and reserved addresses are refused. Each delivery regenerates webhook-timestamp and computes the signature using the current signing secret. On retries, the event body, outer id, and created_at remain unchanged.

Request headers

POST /managedagent/webhooks HTTP/1.1
Host: example.com
Content-Type: application/json
User-Agent: Bailian-ManagedAgent-Webhook/1.0
webhook-id: whe_01JXX8JY9BBM4BK4C2P7K3M3ZR
webhook-timestamp: 1785810621
webhook-signature: v1,BASE64_HMAC_SHA256

Request body

A regular event:
{
  "type": "event",
  "id": "whe_01JXX8JY9BBM4BK4C2P7K3M3ZR",
  "created_at": "2026-08-06T10:30:21.123Z",
  "data": {
    "id": "sesn_xxx",
    "type": "session.status_idled",
    "workspace_id": "ws_xxx"
  }
}
Thread events additionally carry session_thread_id: data.id is the session identifier, and data.session_thread_id is the specific thread identifier. session.thread_created, session.thread_run_started, session.thread_idled, and session.thread_terminated all use this structure:
{
  "type": "event",
  "id": "whe_01JXX8JY9BBM4BK4C2P7K3M3ZR",
  "created_at": "2026-08-06T10:30:21.123Z",
  "data": {
    "id": "sesn_xxx",
    "type": "session.thread_idled",
    "workspace_id": "ws_xxx",
    "session_thread_id": "sthread_xxx"
  }
}
Vault Credential events additionally carry vault_id in data. The event body does not carry the full resource content; the receiver queries the latest resource by calling the corresponding GET endpoint with data.id.

Signature verification

Signed content and algorithm:
signed_content = webhook-id + "." + webhook-timestamp + "." + raw_body
secret_bytes = Base64Decode(RemovePrefix(signing_secret, "whsec_"))
signature = Base64(HMAC-SHA256(secret_bytes, UTF8(signed_content)))
  • The value of webhook-id is the outer event.id, not the webhook_id of the Webhook configuration.
  • signing_secret is whsec_ plus standard Base64 text. When verifying, strip the whsec_ prefix and decode the remainder with standard Base64; do not use the full whsec_... string directly as the HMAC key.
  • Read the unmodified raw request body to verify the signature first, then perform JSON deserialization.
  • Verify that the timestamp is within 5 minutes of the current time, and use a constant-time comparison to verify the signature.
  • Deduplicate idempotently by the outer id; repeated deliveries of the same event use the same id.
  • When ordering is required, use created_at; do not rely on the receive order to infer the final resource state.
import base64
import hashlib
import hmac
import os
import time

secret = os.environ["AGENT_WEBHOOK_SECRET"]
secret_bytes = base64.b64decode(secret[len("whsec_"):])

def verify_webhook(raw_body: bytes, webhook_id: str, timestamp: str, signature_header: str) -> bool:
    try:
        if abs(time.time() - int(timestamp)) > 300:
            return False

        version, signature = signature_header.split(",", 1)
        if version != "v1":
            return False

        signed_payload = f"{webhook_id}.{timestamp}.".encode() + raw_body
        expected = base64.b64encode(
            hmac.new(secret_bytes, signed_payload, hashlib.sha256).digest()
        ).decode()
        return hmac.compare_digest(signature, expected)
    except (TypeError, ValueError):
        return False

Response

The receiver does not need to return a response body and should return a status code within 5 seconds.
Receiver response or errorPlatform behavior
200–299Delivery succeeded; no retry
300–399Redirects are not followed; no retry; the current Webhook is disabled immediately
408, 425, 429Current request failed; enters retry
Other 400–499 statusesCurrent delivery ultimately failed; no retry
500–599Current request failed; enters retry
DNS resolution, connection, write, or read timeoutCurrent request failed; enters retry
HTTPS certificate or hostname verification failureNo retry; the current Webhook is disabled immediately
Domain resolves to a private or reserved addressConnection is refused; the current Webhook is disabled immediately
After the first request for a business event fails, it is retried up to 3 more times, with wait intervals of 10s, 30s, and 1min — up to 4 actual network requests in total. If the 4th attempt still fails, a final failure is recorded. By default, a Webhook is auto-disabled after 20 consecutive business events ultimately fail. webhook.test sends a single synchronous request only, is not retried, and does not affect the consecutive-failure count.
Overview
Sandbox API
Memory API
Flow Agent API
RAG API
Connector API
Framework Integration
Assistant API (Deprecating)
  • Overview