Guest Machines

Webhooks

Receive execution events and start governed work from external systems.

Guest Machines supports two different webhook directions. Outbound webhooks notify your system about platform events. Inbound webhooks let an external system start a configured flow.

Outbound webhooks

Create a webhook for the events your integration actually consumes. Your endpoint should return a successful response quickly and move expensive processing to a queue.

Webhooks are created in the console by an organization admin or owner, which is why they are absent from the API reference: an API key has no role, so it cannot create one however it is scoped. The delivery contract below is what your integration builds against.

Design the receiver to tolerate duplicate delivery, delayed delivery, and events arriving close together.

Delivery headers

Every delivery is signed. Guest Machines sends a JSON body with these headers:

HeaderContents
X-GuestMachines-SignatureComma-separated scheme=value elements; today, sha256=…
X-GuestMachines-TimestampUnix seconds, and part of the signed message
X-GuestMachines-EventThe event name, such as run.completed
X-GuestMachines-Delivery-IdStable across retries; also present in the body

Use X-GuestMachines-Delivery-Id as the deduplication key.

Delivery semantics

Delivery is at least once. Any 2xx response marks the delivery successful; the response body is ignored. A network failure, timeout, redirect, or non-2xx response is retried up to five total attempts with exponential backoff starting at 30 seconds. The delivery ID stays the same across those attempts, while the signature timestamp is generated again for each attempt.

Events can be delayed, duplicated, or delivered out of order relative to a different event. Persist the delivery ID before starting side effects, make the handler idempotent, and respond quickly. Do not hold the request open while performing agent work or another expensive operation.

Verify the signature

The signing secret is shown once when you create the webhook, and again when you rotate it. Store it in a secret manager.

The signed message is the timestamp, a literal ., then the exact raw request body. Compute HMAC-SHA256(secret, "{timestamp}.{body}") and hex-encode it.

Parse the signature header rather than comparing it whole. It is a comma-separated list of scheme=value elements: find the one whose scheme is sha256, compare its value in constant time, and ignore any element whose scheme you do not recognize. A receiver written this way keeps working when a new algorithm is added alongside the current one.

import hashlib
import hmac

def is_signed_by_guest_machines(secret, body_bytes, timestamp, signature_header):
    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.".encode() + body_bytes,
        hashlib.sha256,
    ).hexdigest()

    for element in signature_header.split(","):
        scheme, _, value = element.strip().partition("=")
        if scheme == "sha256" and hmac.compare_digest(value, expected):
            return True

    return False

Sign against the raw bytes you received. Re-serializing the parsed JSON changes key order and whitespace, and the signature will not match. Reject deliveries whose timestamp is far outside your clock tolerance, and reject unsigned requests outright rather than falling back to trusting them.

Rotating a secret invalidates the old one immediately, so update the receiver before rotating.

Event payload

The body is a JSON object with contract_version, the event name, a timestamp, the delivery id, and a data object carrying the event's public fields. An optional request_id correlates events emitted inside an API request with Guest Machines logs. It is omitted for background-originated events.

Validate the fields you need and ignore the ones you do not recognize — new fields and event types are added over time, and a receiver that rejects unknown values will start failing on a release it did not ask for. The complete machine-readable envelope and event catalog are in the outbound webhook contract.

{
  "contract_version": 1,
  "event": "run.completed",
  "timestamp": "2026-08-10T02:43:10.969970+00:00",
  "delivery_id": 17,
  "data": {
    "run_id": 49,
    "status": "completed",
    "trigger": {
      "delivery_id": "your-own-key"
    },
    "verification_status": "verified"
  }
}

The top-level delivery_id identifies this delivery. Use it to deduplicate, not to identify your request.

The body is capped at 64 KiB. Webhooks summarize an event; retrieve the identified run for the complete public result.

Correlating an event with the request that caused it

When an inbound webhook starts the run, include your own string or integer delivery_id in that trigger body. Run events return it as data.trigger.delivery_id, so the notification identifies your request without echoing the rest of the arbitrary inbound payload. Runs started another way, or trigger bodies without that field, have no trigger.

Run lifecycle events

EventMeaning
run.startedExecution began
run.completedFinished successfully
run.failedFinished unsuccessfully
run.cancelledStopped by a person or by policy
run.pausedStopped awaiting intervention — not finished
run.resumedContinued after a pause
run.requires_approvalWaiting on a plan or tool approval

