Skip to main content

Events Webhooks

Receive real-time notifications when events are added, updated, or removed from your feeds.

Event Types

Event TypeWhen it firesPage
event.item_addedA new event is discovered and added to a feedevent.item_added
event.item_updatedAn existing event's data changes (time, venue, status…)event.item_updated
event.item_removedOne or more events are removed from a feed (batched)event.item_removed

See the per-event-type pages for the authoritative data schemas. The sections below cover events-domain behavior that applies across all three.

Webhooks Are Signals, Not Source of Truth

caution

Event webhooks tell you something changed. They do not carry the full event data — only enough to identify what to re-fetch. Always query the Event Feed API for current state.

Why minimal payloads:

  1. Stale-data prevention. Between the source change and your handler, the event may have been updated again. Re-fetching guarantees you act on current state.
  2. Out-of-order safety. Network conditions can deliver webhooks out of order. The API has the authoritative state, so re-fetching makes order irrelevant.
  3. Debouncing. Multiple rapid changes to the same event collapse into a single webhook. The webhook says "sync me", not "here's the diff".

item_added vs item_updated vs item_removed

Event typedata.eventIddata.eventIdsTypical batch size
event.item_addedyes1
event.item_updatedyes1
event.item_removedyes (array)1+

item_removed is the only one that batches. If you write a generic handler, branch on eventType and read the right field — do not assume data.eventId exists for removals.

Debouncing Behavior

  • Window: 2 minutes per organization per feed per event.
  • Scope: debounced independently per event — updating event A does not delay notifications about event B.
  • Implication: during high activity you receive one webhook per (event, 2-minute window), not one per change. Re-fetch from the API to see the final state.

Comprehensive Example

import type { Request, Response } from 'express';
import { verifyWebhookSignature } from './verify'; // see Webhook Overview

app.post('/webhooks/events', async (req: Request, res: Response) => {
// 1. Verify signature against the RAW body (see Webhook Overview).
if (
!verifyWebhookSignature(
req.rawBody,
req.headers,
process.env.WEBHOOK_SECRET!
)
) {
return res.status(401).send('Invalid signature');
}

// 2. Acknowledge receipt immediately. Process async.
res.status(200).send('OK');

// 3. Use envelope `id` for idempotency — stable across retries.
const { eventType, id, data } = req.body;
if (await alreadyProcessed(id)) return;

try {
switch (eventType) {
case 'event.item_added':
case 'event.item_updated': {
const event = await helixApi.getFeedItem(data.feedId, data.eventId);
if (!event) {
// Already removed by the time we re-fetched — treat as removal.
await markEventRemoved(data.eventId);
} else {
await upsertEvent(event);
}
break;
}

case 'event.item_removed': {
// data.eventIds is an array — handle batches.
await markEventsRemoved(data.feedId, data.eventIds);
break;
}

default:
console.warn(`Unknown event type: ${eventType}`);
}

await markProcessed(id);
} catch (err) {
// Already 200'd — log, don't rethrow. Failed sync is recoverable
// on the next webhook or a periodic full reconcile.
console.error('Webhook processing failed', { id, eventType, err });
}
});
Handle "fetched 404"

Between the webhook firing and your re-fetch, the event may have been removed. A 404 from the API on item_added / item_updated is normal — convert it to a removal in your store.

How Webhooks Are Sent

Standard Helix webhook protocol — see the Webhook Overview for envelope shape, signature verification, retry policy, and HTTP headers.

Testing Webhooks

curl -X POST https://api.feeds.onhelix.ai/webhooks/test \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-domain.com/webhooks/events",
"eventType": "event.item_added"
}'

Repeat with event.item_updated or event.item_removed to test each type.

Next Steps