Skip to main content
Crewship exposes agent webhooks and pipeline-webhook triggers. Agent webhooks target one agent; pipeline webhooks target a saved routine. Both are asynchronous and return 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).

Manage incoming endpoints in Integrations

Open Integrations → Incoming webhooks to see routine and agent endpoints in one list. The explorer filters targets by Routines, Agents, or Pages; search, collapse, and the mobile drawer work like the other Integrations tabs. Selecting a target opens its endpoints and recent receipts. The selection is linkable, for example /integrations?tab=incoming&section=routine&target=review-pr. Use Add integration → Incoming webhook (or Add endpoint in a target) to select an existing target and create its endpoint. The default routine endpoint accepts signed JSON requests; credentials are generated automatically. To connect GitHub pull requests, open Advanced settings → Webhook format and choose GitHub pull requests. This uses GitHub’s signature and PR payload adapter. Collapsing the settings does not change the selected format. Routines can create, update, or comment on Issues, so Issues do not have a second, identical target list. Agent webhooks start background runs; they do not append a message to an existing chat. Page endpoints target a compatible webhook or script panel on that Page. Outgoing notifications send Crewship events to another service. Incoming webhooks receive direct HTTP requests. Tools → Triggers manages subscriptions through connected app accounts; it is a separate integration mechanism. The overview counts routine and configured agent endpoints. Page endpoints are loaded only when opening a Page and are explicitly excluded from these totals. Receiving means enabled with credentials, not that delivery was probed. Received · 24h displays — because the API does not provide that aggregate. Refresh reloads target lists, endpoints, and the open receipt list. Receiving URLs for routines and Pages contain a secret token: copy the URL when it is created; subsequent views show a masked path. The signing secret is also shown once. Rotating a routine’s signing secret leaves the receiving URL unchanged and cannot recover a lost URL; create a replacement endpoint if necessary. An agent’s URL contains only public identifiers and remains copyable; its signing secret is still shown once. Routine endpoints support enable/disable, secret rotation, and deletion. Agent endpoints currently support secret rotation, without a separate endpoint disable/delete operation. Page endpoints can be revoked with Delete; rotation requires creating a replacement. Existing work and receipts are retained when an endpoint is deleted. Recent agent receipts show admission decisions, not execution results; routine receipts show the run record’s status when available. Pages expose last-fire information, but no receipt-history API. All mutations remain subject to backend permissions.

Trigger Agent via Webhook

Triggers an agent run from an external system. The webhook validates the request against the agent’s per-agent webhook secret, then starts the agent asynchronously.

Authentication

The endpoint accepts two header-based schemes. Provide one of them; if both are absent the request is rejected with 401. 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):
This binds the signature to the exact payload, so a leaked header from one request can’t be replayed against a different body. Replay protection (optional, recommended). Add an 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:
Requests without 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:
(API: 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:
This path still works during the deprecation window but is going away. Requests authenticated this way receive Deprecation and Sunset: Thu, 31 Dec 2026 23:59:59 GMT response headers.
X-Webhook-Secret is deprecated (sunset 2026-12-31). Migrate to X-Signature before the sunset date.
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.
The agent receives a formatted message:

Response

The webhook returns as soon as the delivery is durably recorded. Agent execution happens afterwards, asynchronously, and is tracked as a work item. 202 Accepted — the delivery and the work item it produced were committed in one transaction before the response was written:
status is the work item’s state at acceptance (queued; see States for what it becomes). A redelivery of an accepted delivery — same source id, same body — answers 202 again with the original delivery_id and work_id and duplicate: true; no second run starts. This also holds while the agent’s rate limit is exhausted: a duplicate is never refused for fullness, only new work is. 200 OK — a ping event ({"event":"ping",…}) is recorded and acknowledged without starting an agent:

What Happens Behind the Scenes

  1. Auth validation — the agent’s webhook secret is read crew-scoped from the database (the plaintext secret never crosses an IPC hop); an X-Signature is verified as an HMAC of the body (or of "{timestamp}.{body}" when X-Timestamp is present, which must also be within the freshness window), otherwise a plaintext X-Webhook-Secret is compared (constant-time)
  2. Delivery identity — the dedup identity is the verified signature (scoped by agent), else the sender’s Idempotency-Key, else a hash of the payload; the body’s SHA-256 is recorded next to it so a reused identity with different bytes is a 409, not a silent overwrite
  3. Durable acceptance — inside one transaction the delivery row and a queued work item (source: webhook, class: background) are written to the ledger; the 202 carries their ids. Rate and capacity gates run before this step and answer 429, a ledger fault answers 503 — nothing is started that was not recorded first
  4. Response, then dispatch — the receipt is flushed to the sender before anything outside the database runs, so a cold crew’s image pull no longer counts against GitHub’s ten-second delivery deadline
  5. Container startup — the crew’s container runtime is ensured running by the work dispatcher, which claims the item and records an attempt
  6. 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.
  7. Agent execution — the agent runs under a webhook-scoped session (webhook-{agentId}-{runId}), with log streaming to the log collector and WebSocket broadcast on workspace:{workspaceId}
  8. Completion and recovery — the work item moves to a terminal state (succeeded, failed, cancelled, …); a crash between acceptance and completion leaves a claimable item, not a lost event. Inspect with crewship work get <work_id> / crewship work deliveries get <delivery_id>, replay with crewship work replay

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:
or via the API:
or from the dashboard: Agent → Settings → Advanced → Webhook signing secret → Rotate. The UI confirms first (the previous secret stops validating the instant the new one is minted), then shows the value once with a copy button — it is never re-displayed on a reload. The response contains the freshly minted 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} and the GET /api/v1/agents list expose only a webhook_secret_set boolean so clients can tell whether a secret is configured; the plaintext is never returned after the reveal.
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

