Event SpecsPublishing

Publishing & Editorial Event Spec

This page defines all standard events for news sites, blogs, magazines, and content publishers. These events cover the full reader lifecycle — content discovery, article reading and media engagement, search, registration, newsletter subscriptions, paywall interactions, subscription conversion, and retention. Implement these events to enable accurate server-side delivery to advertising and analytics platforms.

💡

Where an event overlaps with e-commerce (search, sign_up, login, generate_lead, view_promotion, select_promotion), Signal reuses GA4’s recommended event name so existing Google Tag Manager event names work as-is. Publishing-specific events use the article_*, content_*, and domain-named conventions below.

All events are sent via datafly.track(). Properties are passed as a flat object in the second argument.

⚠️

Never pass raw personal data (email address, phone number, full name) in event properties. Hash identifiers with SHA-256 (lowercase, trimmed) before sending — Signal forwards the hashed value to Meta CAPI, TikTok, and other platforms for identity matching.


Content Discovery Events

view_item_list

Fired when a user views a section front, topic hub, homepage feed, or list of article cards.

datafly.track('view_item_list', {
  item_list_id: 'technology-front',
  item_list_name: 'Technology — Section Front',
  items: [
    { item_id: 'art-20260311-001', item_name: 'The Future of Server-Side Tracking', index: 0, content_category: 'Technology', author: 'Jane Holloway' },
    { item_id: 'art-20260311-002', item_name: 'Inside the Cookieless Web', index: 1, content_category: 'Technology', author: 'Sam Reed' }
  ]
})
PropertyTypeRequiredDescription
item_list_idstringOptionalMachine-readable identifier for the list, section, or feed
item_list_namestringOptionalHuman-readable name of the list or section being browsed
itemsarrayOptionalArray of content items displayed in the list (see Content Items Schema)

select_content

Fired when a user clicks an article card, headline, or recommendation within a list or feed.

datafly.track('select_content', {
  content_type: 'article',
  item_list_id: 'technology-front',
  item_list_name: 'Technology — Section Front',
  items: [
    { item_id: 'art-20260311-001', item_name: 'The Future of Server-Side Tracking', index: 0, content_category: 'Technology' }
  ]
})
PropertyTypeRequiredDescription
content_typestringOptionalType of content selected (e.g. article, video, gallery, podcast)
item_list_idstringOptionalIdentifier of the list the content was selected from
item_list_namestringOptionalName of the list the content was selected from
itemsarrayOptionalArray containing the selected content item (see Content Items Schema)

category_viewed

Fired when a user browses a section, topic, or category listing page (as distinct from a homepage feed).

datafly.track('category_viewed', {
  category: 'Technology',
  page: 2
})
PropertyTypeRequiredDescription
categorystringRequiredSection or category name
pagenumberOptionalPagination page number (1-indexed). Omit or set to 1 for the first page.

view_promotion

Fired when a promotional unit — a sponsored placement, house ad, or featured-story banner — becomes visible to the user.

datafly.track('view_promotion', {
  promotion_id: 'SUB-DRIVE-Q2',
  promotion_name: 'Spring Subscription Drive',
  creative_name: 'sub-drive-hero-v2.jpg',
  creative_slot: 'homepage-hero'
})
PropertyTypeRequiredDescription
promotion_idstringOptionalIdentifier for the promotion
promotion_namestringOptionalHuman-readable name of the promotion
creative_namestringOptionalName or URL of the creative asset shown
creative_slotstringOptionalSlot or placement where the promotion appeared

select_promotion

Fired when a user clicks a promotional unit or house-ad creative.

datafly.track('select_promotion', {
  promotion_id: 'SUB-DRIVE-Q2',
  promotion_name: 'Spring Subscription Drive',
  creative_name: 'sub-drive-hero-v2.jpg',
  creative_slot: 'homepage-hero'
})
PropertyTypeRequiredDescription
promotion_idstringOptionalIdentifier for the promotion
promotion_namestringOptionalHuman-readable name of the promotion
creative_namestringOptionalName or URL of the creative asset shown
creative_slotstringOptionalSlot or placement where the promotion appeared

