Skip to main content

Events Webhooks

Receive updates when new events are published to your feeds.

Event Types

event.item_added

Triggered when a new event is discovered and successfully added to your event feed.

When Webhooks Send

Event webhooks fire immediately when:

  • A new event is discovered from any of your configured sources (sites, sitemaps, index pages, Instagram)
  • Event data is extracted including dates, times, locations, and other structured information
  • The event is successfully added to your event feed

How Webhooks Send

All event webhooks follow the standard Helix webhook protocol:

  • HTTP Method: POST request to your configured endpoint
  • Content Type: application/json
  • Headers: Includes signature, timestamp, webhook ID, and event ID for verification
  • Retry Policy: Failed deliveries are retried with exponential backoff (up to 5 attempts)
  • Security: HMAC-SHA256 signature for request verification

See the Webhook Overview for complete details on security verification, retry policy, and HTTP headers.

Payload Structure

The webhook delivers the complete event data in the same format as the Get Feed Items API:

{
"event": "event.item_added",
"timestamp": "2025-11-17T12:34:56.789Z",
"data": {
"id": "770e8400-e29b-41d4-a716-446655440005",
"eventId": "880e8400-e29b-41d4-a716-446655440006",
"addedToFeedAt": "2025-11-17T12:34:56.789Z",
"publishedAt": null,
"title": "Community Art Fair",
"description": "<p>Annual art fair featuring local artists and craftspeople...</p>",
"shortDescription": "Annual community art fair with local artists",
"occurrence": {
"nextOccurrence": {
"id": "aa0e8400-e29b-41d4-a716-446655440008",
"instanceId": "990e8400-e29b-41d4-a716-446655440007",
"startDateTime": "2025-12-15T10:00:00.000Z",
"endDateTime": "2025-12-15T18:00:00.000Z",
"status": "scheduled"
},
"occurrencesInRange": [
{
"id": "aa0e8400-e29b-41d4-a716-446655440008",
"instanceId": "990e8400-e29b-41d4-a716-446655440007",
"startDateTime": "2025-12-15T10:00:00.000Z",
"endDateTime": "2025-12-15T18:00:00.000Z",
"status": "scheduled"
},
{
"id": "aa0e8400-e29b-41d4-a716-446655440009",
"instanceId": "990e8400-e29b-41d4-a716-446655440007",
"startDateTime": "2025-12-16T10:00:00.000Z",
"endDateTime": "2025-12-16T18:00:00.000Z",
"status": "scheduled"
}
],
"totalOccurrences": 2,
"upcomingOccurrences": 2
},
"time": {
"temporalPattern": "singular",
"timezone": "America/Los_Angeles",
"displayText": "Dec 15-16, 2025 at 10:00 AM - 6:00 PM PST"
},
"location": {
"venue": {
"name": "Downtown Community Park",
"address": "123 Main St, San Francisco, CA 94102"
},
"coordinates": {
"lat": 37.7749,
"lng": -122.4194
},
"displayAddress": "Downtown Community Park, 123 Main St, San Francisco, CA 94102"
},
"capacity": {
"total": 500,
"available": 350,
"unlimited": false
},
"organizer": {
"name": "SF Arts Council",
"url": "https://sfartscouncil.org",
"contact": "[email protected]"
},
"primaryImage": "https://example.com/images/art-fair-2025.jpg",
"supportingImages": [
"https://example.com/images/art-fair-booth1.jpg",
"https://example.com/images/art-fair-booth2.jpg"
],
"primaryUrl": "https://example.com/events/art-fair-2025",
"urls": [
"https://example.com/events/art-fair-2025",
"https://sfartscouncil.org/community-fair"
],
"geoLocations": [
{
"id": "cc0e8400-e29b-41d4-a716-44665544000a",
"description": "Downtown Community Park",
"relationType": "LOCATION",
"score": 95
}
]
}
}

Payload Fields

Event Metadata

