SteveSignalsDocs

Event reference

Event shape

The event object every webhook page item shares: each common key with its exact type, the relevance verdict, removed events and versioning.

A webhook POST is a small run webhook with counts and page tokens. The events come from its pages, GET /api/public/v1/events?pageToken=…, up to 100 in items (see Read a run's events). Each event is one JSON object. It has a set of common fields (the same on every event) and one platform object with the details of the item. This page shows the common part once. The pages for each platform show the platform object.

How to read this page

T | null means the key is always there and the value can be null. optional means the key can be missing. Types are written like TypeScript. Treat every string union as open: a new value can appear without a version change.

Common fields (every event)

Keys are sent in this order.

Common fields (every event)
version
1
The event version. Always the number 1 today. The run webhook has its own version (3, the Signals-Event-Version header). See Versioning.
eventId
string
A UUID. Unique per event. Use it to drop duplicates: it stays the same across retries, replays and pages read twice.
eventType
'ads'|'reviews'|'social_post'|'mention'|'news'|'website'|'jobs'|'competitor'
The event family. Decides which platform field is present. The run webhook's eventType is the same family.
subEventType
string
The platform, for example google_ad, g2_review, x_post. There are 18. See All event types.
action
'new'|'updated'|'removed'
What happened to the item. new is a first sighting. updated is a changed item you may have seen before.
signalType
string
The product event name, for example reviews.new, social.post, website.changed. Full list: All event types.
source
string
The tracker that found it: ads, reviews, linkedin, x, instagram, linkedin_mentions, reddit_mentions, news, website, job_postings or competitor_discovery. linkedin_engagement is never sent by webhook.
orgId
string
Your organization id.
trackerId
string
The tracker (source setup) that produced the event. An opaque id.
projectIdoptional
string
The project the tracker belongs to. The key is missing only when the tracker no longer exists and the event was stored before projects existed.
runId
string
The tracker run that found the event. A UUID.
competitor
object
The competitor you track: { name: string, domain: string | null, subjectKey: string }. name is the display name. domain is the domain you set for the tracker (hubspot.com); it is null when the tracker no longer exists, and on the webhook test event. subjectKey is an internal key for what the tracker watches: opaque, do not parse it.
occurredAt
string|null
When the item happened, ISO 8601 (2026-10-25T23:50:00.523Z). It is the vendor's publish time, or the time Signals fetched it when the vendor gives none. null on removed events, except jobs.removed.
detectedAt
string
When Signals detected the change, ISO 8601.
sequence
number
A whole number that only grows in the Signals change log. Use it to sort events in detection order.
itemKey
string
The stable id of the item. The same item keeps the same itemKey across its new and updated events. It is prefixed by source, for example reviews-g2-13856432 website-https://www.hubspot.com/pricing or job_postings-7421035.
relevance
object
The AI verdict. One of three shapes: see relevance.
adsResponse | reviewResponse | socialPostResponse | mentionResponse | newsResponse | websiteResponse | jobResponse | competitorResponse
object|null
The platform object. Exactly one of these eight keys is present, chosen by eventType. The other seven are absent. null on removed events, except jobs.removed.
metadata
object|null
The same object as the platform field, sent a second time. null on removed events, except jobs.removed.

Exactly one platform field

eventType tells you which key to read: ads → adsResponse, reviews → reviewResponse, social_post → socialPostResponse, mention → mentionResponse, news → newsResponse, website → websiteResponse, jobs → jobResponse, competitor → competitorResponse. metadata is a copy with the same values. Read either one, not both. If you want one code path for every platform, read metadata.

Event · website_change (a full event)Sample with the exact shape (no real run yet)
json
1{
2 "version": 1,
3 "eventId": "df38433d-cc67-4467-ba3a-8892f7a50702",
4 "eventType": "website",
5 "subEventType": "website_change",
6 "action": "updated",
7 "signalType": "website.changed",
8 "source": "website",
9 "orgId": "org_3KikADUjsyXrUJ4RJhWpwcp98Yf",
10 "trackerId": "440b3b16-2000-484a-aa8b-7481d482f1dd",
11 "projectId": "2738158a-08fd-4fc4-912d-43d4c328bc10",
12 "runId": "8ba8ccfa-e9e5-4764-9feb-4b0e88a2ba4a",
13 "competitor": {
14 "name": "HubSpot",
15 "domain": "hubspot.com",
16 "subjectKey": "https://www.hubspot.com/pricing"
17 },
18 "occurredAt": "2026-10-27T15:08:27.064Z",
19 "detectedAt": "2026-10-27T15:08:27.064Z",
20 "sequence": 5,
21 "itemKey": "website-https://www.hubspot.com/pricing",
22 "relevance": {
23 "status": "relevant",
24 "confidence": 1,
25 "reasoning": "HubSpot added HubSpot Credits for Breeze AI agents to its Professional plan. No price changes were detected.",
26 "summary": "HubSpot added HubSpot Credits for Breeze AI agents to its Professional plan. No price changes were detected.",
27 "shortSummary": "Breeze AI Credits Added",
28 "categories": [
29 "feature_update",
30 "pricing_strategy"
31 ]
32 },
33 "websiteResponse": {
34 "title": "HubSpot Pricing",
35 "text": "Title: HubSpot Pricing\n\nURL Source: https://www.hubspot.com/pricing\n\nMarkdown Content:\n# Choose your plan\n\n## Professional\n\nEverything in Starter, plus marketing automation, custom reporting, omni-channel campaigns and HubSpot Credits for Breeze AI agents. …",
36 "url": "https://www.hubspot.com/pricing",
37 "author": null,
38 "media": [],
39 "metrics": null,
40 "pageUrl": "https://www.hubspot.com/pricing",
41 "changes": [
42 {
43 "type": "major",
44 "title": "Breeze AI Credits Added",
45 "description": "Added HubSpot Credits for Breeze AI agents to the Professional plan feature list.",
46 "previous": "# Choose your plan\n\n## Professional\n\nEverything in Starter, plus marketing automation, custom reporting and omni-channel campaigns. …",
47 "current": "# Choose your plan\n\n## Professional\n\nEverything in Starter, plus marketing automation, custom reporting, omni-channel campaigns and HubSpot Credits for Breeze AI agents. …",
48 "categories": [
49 "feature_update",
50 "pricing_strategy"
51 ]
52 }
53 ]
54 },
55 "metadata": {
56 "title": "HubSpot Pricing",
57 "text": "Title: HubSpot Pricing\n\nURL Source: https://www.hubspot.com/pricing\n\nMarkdown Content:\n# Choose your plan\n\n## Professional\n\nEverything in Starter, plus marketing automation, custom reporting, omni-channel campaigns and HubSpot Credits for Breeze AI agents. …",
58 "url": "https://www.hubspot.com/pricing",
59 "author": null,
60 "media": [],
61 "metrics": null,
62 "pageUrl": "https://www.hubspot.com/pricing",
63 "changes": [
64 {
65 "type": "major",
66 "title": "Breeze AI Credits Added",
67 "description": "Added HubSpot Credits for Breeze AI agents to the Professional plan feature list.",
68 "previous": "# Choose your plan\n\n## Professional\n\nEverything in Starter, plus marketing automation, custom reporting and omni-channel campaigns. …",
69 "current": "# Choose your plan\n\n## Professional\n\nEverything in Starter, plus marketing automation, custom reporting, omni-channel campaigns and HubSpot Credits for Breeze AI agents. …",
70 "categories": [
71 "feature_update",
72 "pricing_strategy"
73 ]
74 }
75 ]
76 }
77}

Long text values are shortened here with “ …”. Your endpoint gets the full text.

The item part every platform has

Every platform object starts with the same six keys (after platform, when the platform has one). They describe the item itself.

Item fields · in every platform object
title
string|null
The headline or title. null when the platform has none (most social posts and reviews).
text
string|null
The body text. Can be very long: a news article is the article text as Markdown (cut at 100,000 characters); a website page is only the first 2,000 characters of its Markdown.
url
string|null
A public link to the item. null when the platform gives none (most Google ads).
author
object|null
Who wrote or runs it: { name: string, handle: string | null, url: string | null, imageUrl: string | null }. imageUrl is the author's photo, set for LinkedIn mentions when LinkedIn sends one, else null. LinkedIn photo links expire after a few weeks: download the photo if you keep it. author is null when unknown.
media
object[]
Images, videos and documents: { type: 'image' | 'video' | 'document', url: string }. An empty array when there is none. Media links can expire: download what you need.
metrics
object|null
The raw numbers the platform gave, as { [name]: number }, for example { "rating": 4 }. null when there are none. Social posts and mentions also repeat these numbers as named keys (likes, views, …).

relevance

Signals judges most items against what you track. relevance tells you the result. It has three possible shapes, told apart by status.

relevance · status: 'relevant'
status
'relevant'
The item passed the judge.
confidence
number
From 0 to 1. How sure the judge is.
reasoning
string
Why the judge kept it.
summary
string|null
A short summary of what happened. null when the judge wrote none.
shortSummary
string|null
A one-line version of summary. null when none.
categories
string[]
Labels the judge gave, for example ["Product update/launch"]. Free-form text, can be an empty array.
relevance · status: 'irrelevant'
status
'irrelevant'
The judge dropped the item.
confidence
number
From 0 to 1.
reasoning
string
Why it was dropped.

Irrelevant items are never sent as events. You will not receive this shape. Handle it as unknown if you want to be safe.

relevance · status: 'unjudged'
status
'unjudged'
The source has no judge. The only key.

unjudged is what you get for reviews, ads, job postings, Instagram posts and new competitors. They are delivered as found. News, LinkedIn, X, mentions and website changes are judged, so they carry relevant.

Removed events

The schema allows action: "removed" for every platform. Today only the job postings tracker sends it (jobs.removed, with the job's last known values). The other trackers drop the "gone" change before an event is made, and new competitor discovery reports arrivals only. Build your receiver to survive one anyway.

A removed event has the same common fields, with three differences (not on jobs.removed, which keeps its platform field, metadata and occurredAt):

  • occurredAt is null.
  • The platform field is null.
  • metadata is null.
Event · removed (ads, as the schema defines it)Sample with the exact shape (no real run yet)
json
1{
2 "version": 1,
3 "eventId": "8db42a9b-7414-40bc-9443-66ac1c6c2002",
4 "eventType": "ads",
5 "subEventType": "meta_ad",
6 "action": "removed",
7 "signalType": "ads.removed",
8 "source": "ads",
9 "orgId": "org_3KikADUjsyXrUJ4RJhWpwcp98Yf",
10 "trackerId": "9aa429da-6540-4f14-a1d8-92526ccf42de",
11 "runId": "c9cd89da-52ef-4803-8669-43a0414bc65f",
12 "competitor": {
13 "name": "HubSpot",
14 "domain": "hubspot.com",
15 "subjectKey": "google:AR10072600183532683265@-|meta:https://www.facebook.com/hubspot|linkedin:hubspot"
16 },
17 "occurredAt": null,
18 "detectedAt": "2026-10-26T08:42:28.926Z",
19 "sequence": 284,
20 "itemKey": "ads-meta-1576853724126416",
21 "relevance": {
22 "status": "unjudged"
23 },
24 "adsResponse": null,
25 "metadata": null
26}

Versioning and compatibility

  • An event's version is 1. The run webhook's version is 3, also in the Signals-Event-Version header.
  • Only a breaking change raises it: a key removed, renamed or changed to another type.
  • Adding a key is not breaking. New keys can appear at any time. Ignore keys you do not know.
  • Treat every enum (eventType, subEventType, signalType, source, format, categories) as open. A new value can show up. Do not crash on it.
  • Do not rely on key order in your own parser. It is stable today, but JSON objects are unordered.