Search Events

Fired when a user submits a search query on the site. This is the canonical GA4 search event.

datafly.track('search', {
  search_term: 'climate policy',
  results_count: 47,
  filters: {
    date_range: 'last_30_days',
    category: 'Environment'
  }
})
PropertyTypeRequiredDescription
search_termstringRequiredThe search term entered by the user
results_countnumberOptionalNumber of results returned for the query
filtersobjectOptionalKey-value map of any filters applied to the search

search_performed

Publishing-specific search event for site searches you want to track distinctly from the canonical GA4 search (e.g. archive search, paywalled-content search). Use this when your blueprints route editorial search separately from product search.

datafly.track('search_performed', {
  search_term: 'climate policy',
  results_count: 47,
  search_type: 'archive'
})
PropertyTypeRequiredDescription
search_termstringRequiredThe search term entered by the user
results_countnumberOptionalNumber of results returned for the query
search_typestringOptionalSearch surface (e.g. archive, site, topic)

Article Engagement Events

article_viewed

Fired when a user lands on an article or content detail page. Send this as soon as the page renders, regardless of scroll depth. Also accepted as content_viewed and view_content.

datafly.track('article_viewed', {
  content_id: 'art-20260311-001',
  content_type: 'article',
  content_name: 'The Future of Server-Side Tracking',
  content_category: 'Technology',
  author: 'Jane Holloway',
  content_tags: ['tracking', 'privacy', 'adtech'],
  published_at: '2026-03-11T09:00:00Z',
  word_count: 1840,
  paywall: false
})
PropertyTypeRequiredDescription
content_idstringRequiredUnique identifier for the content
content_typestringOptionalType of content (e.g. article, video, gallery, podcast)
content_namestringOptionalContent headline or title
content_categorystringOptionalPrimary section or category (e.g. Technology, Sport)
authorstringOptionalAuthor name or byline
content_tagsstring[]OptionalArray of topic tags associated with the content
published_atstringOptionalISO 8601 publication date (e.g. 2026-03-11T09:00:00Z)
word_countnumberOptionalTotal word count of the article body
paywallbooleanOptionalWhether the content is behind a paywall or content gate

article_read

Fired once when a user has consumed at least 80% of the article (scrolled to the read threshold, or played a video/podcast to near-completion). Fire a maximum of once per content item per session.

datafly.track('article_read', {
  content_id: 'art-20260311-001',
  content_type: 'article',
  content_name: 'The Future of Server-Side Tracking',
  read_time_seconds: 312,
  scroll_depth: 87
})
PropertyTypeRequiredDescription
content_idstringRequiredUnique identifier for the content
content_typestringOptionalType of content read
content_namestringOptionalContent headline or title
read_time_secondsnumberOptionalTime in seconds from article_viewed to reaching the read threshold
scroll_depthnumberOptionalMaximum scroll depth reached, expressed as a percentage (0–100)
💡

Use article_read (not article_viewed) as the signal for engaged reads when optimising campaigns. A user who scrolls to 80% is a meaningfully different signal from a page bounce.


article_shared

Fired when a user shares an article via a share button or copy-link action.

datafly.track('article_shared', {
  content_id: 'art-20260311-001',
  content_type: 'article',
  share_method: 'twitter'
})
PropertyTypeRequiredDescription
content_idstringRequiredUnique identifier for the content being shared
content_typestringOptionalType of content being shared
share_methodstringOptionalShare channel. One of: twitter, facebook, whatsapp, email, copy_link

article_bookmarked

Fired when a user bookmarks an article or saves it to their reading list or “save for later” collection.

datafly.track('article_bookmarked', {
  content_id: 'art-20260311-001',
  content_type: 'article',
  content_name: 'The Future of Server-Side Tracking',
  collection: 'read-later'
})
PropertyTypeRequiredDescription
content_idstringRequiredUnique identifier for the bookmarked content
content_typestringOptionalType of content being bookmarked
content_namestringOptionalContent headline or title
collectionstringOptionalName of the list or collection the content was saved to

content_shared

Generic share event for non-article content (videos, galleries, podcasts, interactives) shared via a share button or copy-link action.