FieldTypeDescription
idstring (UUID)Unique feed item identifier
eventIdstring (UUID)Event identifier (same event can appear in multiple feeds)
addedToFeedAtstring (ISO 8601)When event was added to this feed
publishedAtstring (ISO 8601)|nullWhen event was originally published (currently always null)

Core Event Data

FieldTypeDescription
titlestringEvent title (may contain HTML)
descriptionstringFull event description (may contain HTML)
shortDescriptionstring|nullBrief summary of the event

Occurrence Information

The occurrence object aggregates all instances and occurrences:

FieldTypeDescription
nextOccurrenceobject|nullEarliest occurrence (see structure below)
occurrencesInRangearrayAll occurrences (array of objects with same structure as nextOccurrence)
totalOccurrencesnumberTotal number of occurrences
upcomingOccurrencesnumberNumber of future occurrences (from current time)

Next Occurrence Fields:

FieldTypeDescription
idstring (UUID)Occurrence identifier
instanceIdstring (UUID)Parent instance identifier
startDateTimestring (ISO 8601)When the occurrence starts
endDateTimestring (ISO 8601)|nullWhen the occurrence ends
statusstringInstance status: scheduled, cancelled, postponed, rescheduled, completed

Structured Event Details

Time Information (time object|null):

FieldTypeDescription
temporalPatternstringsingular (one-time), recurring (repeats), or ongoing (continuous)
timezonestringIANA timezone (e.g., America/Los_Angeles)
displayTextstringHuman-readable time (e.g., "Oct 20, 2025 at 7:00 PM PST")

Location Information (location object|null):

FieldTypeDescription
venue.namestringVenue name
venue.addressstringStreet address
coordinates.latnumberLatitude
coordinates.lngnumberLongitude
online.platformstringPlatform name (e.g., "Zoom")
online.urlstringEvent URL
displayAddressstringHuman-readable address for display

Capacity Information (capacity object|null):

FieldTypeDescription
totalnumberTotal capacity/seats
availablenumberCurrently available seats
unlimitedbooleanWhether capacity is unlimited

Organizer Information (organizer object|null):

FieldTypeDescription
namestringOrganizer name
urlstringOrganizer website
contactstringContact information (email/phone)
FieldTypeDescription
primaryImagestring|nullMain event image URL
supportingImagesstring[]Additional image URLs
primaryUrlstring|nullMain event URL/website
urlsstring[]All related URLs

Geographic Relevance

The geoLocations array contains all geographic locations:

FieldTypeDescription
idstring (UUID)Geographic location identifier
descriptionstringLocation description (e.g., venue name, city)
relationTypestringLOCATION (specific venue) or RELEVANCE_REGION (broader area)
scorenumberRelevance score (0-100)

Example Handler

app.post('/webhooks/events', async (req, res) => {
const { event, data } = req.body;

if (event === 'event.item_added') {
console.log(`New event added: ${data.title}`);

// Access next occurrence
if (data.occurrence.nextOccurrence) {
console.log(
`Next occurrence: ${data.occurrence.nextOccurrence.startDateTime}`
);
console.log(`Status: ${data.occurrence.nextOccurrence.status}`);
}

// Access location
if (data.location?.venue) {
console.log(`Venue: ${data.location.venue.name}`);
console.log(`Address: ${data.location.displayAddress}`);
}

// Access time information
if (data.time) {
console.log(`Pattern: ${data.time.temporalPattern}`);
console.log(`Timezone: ${data.time.timezone}`);
}

// Access organizer
if (data.organizer) {
console.log(`Organized by: ${data.organizer.name}`);
}

// Process the event
await processEvent(data);
}

res.status(200).send('OK');
});

Common Use Cases

Display Upcoming Events