Subscribe to the terminal events and to run.paused and run.resumed. A run that pauses emits nothing further until someone acts on it, so a consumer subscribed only to run.completed and run.failed waits indefinitely on a run that has already stopped.

run.requires_approval carries approval_kind as plan or tool. For a plan approval, retrieve the active plan and use the public approve, deny, or replan controls. For a tool approval, retrieve the pending interaction and submit the attributed decision through the interaction response. A generic resume call cannot supply or bypass that decision.

Other event families

Pipelines, workforce runs, run governance, alerts, and budgets all publish events too. Webhook events is the full catalog, grouped by what each event reports on.

The console's test action sends ping, which is a delivery test rather than a subscribable platform event. Capability discovery returns both the current subscribable catalog and that test-event name.

Pausing is not an outcome

run.paused means the run stopped and is waiting. It carries:

FieldContents
pause_reasonStable machine-readable code, e.g. daily.tokens
pause_categoryBroader class, e.g. resource_limit, checkpoint_failure
resumableWhether resuming can carry the run to a terminal event
reasonHuman-readable sentence, for display only

Branch on pause_reason, not on pause_status: the reason code is the contract, while the status string is a lifecycle detail that gains members. A quota pause and a platform fault both arrive as run.paused and need opposite handling — the first clears when the limit is raised, the second needs investigation.

When resumable is true, the run can continue once its blocker is cleared, emitting run.resumed and then a terminal event. Do not treat run.paused as a final answer: publishing a conclusion there and closing the work leaves a stale result when the run later completes.

Stability

The envelope, required headers, signing message, current sha256 scheme, and delivery ID semantics are the public delivery contract. Future algorithms will be added as further elements in X-GuestMachines-Signature rather than replacing sha256, so a receiver that selects the scheme it knows keeps verifying without changes. New event types and event-data fields are additive; ignore what you do not recognize.

This commitment covers webhook delivery only. HTTP request compatibility is governed separately by the API reference.

Inbound webhooks

An inbound webhook exposes a trigger URL for a specific automation. Treat the trigger token as a secret. Rotate it if it is disclosed and update the sending system immediately.

Validate and constrain payloads before they reach expensive agent execution. A public trigger should not become an unbounded prompt relay.

Enable Require the run to create a file when every triggered run must return a downloadable file. The API stores this setting as require_artifact; a matching run must create at least one file and pass the artifact_requirement check. If the target also has an output schema, both checks must pass before the run is verified. This setting belongs to the authenticated webhook configuration. A sender payload field named require_artifact remains ordinary data under input_data.webhook.payload and cannot change it.

An inbound webhook can also provide fixed input files through its authenticated artifact_inputs configuration. These files are chosen by the webhook owner, authorized when the configuration is saved, and authorized again whenever the webhook fires. The public sender cannot add, remove, or replace them through its payload. Schedules use the same fixed-file behavior.

In a shared organization, a webhook can also carry Project, Ticket, and Cost center tags, stored as project_tag, ticket_tag, and cost_center_tag. Every triggered run is stamped with them, which is the only way webhook-driven work reaches the chargeback breakdown — nobody is present at trigger time to be asked. These belong to the webhook configuration for the same reason require_artifact does, and it matters more here: a trigger URL is public, so a sender that could name a cost center could file your organization's spend against any team it chose. Identically named fields in a payload stay ordinary data under input_data.webhook.payload.

The automation behind the URL is exactly one agent or one team, fixed when the webhook is created. Your request, the trigger URL, and the 202 are identical either way; what differs is duration. A team run fans out and routinely takes longer than a single agent's, and often longer than the webhook waits to record an outcome, so read the result from the runs endpoints or an outbound webhook rather than from the webhook's last recorded outcome. Pointing a webhook at a team is also refused at creation unless every member can run unattended — see build a team.

Operational checklist

  • Use HTTPS.
  • Verify the signature on every delivery, and reject anything unsigned.
  • Store received delivery IDs.
  • Respond before doing expensive work.
  • Retry only transient failures.
  • Monitor repeated delivery failures.
  • Never log credentials or full sensitive payloads.

On this page