datafly.track('content_shared', {
  content_id: 'vid-20260311-009',
  content_type: 'video',
  share_method: 'whatsapp'
})
PropertyTypeRequiredDescription
content_idstringRequiredUnique identifier for the content being shared
content_typestringOptionalType of content being shared
share_methodstringOptionalShare channel. One of: twitter, facebook, whatsapp, email, copy_link

content_liked

Fired when a user likes, reacts to, or upvotes a piece of content.

datafly.track('content_liked', {
  content_id: 'art-20260311-001',
  content_type: 'article',
  reaction: 'like'
})
PropertyTypeRequiredDescription
content_idstringRequiredUnique identifier for the content being liked
content_typestringOptionalType of content being liked
reactionstringOptionalReaction type if multiple are offered (e.g. like, love, insightful)

content_saved

Fired when a user saves non-article content to their bookmarks, reading list, or “save for later” collection.

datafly.track('content_saved', {
  content_id: 'pod-ep-142',
  content_type: 'podcast',
  content_name: 'The Privacy Briefing — Episode 142',
  collection: 'listen-later'
})
PropertyTypeRequiredDescription
content_idstringRequiredUnique identifier for the saved content
content_typestringOptionalType of content being saved
content_namestringOptionalContent headline or title
collectionstringOptionalName of the list or collection the content was saved to

content_downloaded

Fired when a user downloads a piece of content — a PDF article, whitepaper, report, or offline copy.

datafly.track('content_downloaded', {
  content_id: 'report-2026-adtech',
  content_type: 'report',
  content_name: 'State of AdTech 2026',
  file_format: 'pdf'
})
PropertyTypeRequiredDescription
content_idstringRequiredUnique identifier for the downloaded content
content_typestringOptionalType of content being downloaded (e.g. report, whitepaper, article)
content_namestringOptionalContent title
file_formatstringOptionalFile format of the download (e.g. pdf, epub, mp3)

comment_posted

Fired when a user successfully submits a comment on an article.

datafly.track('comment_posted', {
  content_id: 'art-20260311-001',
  comment_id: 'cmt-77241',
  reply_to: 'cmt-77190'
})
PropertyTypeRequiredDescription
content_idstringRequiredContent the comment was posted on
comment_idstringOptionalUnique identifier for the new comment
reply_tostringOptionalcomment_id of the parent comment, if this is a reply

author_followed

Fired when a user follows or subscribes to updates from an author.

datafly.track('author_followed', {
  author_id: 'author-jane-holloway',
  author_name: 'Jane Holloway'
})
PropertyTypeRequiredDescription
author_idstringRequiredUnique identifier for the author
author_namestringOptionalDisplay name of the author

topic_followed

Fired when a user follows a topic, tag, or section to personalise their feed.

datafly.track('topic_followed', {
  topic_id: 'topic-climate',
  topic_name: 'Climate',
  source: 'topic_hub'
})
PropertyTypeRequiredDescription
topic_idstringRequiredUnique identifier for the topic or tag
topic_namestringOptionalDisplay name of the topic
sourcestringOptionalWhere the follow was triggered (e.g. topic_hub, article, onboarding)

Video & Audio Events

video_start

Fired when an embedded video begins playback.

datafly.track('video_start', {
  content_id: 'vid-20260311-009',
  content_name: 'Explainer: How Server-Side Tagging Works',
  content_category: 'Technology',
  video_duration: 184,
  video_provider: 'jwplayer'
})
PropertyTypeRequiredDescription
content_idstringRequiredUnique identifier for the video
content_namestringOptionalVideo title
content_categorystringOptionalSection or category of the video
video_durationnumberOptionalTotal duration of the video in seconds
video_providerstringOptionalPlayer or hosting provider (e.g. jwplayer, youtube, brightcove)

video_progress

Fired when playback crosses a progress milestone (typically 25%, 50%, 75%).

