Skip to main content

Quickstart

Create a destination, subscribe it to events, verify the endpoint is wired correctly, and receive your first delivery. Every request below is runnable against /events/*.

Prerequisites​

  • A Helix API key. See Authentication.
  • An HTTPS endpoint you control that can receive a POST request.

Base URL: https://api.feeds.onhelix.ai

Step 1: Create a destination​

curl -X POST https://api.feeds.onhelix.ai/events/destinations \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Newsroom CMS",
"transport": "webhook",
"config": { "url": "https://cms.acme.com/hx" }
}'

The response includes your signing secret in full — store it now. It is not a show-once secret: the secrets endpoint returns every still-valid secret in full at any time.

The response also includes a validation block from a pre-flight reachability probe. An unreachable URL still saves; only a non-HTTPS or unsafe URL is rejected. See API Reference.

The destination is created with 201 Created (shortened here):

{
"success": true,
"data": {
"id": "8f14e45f-ceea-467e-adde-3fb5c25adfcd",
"externalId": null,
"name": "Newsroom CMS",
"transport": "webhook",
"config": { "url": "https://cms.acme.com/hx" },
"status": "enabled",
"debounceWindowSeconds": 120,
"secrets": [
{
"id": "3c1e9b7a-5d2f-4a8e-b6c4-0f9e8d7c6b5a",
"secret": "whsec_…",
"createdAt": "2026-10-30T12:00:00.000Z",
"expiresAt": null
}
],
"validation": {
"reachable": true,
"statusCode": 200,
"responseTimeMs": 142,
"error": null
}
}
}

Save the returned id — you'll need it for the next step. You can also set your own externalId in the request body to reference this destination by an id from your own system instead of the server-assigned one — see {ref} resolution.

Step 2: Subscribe to events​

Declare which events this destination should receive. This example scopes to every news.* event across the whole organization:

curl -X POST https://api.feeds.onhelix.ai/events/destinations/{ref}/subscriptions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"eventTypes": ["news.*"],
"scopeKind": "all"
}'

{ref} is the destination's id (or its externalId). The subscription is created with 201 Created. To scope to specific feeds instead, use scopeKind: "feeds" with a feeds array of { "feedType", "feedId" } pins, where feedType is news or events:

{
"eventTypes": ["event.*"],
"scopeKind": "feeds",
"feeds": [
{ "feedType": "events", "feedId": "550e8400-e29b-41d4-a716-446655440000" }
]
}

See Concepts.

See Event Types for the full catalog of eventTypes values, or browse the per-type pages starting with news.item.added.

Step 3: Verify signature handling​

Implement signature verification now, then prove it with a test delivery (below) before the first real event fires:

Your endpoint's contract

Respond within 30 seconds. Any 2xx status is success; everything else is a failure. The response body is never read — acknowledge first, then do the real work asynchronously, as the handler below does. See Reliability: The contract for the full rules.

const express = require('express');
const { Webhook } = require('standardwebhooks');

const secret = 'whsec_...'; // the whsec_… value from Step 1
const wh = new Webhook(secret);

const app = express();

app.post(
'/hx',
express.raw({ type: 'application/cloudevents+json' }),
(req, res) => {
try {
wh.verify(req.body, req.headers);
} catch (err) {
return res.status(401).send('Invalid signature');
}
res.status(200).send('OK');
// process asynchronously below
}
);

See Signature Verification for the full explanation and other language libraries.

Now send yourself a signed test delivery. It goes through exactly the path a real event takes — same envelope, same signed headers, same custom headers — and tells you what your endpoint answered:

curl -X POST https://api.feeds.onhelix.ai/events/destinations/{ref}/test \
-H "Authorization: Bearer YOUR_API_KEY"
{
"success": true,
"data": {
"delivered": true,
"statusCode": 200,
"responseTimeMs": 142,
"error": null,
"webhookId": "…",
"event": { "type": "delivery.test", "id": "…", "time": "…" }
}
}

delivered: false with a statusCode of 401 means your endpoint rejected the signature; null with an error means it never answered. The event type is delivery.test — accept it (verify, return 2xx) and otherwise ignore it. See Reliability: Test deliveries.

Step 4: Receive a delivery​

This step needs real feed activity to trigger it. If you don't already have a feed producing content, set one up first: see News Feeds Quickstart or Event Feeds Quickstart, then add or update an item in a feed your subscription covers.

Once a matching event occurs (a new article lands in a subscribed feed, for example), your endpoint receives a POST with a CloudEvents envelope. Give it a minute or two: delivery is debounced per subject (120 seconds by default), so a freshly created destination seeing nothing for the first couple of minutes is expected, not broken.

{
"specversion": "1.0",
"type": "news.item.added",
"source": "/feeds/news/{feedId}",
"subject": "123e4567-e89b-12d3-a456-426614174000",
"id": "456e7890-a12b-34c5-d678-901234567890",
"time": "2024-01-15T10:30:00.000Z",
"datacontenttype": "application/json",
"data": {
"feedId": "123e4567-e89b-12d3-a456-426614174000",
"id": "123e4567-e89b-12d3-a456-426614174000",
"newsPageId": "123e4567-e89b-12d3-a456-426614174000"
}
}

Use the envelope id as your idempotency key — it is stable across retry attempts of the same delivery. A replay reuses that same id, though, so a naive "skip if I've already seen this id" store will also silently skip a replay you actually wanted reprocessed — see Reliability: Idempotency for how to key your dedupe so a replay still gets through. The data payload is intentionally minimal; fetch full content from the corresponding feed API. See Concepts for the envelope and payload philosophy.

Next steps​

  • Concepts — envelope, type taxonomy, wildcards, feed scoping, debounce.
  • Reliability — retries, auto-disable, replay, backfill.
  • API Reference — the complete /events/* surface.