The signature is required. With 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 adds literal values; it does not evaluate templates or merge payload fields into top-level inputs, and it cannot override those three fields. Declared form inputs are validated before accepting a new delivery: invalid inputs return 400 without reserving a delivery ID or run ID. A malformed target definition returns 409. These signed, rate-limited rejections are recorded as failed deliveries and increment the webhook’s failure streak; repeated failures can alert managers, and a successful delivery resets the streak. Delivery health includes sender configuration/data errors, not only server execution errors. Idempotency-Key (or X-Crewship-Event-ID) can select the deduplication identity; identical signed deliveries also deduplicate. Success returns 202:
delivery_id is the receipt written before the response. A duplicate returns 202 with status: "DEDUPED", the original run_id and delivery_id, deduped: true and duplicate: 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. From the CLI, crewship routine webhooks fire <url> --secret … --body … signs and sends a delivery exactly this way (see webhooks fire).

Routine webhook receipts

A routine delivery is executed by the pipeline engine and never produces a work item. What it leaves behind is a receipt: its identity, the body’s fingerprint and the run it produced. Two read routes answer “did you receive this one, and what ran” without re-sending the delivery. Receipt identity is (workspace, webhook, source delivery id) — the Idempotency-Key / X-Crewship-Event-ID the sender supplied, or the synthetic key derived from the signed bytes. Passing ?webhook_id= and ?source_delivery_id= asks that identity question and answers with a page of zero or one; ?source_delivery_id= on its own is a 400. run_id is the real pipeline run (poll /api/v1/workspaces/{workspaceId}/pipeline-runs/{runId}); run_status is that run’s current status and is null when no run record is available. The receipt is written and the 202 sent before execution starts, so null can mean the run has not started yet just as well as a dispatch that failed before a record was written, or a record removed by run retention — the absence does not say which, and neither does the API. The raw payload is never returned; body_sha256 and body_bytes identify it. Dedup window and retention. A receipt deduplicates redeliveries of its identifier for 30 days from acceptance (dedup_expires_at). A sweeper removes expired receipts at startup and then daily, so downtime cannot extend the window; from then on the same identifier is new work and runs again — the contract promises no replay suppression past the window. Receipts recorded before the window existed were dated from the upgrade and keep the full 30 days from there.

POST /api/v1/webhooks/{token}/github-pull-request

Create an endpoint in Integrations → Incoming webhooks → Routines and choose Advanced settings → Webhook format → GitHub pull requests, or from the CLI crewship routine webhooks create --slug <routine> --ingress-profile github. The creation API accepts ingress_profile: "github"; the default is crewship. The profile cannot be changed by PATCH: create a new endpoint when changing sender protocols. Copy the URL and signing secret at creation; neither can be recovered from the list. Rotating the secret preserves the URL you saved. Authentication uses the token in the path and X-Hub-Signature-256, containing sha256= plus the HMAC-SHA256 of the exact request body. It does not use a user session. Configure GitHub with content type application/json, the shown secret and Pull requests events. The existing hostname and HTTPS ingress suffice; a separate webhook DNS name is not required. The base URL without the suffix also enforces the stored GitHub profile, so a legacy signature cannot bypass it. The JSON request can contain the full GitHub payload. Execution requires a pull_request object and action equal to opened, synchronize or reopened:
X-GitHub-Delivery is required for these actions. X-GitHub-Event: ping and unsupported actions are acknowledged without starting a routine. The target routine is fixed by the endpoint configuration, and the action is read from signed JSON rather than chosen by the unsigned event header. The routine receives the event as its webhook inputs and must implement the intended issue or review steps; choosing a routine does not add those steps automatically. Response statuses: GitHub delivery IDs are unsigned. Within this endpoint, identical signed bytes therefore also deduplicate atomically, including concurrent requests with different IDs. The receipt records profile github and source ID github:<delivery-id>. The same 30-day receipt retention applies; once expired receipts are removed, the same signed payload can start new work. No permanent replay protection is promised. This endpoint does not publish PR reviews or enforce a repository/branch allowlist. Connect a dedicated review routine only after configuring those decisions there. To exercise the endpoint before pointing GitHub at it, crewship routine webhooks fire <url>/github-pull-request --secret … --body @pull_request.json --delivery-id <id> signs the payload with X-Hub-Signature-256 and prints the receipt; crewship routine webhooks receipts list --webhook <id> --source-id github:<id> reads it back.