datafly.track('video_progress', {
  content_id: 'vid-20260311-009',
  content_name: 'Explainer: How Server-Side Tagging Works',
  percent_complete: 50,
  video_duration: 184
})
PropertyTypeRequiredDescription
content_idstringRequiredUnique identifier for the video
content_namestringOptionalVideo title
percent_completenumberOptionalProgress milestone reached (e.g. 25, 50, 75)
video_durationnumberOptionalTotal duration of the video in seconds

video_complete

Fired when a user finishes watching a video (reaches the end).

datafly.track('video_complete', {
  content_id: 'vid-20260311-009',
  content_name: 'Explainer: How Server-Side Tagging Works',
  video_duration: 184
})
PropertyTypeRequiredDescription
content_idstringRequiredUnique identifier for the video
content_namestringOptionalVideo title
video_durationnumberOptionalTotal duration of the video in seconds

audio_start

Fired when a podcast episode or audio article begins playback.

datafly.track('audio_start', {
  content_id: 'pod-ep-142',
  content_name: 'The Privacy Briefing — Episode 142',
  content_category: 'Podcasts',
  audio_duration: 2640
})
PropertyTypeRequiredDescription
content_idstringRequiredUnique identifier for the audio content
content_namestringOptionalEpisode or audio title
content_categorystringOptionalSection, show, or category
audio_durationnumberOptionalTotal duration of the audio in seconds

Account Events

sign_up

Fired when a user completes account registration (free registered reader).

datafly.track('sign_up', {
  method: 'email'
})
PropertyTypeRequiredDescription
methodstringOptionalRegistration method (e.g. email, google, apple)

login

Fired when a user signs in to their account.

datafly.track('login', {
  method: 'email'
})
PropertyTypeRequiredDescription
methodstringOptionalLogin method (e.g. email, google, apple)

generate_lead

Fired when a registered or anonymous reader submits interest in a product, demo, or gated download — for example completing a lead-gen form on a B2B publication. Never pass raw email or phone here; the contact PII is hashed server-side via Signal’s identity blueprint.

datafly.track('generate_lead', {
  lead_type: 'gated_report',
  content_id: 'report-2026-adtech',
  value: 0,
  currency: 'GBP'
})
PropertyTypeRequiredDescription
lead_typestringOptionalThe kind of lead captured (e.g. gated_report, demo_request, event_signup)
content_idstringOptionalContent or asset that generated the lead
valuenumberOptionalEstimated monetary value of the lead
currencystringOptionalISO 4217 currency code

Newsletter Events

newsletter_subscribed

Fired when a user successfully subscribes to a newsletter or mailing list.

datafly.track('newsletter_subscribed', {
  list_id: 'daily-briefing',
  email_hash: 'b4c9a...f1e2',
  source: 'article'
})
PropertyTypeRequiredDescription
list_idstringOptionalIdentifier of the newsletter or list subscribed to
email_hashstringOptionalSHA-256 hash of the subscriber’s email address. Never send raw email addresses in event properties.
sourcestringOptionalWhere the subscription was triggered. One of: inline, popup, footer, article
⚠️

Never pass a raw email address in event properties. Always hash with SHA-256 (lowercase, trimmed) before sending. This hashed value can be used by Meta CAPI and other platforms for identity matching.


newsletter_unsubscribed

Fired when a user unsubscribes from a newsletter or mailing list.

datafly.track('newsletter_unsubscribed', {
  list_id: 'daily-briefing',
  reason: 'too_frequent'
})
PropertyTypeRequiredDescription
list_idstringOptionalIdentifier of the newsletter or list unsubscribed from
reasonstringOptionalReason provided by the user for unsubscribing

Paywall & Subscription Events

paywall_viewed

Fired when a user encounters a content gate or paywall prompt, whether inline (content cut-off) or as a modal overlay.

datafly.track('paywall_viewed', {
  content_id: 'art-20260311-044',
  subscription_type: 'digital',
  position: 'modal'
})
PropertyTypeRequiredDescription
content_idstringRequiredContent that triggered the paywall
subscription_typestringOptionalSubscription tier or product required to access the content
positionstringOptionalHow the gate was presented. One of: inline, modal

subscription_started

Fired when a user successfully starts a subscription (paid or trial). For Signal’s canonical revenue mapping this aligns with the GA4 purchase conversion.