function displayEvent(event) {
const { title, occurrence, location, time } = event;

console.log(`\n${title}`);

// Show next occurrence
if (occurrence.nextOccurrence) {
const startDate = new Date(occurrence.nextOccurrence.startDateTime);
console.log(`When: ${startDate.toLocaleString()}`);
console.log(`Status: ${occurrence.nextOccurrence.status}`);
}

// Show location
if (location) {
console.log(`Where: ${location.displayAddress}`);
}

// Show multiple occurrences
if (occurrence.totalOccurrences > 1) {
console.log(
`This event has ${occurrence.totalOccurrences} occurrences in the date range`
);
}
}

Filter by Event Type

app.post('/webhooks/events', async (req, res) => {
const { data } = req.body;

// Check if it's a recurring event
if (data.time?.temporalPattern === 'recurring') {
console.log('Recurring event detected');
await handleRecurringEvent(data);
}

// Check if it's an online event
if (data.location?.online) {
console.log(`Online event on ${data.location.online.platform}`);
await handleOnlineEvent(data);
}

// Check if tickets are limited
if (data.capacity && !data.capacity.unlimited) {
const availablePercent =
(data.capacity.available / data.capacity.total) * 100;
if (availablePercent < 20) {
console.log('Low ticket availability - send alert!');
await sendLowAvailabilityAlert(data);
}
}

res.status(200).send('OK');
});

Handle Multi-Occurrence Events

function handleEventOccurrences(event) {
const { occurrence } = event;

// Get all occurrence dates
const dates = occurrence.occurrencesInRange.map((occ) =>
new Date(occ.startDateTime).toLocaleDateString()
);

console.log(`Event occurs on: ${dates.join(', ')}`);

// Check for cancelled occurrences
const cancelled = occurrence.occurrencesInRange.filter(
(occ) => occ.status === 'cancelled'
);

if (cancelled.length > 0) {
console.log(`Warning: ${cancelled.length} occurrence(s) are cancelled`);
}

// Count upcoming vs total
console.log(
`${occurrence.upcomingOccurrences} upcoming of ${occurrence.totalOccurrences} total`
);
}

Timezone Handling

All timestamps are in UTC (ISO 8601 format). Convert to local timezone:

function formatEventTime(event) {
const { occurrence, time } = event;

if (!occurrence.nextOccurrence || !time) {
return 'Time TBD';
}

const startTime = new Date(occurrence.nextOccurrence.startDateTime);

// Use the event's timezone
const localTime = startTime.toLocaleString('en-US', {
timeZone: time.timezone,
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
});

return `${localTime} (${time.timezone})`;
}

Geographic Filtering

app.post('/webhooks/events', async (req, res) => {
const { data } = req.body;

// Check if event is in San Francisco
const inSF = data.geoLocations.some((geo) =>
geo.description.toLowerCase().includes('san francisco')
);

if (inSF) {
console.log('Event in San Francisco detected');

// Check if it's a specific venue or broader region
const venues = data.geoLocations.filter(
(geo) => geo.relationType === 'LOCATION'
);
const regions = data.geoLocations.filter(
(geo) => geo.relationType === 'RELEVANCE_REGION'
);

console.log(`Venues: ${venues.map((v) => v.description).join(', ')}`);
console.log(`Regions: ${regions.map((r) => r.description).join(', ')}`);
}

res.status(200).send('OK');
});

Security Best Practices

Always verify webhook signatures before processing:

import crypto from 'crypto';

function verifyWebhookSignature(req, webhookSecret) {
const signature = req.headers['x-helix-signature'];
const timestamp = req.headers['x-helix-timestamp'];
const payload = JSON.stringify(req.body);

const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(`${timestamp}.${payload}`)
.digest('hex');

return signature === expectedSignature;
}

app.post('/webhooks/events', async (req, res) => {
// Verify signature first
if (!verifyWebhookSignature(req, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}

// Process webhook
const { event, data } = req.body;
await processEvent(data);

res.status(200).send('OK');
});

Testing Webhooks

Use the webhook testing endpoint to send test events:

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",
"event": "event.item_added"
}'

Next Steps