HELIOS DOCS
Webhooks

Webhooks

Helios sends a signed JSON request to your URL when a run finishes, a chat turn completes, a library change is staged, and more. Learn the request format, how to check the signature, and how payloads change over time.

A webhook target is a URL that Helios calls when an event you chose happens. You add targets in Settings > Webhooks or with the helios webhook commands. Each target picks its events, its scope (a project, the whole organization, or only you) and a payload version.

The request

Every delivery is an HTTPS POST with a JSON body of at most 64 KB. The body is the same for every event type. Only type and data change.

{
  "id": "wev_m1x8q2c7k4n9p0r3s5t6v8w1",
  "type": "workflow.run.failed",
  "version": "2026-10-01",
  "occurredAt": "2026-10-12T09:41:27.512Z",
  "organizationId": "org_4f7a1c2e9b",
  "projectId": "prj_k2m8d1x5q7",
  "data": {
    "runId": "awr_h3j9t2v6b1",
    "workflowId": "awf_p8c4n7w2z5",
    "workflowName": "Nightly triage",
    "status": "failed",
    "error": { "code": "timeout" }
  }
}
FieldMeaning
idThe event ID. It is the same on every retry and resend, and equals the webhook-id header.
typeThe event type, such as workflow.run.failed.
versionThe payload version the target is pinned to.
occurredAtWhen the event happened, in ISO 8601 UTC.
organizationIdYour organization ID.
projectIdThe project the event belongs to. It is absent for organization events such as project.created.
dataThe event's fields. See the event list for each type.

Payloads are thin. They say what happened and point to the resource by ID, name and status. They never carry content such as run output, message text, prompts, file contents, variable values or secrets. Fetch the resource from the Helios API when you need more. Organization events carry project IDs only, never project names. The path lists in library events and the member lists in group.members_changed are cut short when they would make data larger than 60 KB, and then carry truncated: true and the full count. Any other event whose data is still larger than 60 KB is not sent.

Reply with any 2xx status within 10 seconds to accept a delivery. Any other status, a redirect or a timeout counts as a failure, and Helios retries it.

Check the signature

Helios signs every request with Standard Webhooks. Each request has three headers.

HeaderValue
webhook-idThe event ID.
webhook-timestampThe send time in Unix seconds. It is new on every attempt.
webhook-signaturev1, followed by a base64 HMAC-SHA256 of {id}.{timestamp}.{body}.

The signing secret starts with whsec_. You see it when you create the target and when you rotate it. Check the signature on the raw request body, before you parse it. The standard-webhooks library does this for you.

npm install standard-webhooks
import { Webhook } from "standard-webhooks";

const webhook = new Webhook(process.env.HELIOS_WEBHOOK_SECRET ?? "");

export async function handleHeliosWebhook(request: Request): Promise<Response> {
  const body = await request.text();
  let event: { id: string; type: string; occurredAt: string; data: unknown };
  try {
    event = webhook.verify(body, Object.fromEntries(request.headers)) as typeof event;
  } catch {
    return new Response("Invalid signature", { status: 401 });
  }

  // Reply within 10 seconds. Do slow work in your own job queue.
  await enqueueHeliosEvent(event);
  return new Response(null, { status: 204 });
}

The library rejects a request whose timestamp is more than 5 minutes away from your clock, which blocks replayed requests. Keep your server clock in sync.

When you rotate the secret, the old secret stays valid for 24 hours. During that time webhook-signature holds two signatures separated by a space, one for each secret. The library accepts the request if either one matches, so you can update your secret at any time in that window. A second rotation inside that window replaces the overlap, so the oldest secret stops being used at once.

Delivery and ordering

Delivery is at least once and unordered.

  • Duplicates. A retry or a resend can deliver the same event again. Store the webhook-id of the events you processed and skip an ID you have already seen.
  • Order. A retried workflow.run.started can arrive after workflow.run.succeeded. Use occurredAt to put events in order, not the time the request arrived.
  • Filtering. A target receives only the event types it subscribes to. There is no option to receive every event, and a new event type reaches no existing target until an admin adds it.

Versions

Each target is pinned to one dated payload version, such as 2026-10-01. New targets use the latest version. An admin can change a target's version at any time. Helios builds each body when it sends it, so the next delivery uses the new version, including a resend of an older event.

Within a version, payloads change only in ways that keep existing receivers working.

  • A new event type.
  • A new optional field.
  • A new value of an existing enum field, such as a new error.code.

To stay compatible, ignore fields you do not know and accept enum values you do not know.

Anything else ships only in a new version. This includes removing or renaming a field, changing a field's type or meaning, removing an event type, or changing when an event fires.

A version is supported for at least 12 months after the next version ships. From 90 days before support ends, targets on that version show a banner with the end date and the version to move to. When support ends, targets still on that version are turned off with the reason version_retired.

Last updated on