202 after authentication and admission checks.
This endpoint uses webhook-secret authentication only — not session or CLI token auth. Provide exactly one of
X-Signature (preferred) or X-Webhook-Secret (deprecated).Trigger Agent via Webhook
Authentication
The endpoint accepts two header-based schemes. Provide one of them; if both are absent the request is rejected with401.
X-Signature (preferred). A hex-encoded HMAC-SHA256 of the raw request body, keyed by the agent’s per-agent webhook secret (no sha256= prefix):
X-Timestamp header (unix
seconds). When present, the signature must cover "{timestamp}.{body}" and the
timestamp must be within 5 minutes of the server clock — a captured signed
request replayed after that window is rejected with 400, and the timestamp
itself can’t be swapped because it is part of the signed material:
X-Timestamp keep the body-only scheme above (backward
compatible). When a signature is present it is also used as the dedup identity
(instead of any client-supplied Idempotency-Key), so a replay can’t force a
second run by supplying a fresh key.
Enforcing timestamps per agent. The timestamped scheme is optional by
default so un-migrated senders don’t break — but a body-only signature stays
replayable indefinitely (bounded only by the dedup window). Once an agent’s
sender emits X-Timestamp, flip the agent to require it:
PATCH /api/v1/agents/{id} with {"webhook_require_timestamp": true};
the current value is echoed on GET /api/v1/agents/{id} and in
crewship agent get.) With it on, a body-only HMAC and the deprecated
plaintext X-Webhook-Secret are both rejected with 400 pointing at the
{timestamp}.{body} scheme — closing the replay window entirely for that
agent.
X-Webhook-Secret (deprecated). The per-agent secret sent in plaintext:
Deprecation and Sunset: Thu, 31 Dec 2026 23:59:59 GMT response headers.
Both schemes validate using constant-time comparison. This endpoint does not use session or CLI token authentication.
Path Parameters
Request Headers
Request Body
The payload is passed to the agent as context. The structure is flexible — include whatever data is relevant to the agent’s task.Response
The webhook returns immediately. Agent execution happens asynchronously.202 Accepted on success — the body is {"status": "accepted"}. The status reflects the fire-and-forget contract: the agent run is enqueued asynchronously, and 202 (not 200) signals “we received it, work has not finished”.
401 Unauthorized if neither X-Signature nor X-Webhook-Secret is provided, or the supplied signature/secret is invalid.
400 Bad Request if X-Timestamp is present but unparseable or outside the 5-minute tolerance (a stale or replayed signed request).
What Happens Behind the Scenes
Webhook execution pipeline
Webhook execution pipeline
- Auth validation — the agent’s webhook secret is read crew-scoped from the database (the plaintext secret never crosses an IPC hop); an
X-Signatureis verified as an HMAC of the body (or of"{timestamp}.{body}"whenX-Timestampis present, which must also be within the freshness window), otherwise a plaintextX-Webhook-Secretis compared (constant-time) - Agent config resolution — the agent’s full configuration (CLI adapter, LLM model, credentials, etc.) is resolved
- Chat session creation — a webhook-scoped chat session is created or reused (
webhook-{agentId}) - Container startup — the crew’s container runtime is ensured running
- Run record creation — a run record with trigger type
WEBHOOKis created - Ingress trust fence — the payload (
event,source,data) is external, attacker-controlled input, so it is wrapped in a nonce-delimited<untrusted …>block and injection-scanned before it becomes the agent’s user message. The agent’s system prompt instructs it to treat fenced blocks as pure data, never as instructions. See Ingress trust fence. - Agent execution — the agent is started asynchronously with a 10-minute timeout
- Log streaming — agent output is streamed to the log collector and broadcast via WebSocket on
workspace:{workspaceId} - Run completion — the run record is updated with status (
COMPLETEDorFAILED), exit code, duration, and error message
Prerequisites
The webhook endpoint is only registered when all of the following are configured:- Orchestrator is available
- Container provider (Keeper) is available
- Log writer is configured
- Internal token is set
If any prerequisite is missing the route is never registered, so the endpoint returns
404.Use Cases
Monitoring Webhook Runs
After triggering, use these endpoints to monitor execution:Getting the Webhook Secret
The webhook signing secret follows show-once semantics — the same convention as CLI tokens and notification-channel secrets. No API, CLI, or UI surface ever returns a stored secret back. To obtain one, rotate:webhook_secret exactly once — store it in the external system (GitHub/Grafana/… webhook config) immediately. Deliveries signed with the previous secret are rejected as soon as the rotation lands. Rotation requires agent-edit rights (OWNER/ADMIN, or MANAGER for agents they created).
GET /api/v1/agents/{agentId} exposes only a webhook_secret_set boolean so clients can tell whether a secret is configured.
The former internal endpoint
GET /api/v1/internal/agents/{agentId}/webhook-secret was removed — the trigger endpoint validates signatures against the database directly, and the plaintext secret never crosses an IPC hop.Pipeline webhooks
Pipeline-webhook administration is workspace-authenticated. Dispatch is public: the high-entropy token in the URL plus its HMAC signature are the credentials.
Create accepts either
target_pipeline_slug or target_pipeline_id:
name defaults to the target slug. If signing_secret is omitted, the server
generates one. The 201 response includes signing_secret exactly once;
list responses include signing_secret_set, not the secret. The response also
contains token, target identifiers/version, inputs_template, enabled,
rate-limit and last-fire telemetry. A zero or negative configured rate uses the
server floor of 600 fires/minute. Creation requires encrypted credential storage;
misconfiguration is 500. Invalid target/body is 400, and insufficient role
is 403.
Pipeline dispatch
X-Crewship-Timestamp: <unix-seconds>, sign
<timestamp>.<raw-body>; the timestamp must be fresh. The body may be any JSON
(or raw bytes): the routine receives request-derived event, raw, and
headers inputs. inputs_template may add keys but cannot override those
three fields. Idempotency-Key (or X-Crewship-Event-ID) can select the
deduplication identity; identical signed deliveries also deduplicate.
Success returns:
202 with status: "DEDUPED", the original run_id, and
deduped: true. The run may later be COMPLETED, FAILED, CANCELLED, or
WAITING; poll /api/v1/workspaces/{workspaceId}/pipeline-runs/{runId}.
Dispatch does not accept session/JWT authentication, and it never falls back to
an unsigned request.