HelixML

Configure outbound webhooks

Send signed, durable notifications when Helix creates a spec task, changes a task status, or publishes an artifact.

Outbound webhooks notify another service when work changes in Helix. They are useful for CI orchestration, security automation, reporting, and any integration that needs to react to spec-task or artifact events.

This is different from an agent app webhook trigger: a trigger sends a request to Helix to run an agent, while an outbound webhook sends an event from Helix to your service.

Helix implements the Standard Webhooks specification. Deliveries are signed and retried, and each endpoint can subscribe to an entire organisation or one project.

Before you start

You need:

  • An organisation owner account and its API key
  • The organisation ID or slug
  • A public HTTPS receiver that can preserve the raw request body

Only organisation owners can manage endpoints. An organisation can have up to 25 active endpoints.

Set these values for the examples below:

export HELIX_URL="https://app.helix.ml"
export HELIX_API_KEY="your-api-key"
export HELIX_ORG="your-org-id-or-slug"

For a self-hosted deployment, set HELIX_URL to the public URL of your Helix control plane.

Create an endpoint

Create an endpoint with POST /api/v1/organizations/{org}/webhook-endpoints:

curl --fail-with-body \
  -X POST "$HELIX_URL/api/v1/organizations/$HELIX_ORG/webhook-endpoints" \
  -H "Authorization: Bearer $HELIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://automation.example.com/webhooks/helix",
    "description": "Production task automation",
    "project_id": "project_...",
    "events": ["spec_task.status_changed", "artifact.published"]
  }'

project_id is optional. Omit it to receive matching events from every project in the organisation. Omit events to subscribe to all supported events, or pass ["*"].

The response contains the new endpoint and its signing secret:

{
  "endpoint": {
    "id": "whep_...",
    "organization_id": "org_...",
    "project_id": "project_...",
    "url": "https://automation.example.com/webhooks/helix",
    "events": ["artifact.published", "spec_task.status_changed"],
    "status": "active",
    "secret_preview": "9xY="
  },
  "secret": "whsec_..."
}

Store secret in your receiver's secret manager. Helix returns the full value only when you create an endpoint or rotate its secret. Later API responses contain only secret_preview.

Events

EventSent whendata fields
spec_task.createdA spec task becomes available in a projectspec_task_id, project_id, organization_id, status, status_updated_at
spec_task.status_changedA spec task moves to another statusThe fields above, plus previous_status
artifact.publishedHelix creates an artifact or publishes a new versionartifact_id, project_id, organization_id, active_version_id, kind, source_spec_task_id

The request body is a small notification rather than a complete resource snapshot:

{
  "id": "whevt_...",
  "api_version": "v1",
  "type": "spec_task.status_changed",
  "organization_id": "org_...",
  "project_id": "project_...",
  "data": {
    "spec_task_id": "task_...",
    "project_id": "project_...",
    "organization_id": "org_...",
    "status": "done",
    "previous_status": "pull_request",
    "status_updated_at": "2026-09-22T12:00:00Z"
  },
  "timestamp": "2026-09-22T12:00:00Z"
}

Use the IDs in data to fetch current state from the Helix API. An event can be delayed or replayed, so do not treat the body as the latest version of the task or artifact.

Verify requests

Every delivery includes these headers:

HeaderMeaning
webhook-idDelivery ID. It stays the same across automatic retries.
webhook-timestampUnix timestamp used to sign the request.
webhook-signatureOne or more Standard Webhooks signatures.
webhook-typeEvent type, such as artifact.published.

Verify webhook-id, webhook-timestamp, and the exact raw request body with your Standard Webhooks library and the whsec_... secret. Do this before parsing or acting on the JSON. The Standard Webhooks libraries handle signature parsing and timestamp checks.

After verification:

  1. Deduplicate on webhook-id.
  2. Queue any longer work.
  3. Return a 2xx response quickly.
  4. Fetch the referenced task or artifact from Helix before acting on it.

Your receiver must tolerate duplicate deliveries. Automatic retries reuse the same webhook-id; a manual replay also reuses the delivery.

Delivery and retries

Helix records the event in the same database transaction as the state change, then delivers it asynchronously. This prevents a committed change from losing its notification if an API process restarts.

  • Any 2xx response marks the delivery successful.
  • Helix does not follow redirects.
  • 410 Gone disables the endpoint and stops new deliveries to it.
  • Other failures retry with exponential backoff and jitter. The default is eight attempts over several days.
  • A valid Retry-After response header can postpone the next attempt.

Design your receiver for at-least-once delivery: verify the signature, deduplicate, acknowledge, and process asynchronously.

Inspect and replay deliveries

List the most recent 50 deliveries for an endpoint:

curl --fail-with-body \
  "$HELIX_URL/api/v1/organizations/$HELIX_ORG/webhook-endpoints/whep_.../deliveries" \
  -H "Authorization: Bearer $HELIX_API_KEY"

Each result includes the event type, delivery status, attempt count, last HTTP status, last error category, and timestamps. Status is one of pending, processing, retrying, delivered, failed, or disabled.

Replay a delivery after fixing the receiver:

curl --fail-with-body \
  -X POST "$HELIX_URL/api/v1/organizations/$HELIX_ORG/webhook-endpoints/whep_.../deliveries/whd_.../replay" \
  -H "Authorization: Bearer $HELIX_API_KEY"

The API returns 202 Accepted. A delivery cannot be replayed while it is being processed.

Update or disable an endpoint

PUT replaces the endpoint configuration. Include the URL and the complete event selection you want to keep:

curl --fail-with-body \
  -X PUT "$HELIX_URL/api/v1/organizations/$HELIX_ORG/webhook-endpoints/whep_..." \
  -H "Authorization: Bearer $HELIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://automation.example.com/webhooks/helix",
    "description": "Production task automation",
    "events": ["*"],
    "enabled": true
  }'

Disable an endpoint with DELETE:

curl --fail-with-body \
  -X DELETE "$HELIX_URL/api/v1/organizations/$HELIX_ORG/webhook-endpoints/whep_..." \
  -H "Authorization: Bearer $HELIX_API_KEY"

This disables the endpoint rather than removing its delivery history. Set enabled to true in a later PUT request to enable it again.

Rotate a signing secret

curl --fail-with-body \
  -X POST "$HELIX_URL/api/v1/organizations/$HELIX_ORG/webhook-endpoints/whep_.../rotate-secret" \
  -H "Authorization: Bearer $HELIX_API_KEY"

Store the returned secret immediately. For the next 24 hours, Helix signs deliveries with both the new and previous secrets. Configure the receiver to accept the new secret during that overlap, then remove the old one.

Self-hosted configuration

Outbound webhooks work without extra configuration. Operators can tune the worker with the WEBHOOK_* environment variables.

Production endpoints must use HTTPS and resolve to public IP addresses. For local development only, WEBHOOK_ALLOW_PRIVATE_ENDPOINTS=true permits HTTP and private or loopback destinations. Do not enable it on an internet-facing deployment.