// Execution Management (XEM)

Webhook Events

Every execution lifecycle event the router fans out over the exec.* namespace. Same envelope, same HMAC-SHA256 signing, same retry schedule as the rest of the webhook surface. Subscribe with the standard create endpoint, verify the signature, dedupe on event id.

// nav: g e envelope · g x execution · g a approvals · g s signing · g d delivery · ? chip

Overview

Snake_case on the wire. All exec.* payload field names are emitted in snake_case (for example invocation_id, tool_name, workspace_id). The JSON examples below show the exact field names that subscribers will receive.

EventStatusDescription
exec.dispatchedactiveTool invocation forwarded to a XEM agent.
exec.completedactiveTool invocation finished successfully.
exec.failedactiveTool invocation failed with a non-zero exit code or transport error.
exec.timeoutactiveTool invocation exceeded its declared timeout_ms.
exec.ambiguousreservedReserved event type; no payload is emitted at this time.
exec.approval_requestedactiveAn approval request was enqueued for a destructive invocation.
exec.approvedactiveApproval request granted by a privileged user.
exec.rejectedactiveApproval request explicitly rejected.
exec.approval_escalatedactiveApproval escalation chain advanced to the next stage.
exec.approval_timed_outactiveApproval request exceeded its configured timeout.
exec.agent_enrolledactiveA XEM execution agent completed enrollment.
exec.agent_offlineactiveA XEM agent lost its lease and went offline.
exec.discovery_pendingreservedReserved event type; no payload is emitted at this time.
exec.workspace_createdreservedReserved event type; no payload is emitted at this time.
exec.workspace_budget_exhaustedreservedReserved event type; no payload is emitted at this time.

Event types marked reserved are declared in the router enum but no payload is currently produced. Subscribers may register for them without error, but no deliveries will fire until the router begins emitting them.

Event Envelope

Every exec.* delivery uses the canonical webhook envelope. The data object varies by event type and is documented per-event below.

json
{ "id": "evt_a1b2c3d4e5f6a7b8c9d0e1f2", "object": "event", "type": "exec.completed", "created_at": 1709000100, "data": { } }
FieldTypeDescription
idstringUnique event identifier prefixed with evt_ (24 hex characters). Suitable for deduplication.
objectstringAlways the literal string event.
typestringEvent type identifier (for example exec.completed).
created_atintegerUnix timestamp (seconds since epoch) when the event was emitted.
dataobjectEvent-specific payload. Schema depends on type.

The project id and webhook delivery id are not placed in the JSON body. The delivery id is carried in the X-Webhook-ID HTTP header; the owning project is determined from the subscription rather than per-event metadata.

End-to-End Wire Example

The following is a complete exec.completed delivery exactly as a subscriber endpoint will receive it.

http
POST /xerotier HTTP/1.1 Host: ops-ingest.example.com Content-Type: application/json User-Agent: Xerotier-Webhooks/1.0 X-Webhook-ID: 0f8c4a1e-1c2d-4a9b-9c1f-2e3d4a5b6c7d X-Webhook-Timestamp: 1709000100 X-Webhook-Signature: sha256=9c1a...e3f0 { "id": "evt_a1b2c3d4e5f6a7b8c9d0e1f2", "object": "event", "type": "exec.completed", "created_at": 1709000100, "data": { "invocation_id": "inv_01HXXXX", "status": "success", "duration_ms": 45200, "exit_code": 0, "completed_at": "2026-04-13T14:58:48.612Z" } }

Execution Events

Each execution lifecycle event has its own payload schema. The field set is intentionally minimal; richer state is fetched on demand via the invocation read endpoint.

exec.dispatched

json
{ "invocation_id": "inv_01HXXXX", "tool_name": "kubectl_drain", "workspace_id": "ws_01HXXXX", "agent_id": "agt_01HXXXX", "risk": "destructive", "dispatched_at": "2026-04-13T14:58:03.412Z" }
FieldTypeDescription
invocation_idstringUnique invocation identifier.
tool_namestringTool being invoked.
workspace_idstringWorkspace the tool executes in.
agent_idstringAgent assigned to the invocation.
riskstringRisk classification declared by the tool manifest.
dispatched_atstringISO 8601 timestamp when dispatch occurred.

