Describes the inbound HTTP request the platform sends to your callback URL when delivering events: request method and headers, the signature verification algorithm, event envelope fields, and idempotency, retry, and delivery guarantees. The event body carries only resource identifiers; query the latest state via data.id.
Overview
After you create a Webhook and subscribe to named events, the platform delivers a notification to the configured callback URL whenever an event occurs. This page describes the inbound request contract the platform sends: your receiver only needs to verify the signature per this contract, parse the event envelope, and query the latest resource state via data.id.
The event body does not carry the full resource content — only the event type, resource identifiers, and essential context. The receiver queries the latest resource by calling the corresponding GET endpoint with data.id.
Supported events
events supports the following 32 named events. Wildcard subscriptions such as * are not supported.
| Category | Events |
|---|---|
| Session management plane | session.created, session.updated, session.archived, session.deleted |
| Session run status | session.status_run_started, session.status_idled, session.status_terminated |
| Session Thread | session.thread_created, session.thread_run_started, session.thread_idled, session.thread_terminated |
| Agent | agent.created, agent.updated, agent.archived |
| Deployment | deployment.created, deployment.updated, deployment.archived, deployment.paused, deployment.unpaused |
| Deployment Run | deployment_run.started, deployment_run.failed, deployment_run.succeeded |
| Environment | environment.created, environment.updated, environment.archived, environment.deleted |
| Vault | vault.created, vault.archived, vault.deleted |
| Vault Credential | vault_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 example:
| Header | Description |
|---|---|
Content-Type | Always application/json |
User-Agent | Always Bailian-ManagedAgent-Webhook/1.0 |
webhook-id | The outer event.id, format whe_<ULID>; used for idempotent deduplication and as part of the signed content |
webhook-timestamp | Unix second-precision timestamp generated for this delivery; regenerated on each delivery |
webhook-signature | Signature, format v1, plus the Base64-encoded HMAC-SHA256 result |
Signature verification
Signed content and algorithm:
- The value of
webhook-idis the outerevent.id, not thewebhook_idof the Webhook configuration. signing_secretiswhsec_plus standard Base64 text. When verifying, strip thewhsec_prefix and decode the remainder with standard Base64; do not use the fullwhsec_...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.
The signing secret is returned only in the successful responses of Create and reset-secret; later queries do not return it. Store it securely.
Event envelope fields
A regular event body:
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:
| Field | Type | Description |
|---|---|---|
type | string | Always event |
id | string | Event ID, format whe_<ULID>; all delivery attempts for the same event share the same outer id, matching the webhook-id header |
created_at | string | Event creation time, ISO 8601 |
data | object | Event payload |
data.id | string | Resource identifier; for Thread events, this is the Session identifier |
data.type | string | Event type |
data.workspace_id | string | Workspace ID |
data.session_thread_id | string | For Thread events, the specific Thread identifier |
data.vault_id | string | Carried in Vault Credential events |
Idempotency, retry, and delivery guarantees
The receiver does not need to return a response body and should return a status code within 5 seconds. The platform decides subsequent behavior based on the receiver's response or error:
| Receiver response or error | Platform behavior |
|---|---|
| 200–299 | Delivery succeeded; no retry |
| 300–399 | Redirects are not followed; no retry; the current Webhook is disabled immediately |
| 408, 425, 429 | Current request failed; enters retry |
| Other 400–499 statuses | Current delivery ultimately failed; no retry |
| 500–599 | Current request failed; enters retry |
| DNS resolution, connection, write, or read timeout | Current request failed; enters retry |
| HTTPS certificate or hostname verification failure | No retry; the current Webhook is disabled immediately |
| Domain resolves to a private or reserved address | Connection is refused; the current Webhook is disabled immediately |
webhook.test sends a single synchronous request only, is not retried, and does not affect the consecutive-failure count.
Other delivery semantics:
- Possible duplicates: The same event may be delivered multiple times, always with the same
event.id; deduplicate idempotently by that identifier. - No ordering guarantee: Events may arrive out of order; sort by
created_atwhen ordering is required, and treat the resource query result as the source of truth for final state. - Query retention: Delivered events are retained for 7 days, after which they can no longer be queried.