datafly.track('subscription_started', {
  transaction_id: 'SUB-10042',
  plan_id: 'digital-monthly',
  plan_name: 'Digital Monthly',
  value: 9.99,
  currency: 'GBP',
  trial: false
})
PropertyTypeRequiredDescription
transaction_idstringRequiredUnique subscription order identifier — used for deduplication
plan_idstringOptionalMachine-readable plan identifier
plan_namestringOptionalHuman-readable plan name
valuenumberOptionalSubscription price charged at start
currencystringOptionalISO 4217 currency code
trialbooleanOptionalWhether this is the start of a free trial
⚠️

Always send transaction_id on subscription_started events for deduplication. Platforms like Meta CAPI and GA4 use it to prevent double-counting alongside browser-side events.


subscription_renewed

Fired when an existing subscription renews and the reader is charged again.

datafly.track('subscription_renewed', {
  transaction_id: 'SUB-10042-R7',
  plan_id: 'digital-monthly',
  value: 9.99,
  currency: 'GBP'
})
PropertyTypeRequiredDescription
transaction_idstringRequiredUnique identifier for the renewal charge — used for deduplication
plan_idstringOptionalIdentifier of the renewing plan
valuenumberOptionalAmount charged on renewal
currencystringOptionalISO 4217 currency code

subscription_cancelled

Fired when a user cancels their subscription.

datafly.track('subscription_cancelled', {
  plan_id: 'digital-monthly',
  reason: 'too_expensive'
})
PropertyTypeRequiredDescription
plan_idstringOptionalIdentifier of the cancelled plan
reasonstringOptionalCancellation reason provided by the user

Content Items Schema

💡

Several discovery events accept an items array. Each object can include the following properties. The items[] array uses GA4-style field names. Signal’s server-side blueprints automatically transform these to vendor-specific formats (e.g., Meta’s contents[] with id/title fields).

PropertyTypeDescription
item_idstringYour internal content ID
item_namestringContent headline or title
content_categorystringSection or category of the content
content_typestringType of content (e.g. article, video, gallery, podcast)
authorstringAuthor name or byline
indexnumberPosition in list (0-indexed, relevant in list/feed contexts)
item_list_idstringIdentifier of the list the item was shown in
item_list_namestringName of the list the item was shown in

Vendor Mapping

The table below shows how Datafly Signal maps publishing events to the native event names expected by each vendor’s server-side API. Signal uses the GA4-style event names as its canonical format, and blueprints handle the transformation to vendor-specific payloads.

Signal EventGA4Meta CAPITikTok Events API
view_item_listview_item_listViewContentViewContent
select_contentselect_contentViewContentClickButton
category_viewedview_item_listViewContentViewContent
view_promotionview_promotion(custom)(custom)
select_promotionselect_promotion(custom)(custom)
searchsearchSearchSearch
search_performedsearchSearchSearch
article_viewedview_itemViewContentViewContent
article_readselect_contentViewContentViewContent
article_sharedshare(custom)(custom)
article_bookmarkedadd_to_wishlistAddToWishlistAddToWishlist
content_sharedshare(custom)(custom)
content_likedselect_content(custom)(custom)
content_savedadd_to_wishlistAddToWishlistAddToWishlist
content_downloadedfile_download(custom)Download
comment_postedselect_content(custom)(custom)
author_followedjoin_groupSubscribeSubscribe
topic_followedjoin_groupSubscribeSubscribe
video_startvideo_start(custom)(custom)
video_progressvideo_progress(custom)(custom)
video_completevideo_complete(custom)(custom)
audio_startselect_content(custom)(custom)
sign_upsign_upCompleteRegistrationCompleteRegistration
loginlogin(custom)(custom)
generate_leadgenerate_leadLeadSubmitForm
newsletter_subscribedsign_upSubscribeSubscribe
newsletter_unsubscribed(custom)(custom)(custom)
paywall_viewedview_promotionViewContentViewContent
subscription_startedpurchaseSubscribeSubscribe
subscription_renewedpurchaseSubscribeSubscribe
subscription_cancelledrefund(custom)(custom)