exec.completed

json
{ "invocation_id": "inv_01HXXXX", "status": "success", "duration_ms": 45200, "exit_code": 0, "completed_at": "2026-04-13T14:58:48.612Z" }
FieldTypeDescription
invocation_idstringUnique invocation identifier.
statusstringTerminal status (for example success).
duration_msintegerWall-clock execution duration in milliseconds.
exit_codeintegerProcess exit code returned by the tool.
completed_atstringISO 8601 completion timestamp.

exec.failed

json
{ "invocation_id": "inv_01HXXXX", "error_code": "tool_exit_nonzero", "error_message": "kubectl drain returned exit code 1", "failed_at": "2026-04-13T14:58:48.612Z" }
FieldTypeDescription
invocation_idstringUnique invocation identifier.
error_codestringMachine-readable error code.
error_messagestringHuman-readable error description.
failed_atstringISO 8601 timestamp when the failure was recorded.

exec.timeout

json
{ "invocation_id": "inv_01HXXXX", "timeout_ms": 60000, "elapsed_ms": 60214, "timed_out_at": "2026-04-13T14:59:03.626Z" }
FieldTypeDescription
invocation_idstringUnique invocation identifier.
timeout_msintegerConfigured timeout threshold in milliseconds.
elapsed_msintegerActual elapsed time before timeout was declared.
timed_out_atstringISO 8601 timestamp when the timeout was recorded.

Approval Events

Approval-chain events fire as a destructive invocation moves through request, decision, escalation, or timeout. Each event carries only the fields relevant to its stage.

exec.approval_requested

json
{ "invocation_id": "inv_01HXXXX", "approval_id": "app_01HXXXX", "tool_name": "kubectl_drain", "risk": "destructive", "workspace_id": "ws_01HXXXX", "requested_at": "2026-04-13T14:58:00.000Z" }
FieldTypeDescription
invocation_idstringInvocation that triggered the approval request.
approval_idstringUnique approval-request identifier, suitable as a routing key.
tool_namestringTool whose invocation is gated on this approval.
riskstringRisk classification declared by the tool manifest (for example destructive).
workspace_idstringWorkspace the gated invocation targets.
requested_atstringISO 8601 timestamp when the approval request was enqueued.

exec.approved

json
{ "invocation_id": "inv_01HXXXX", "approval_id": "app_01HXXXX", "decided_by": "usr_01HXXXX", "approved_at": "2026-04-13T14:59:12.001Z" }
FieldTypeDescription
invocation_idstringInvocation released for dispatch.
approval_idstringApproval request that was granted.
decided_bystringUser identifier of the privileged user who granted the request.
approved_atstringISO 8601 timestamp when the decision was recorded.

exec.rejected

json
{ "invocation_id": "inv_01HXXXX", "approval_id": "app_01HXXXX", "reason": "out of change window", "rejected_at": "2026-04-13T14:59:12.001Z" }
FieldTypeDescription
invocation_idstringInvocation that was blocked from dispatch.
approval_idstringApproval request that was rejected.
reasonstringFree-form human-readable rejection reason supplied by the decider.
rejected_atstringISO 8601 timestamp when the rejection was recorded.

exec.approval_escalated

json
{ "invocation_id": "inv_01HXXXX", "approval_id": "app_01HXXXX", "stage": "stage-2", "target": "oncall-platform", "escalated_at": "2026-04-13T15:03:00.000Z" }
FieldTypeDescription
invocation_idstringInvocation whose approval chain advanced.
approval_idstringApproval request whose chain advanced.
stagestringStage identifier the chain advanced to (escalation policy defined).
targetstringGroup or rotation now responsible for the decision.
escalated_atstringISO 8601 timestamp when the escalation fired.

exec.approval_timed_out

json
{ "invocation_id": "inv_01HXXXX", "approval_id": "app_01HXXXX", "timed_out_at": "2026-04-13T15:13:00.000Z" }
FieldTypeDescription
invocation_idstringInvocation that was blocked from dispatch by the timeout.
approval_idstringApproval request that expired without a decision.
timed_out_atstringISO 8601 timestamp when the timeout was recorded.

The exec.approval_requested event is the trigger for approval-delegation patterns (forwarding to Slack, PagerDuty, or an internal approval system). Any downstream system that re-emits the payload must verify the original X-Webhook-Signature before trusting its contents; see Signature Verification.

Agent Events

exec.agent_enrolled

json
{ "agent_id": "agt_01HXXXX", "registration_name": "fleet-ops-sfo1", "project_id": "proj_01HXXXX", "manifest_hash": "sha256:b5a0...c91d", "enrolled_at": "2026-04-13T14:58:03.412Z" }
FieldTypeDescription
agent_idstringUnique agent identifier.
registration_namestringHuman-readable name declared in the agent manifest.
project_idstringUUID of the project that owns the agent.
manifest_hashstringSHA-256 hash of the accepted capability manifest.
enrolled_atstringISO 8601 timestamp when enrollment completed.

exec.agent_offline

json
{ "invocation_id": "inv_01HXXXX", "agent_id": "agt_01HXXXX", "reason": "lease_expired", "detected_at": "2026-04-13T15:00:12.000Z" }
FieldTypeDescription
invocation_idstringInvocation that surfaced the offline transition.
agent_idstringAgent that went offline.
reasonstringDetection reason (for example lease_expired).
detected_atstringISO 8601 timestamp when the offline state was detected.

Workspace Events

exec.workspace_created and exec.workspace_budget_exhausted are declared in the router event-type enum but are not currently emitted. The payload schema is reserved and will be documented here when the router begins producing these events. Subscribers may register for them today; no deliveries will fire until the emitter ships.

Discovery Events

exec.discovery_pending is declared in the router event-type enum but is not currently emitted. The payload schema is reserved and will be documented here when the router begins producing this event.

Subscribing to Events

Webhook endpoints are created with the standard POST /v1/webhooks request shape. The events array selects which event types are delivered; there is no per-endpoint workspace or risk filter today, so subscribers receive every delivery for the event types they list.

json
{ "url": "https://ops-ingest.example.com/xerotier", "events": ["exec.approval_requested", "exec.approval_escalated"], "description": "On-call approval bridge", "metadata": { "owner": "platform-oncall" } }

For finer-grained routing, inspect data.workspace_id and data.risk on receipt and drop or re-route downstream. See the Webhooks API reference for the complete create, update, list, and delete surface.

Signature Verification

Every delivery includes three HTTP headers that let subscribers authenticate the payload:

HeaderDescription
X-Webhook-IDUUID of the delivery record in webhook_deliveries. Use for delivery tracing.
X-Webhook-TimestampUnix epoch (seconds) when the router signed the request.
X-Webhook-SignatureHMAC-SHA256 signature in the format sha256=<hex-digest>.

The signature is computed as HMAC_SHA256(secret, timestamp + "." + raw_body), hex-encoded and prefixed with sha256=. The secret is returned exactly once at endpoint create time. See the Webhooks API reference for verification snippets in Python, Node, and shell.

Delivery & Retry

  • Deliveries retry on non-2xx responses or network errors using a fixed exponential backoff schedule: 1 min, 5 min, 15 min, 1 hr, 4 hr. A delivery is attempted up to 6 times total (1 initial attempt plus up to 5 retries).
  • Per-attempt delivery timeout is 30 seconds; endpoints that exceed this are treated as a failed attempt and re-scheduled per the backoff above.
  • Ordering is not guaranteed. Subscribers should dedupe on the envelope id and sort by created_at.
  • At-least-once delivery. The envelope id is suitable as a dedup key.
  • Delivery records (request payload, response status, response body up to 1 KiB) surface in GET /v1/webhooks/:id/deliveries.

For the full retry table, signing math, and limits, see the Webhooks API reference.