GuidesAPI ReferenceRelease Notes
HomeLog InHome
Guides

Migrate on Web

Heads up:

Preliminary pre-release documentation, subject to change without notice. The full list of properties on each event isn't published yet.

Updated on September 25, 2026.

The unified schema ships in Web 2.0.x. Widgets and players still render. Analytics listeners have no compile step, so a mismatch keeps running and sees nothing.

This page is written to be followed by a developer or a coding agent. Read Ask before you change these before you change code.

Read first: Migrate to the unified events schema holds the mapping tables this page assumes. This page adds only what's specific to Web. The old value to new value pairs are only on that page, so don't change code from this page alone.

API reference: Web

Before you start

  • Wait for the SDK version. The unified schema ships in Web 2.0.x. Don't start the code changes until you're upgrading to that line.
  • Accept both schema versions. Your pipeline needs to accept api_scheme_version "2" and "2.1" while older SDK versions in the field still emit legacy events. "2" is the 0.37.x line. "2.1" is 2.0.x.
  • Nothing is aliased. Old names aren't mapped to new ones anywhere in the SDK, so anything you miss goes silent rather than failing.
  • The full list of properties on each event isn't published yet. Treat a mapping that isn't listed as unknown, not as unchanged. Don't run this as an unattended automated pass.

Ask before you change these

Don't make these changes without a person. The mapping tables say what the payload does now, not what your app should do about it. Listener remaps in Fix silent listener matches are in scope. Rows below are not. Don't map a removed event to the nearest surviving name to keep a dashboard drawing.

ChangeWhy a person decides
story_completedIt now means one Story finished, not a completed playlist. Code that treated it as playlist completion needs remaining_unread_count = 0, which is a logic change.
Removed events with no replacementForced play and pause, and cta_dismissed, are gone. Dropping the feature that depended on them is a product decision. search_suggestions_shown is gone. For suggestion click-through, count search_suggestions_clicked against search_opened. On Web, heartbeat is also gone. Reconstruct watch time from video_current_time and seek events.
Call to action metricscta_shown counts once per start rather than once per appearance, so thresholds and rates built on the old counting need rebasing. A color-only CTA, or one with empty text and url, reports neither cta_shown: true nor cta_config.
Search attributionSearch identifiers ride every player event in the session, so counting logic has to move to unique search_session_id.
Widget containerId valuesThe label groups your widget metrics, so choosing it is a naming decision, not a fix. Don't generate it per page load, and don't paste a name from the class, the file, or an example.
Consent listenersEvery consent event arrives as event_action: "consent_action". Your original action string is in consent.action. Matching the action string as event_action matches nothing.
banner_ad_clickThe count is a floor inferred from focus, not a click callback. Don't treat it as an exact click-through numerator.

Step 1: Find the affected code

The result of this step is a list of files to change, not a change.

  1. Search your app for each string in the table below.
  2. Search each Legacy cell in Migrate to the unified events schema: value remap, exit triggers, start triggers, renamed events, removed events, removed properties, and renamed properties.
  3. Search your dashboards, saved segments, and warehouse queries for the same strings. Code and reporting break independently, and reporting breaks silently.

Nothing fails at build time. A listener that still matches a legacy event_action, a capitalized event_category, or an unprefixed property key keeps running and silently sees a different value, or nothing. Review those by hand.

Search forWhy it matters
onEventTriggered, blaze-event-triggeredHandlers that read the payload
e.detail.eventData, eventDataProperty reads. Unprefixed keys such as story_id are now undefined
event_category, event_actionCode that branches on event identity
playback_play, playback_pauseRenamed to user_initiated_playback_play / user_initiated_playback_pause
screen_changeRenamed to viewing_mode_transition
share as an event_actionVideo share is now share_click
sdk_initSame name. event_category moved from widget to global
origin_widget_id, origin_placement_id, origin_placement_name, labels_expression, story_sourceReferring fields that are gone. Join on origin_source_id[0]
JSON.parse on payload fieldscontent_extra_info, story_page_content_extra_info, widget_content_list, and follow_state are no longer JSON strings
screen_state, current_state, next_stateReplaced by a single viewing_mode string
forced_playback_play, forced_playback_pauseRemoved, no replacement. Report the call site. See Ask before you change these.
cta_visible, cta_dismissedRemoved. cta_dismissed has no replacement. See Ask before.
audio, share_success, heartbeatRemoved events
moments_playlist_start, moments_playlist_exitRemoved events
expand, minimizeRemoved. Use viewing_mode_transition
search_suggestions_shownRemoved. For suggestion click-through, count search_suggestions_clicked against search_opened. See Ask before.
pushConsentEventWire shape changed. See Ask before.
WidgetRowView, WidgetGridView, WidgetEmbeddedStory, WidgetEmbeddedVideoThe first argument is widget_id. See Required widget id.
Title Case string literals compared against property valuesSearch the Legacy column of the shared value remap. Don't recase and assume a match.

Step 2: Fix silent listener matches

There are no deprecation shims and no compile errors. If a search hit matches Ask before you change these, stop and report it.

WasNow
Unprefixed keys on eventData, such as story_idPlan names including the namespace: story.story_id, tech.device_type, moment.moment_exit_trigger, story.cta_config.text. A read of the unprefixed key is undefined.
api_scheme_version "2" onlyAccept "2" and "2.1"
event_action === "playback_play" / "playback_pause"user_initiated_playback_play / user_initiated_playback_pause
event_action === "screen_change"viewing_mode_transition
event_action === "share" on Videoshare_click
sdk_init filtered by category widget or WidgetCategory is global
origin_widget_id, origin_placement_id, origin_placement_name, labels_expression, story_source, referrer_page_type, referrer_page_domain, session_referrer_*referring.origin_source_id and origin_source_type are index-aligned lists. On Web the chain is one element. Join on origin_source_id[0], and filter origin_source_type[0] when you need the surface kind (widget, embedded, entry_point, inline, moments_tab). Label expressions and content lists moved to data_source_type / data_source_value / data_source_config.
JSON.parse on content_extra_info, story_page_content_extra_info, widget_content_list, or follow_stateDrop the parse. Extra-info fields are objects. widget_content_list is an ordered array of content IDs. follow_state is an object.
placement_distance, ad_duration, ad_duration_viewed_percent, skip_time_offset, ad_index, loop_number as stringsNumbers. skippable is a boolean.
screen_state.current_state / screen_state.next_stateviewing_mode, a single string. Web emits embedded or fullscreen. On viewing_mode_transition it is the destination mode.
Custom consent action names as event_actionevent_action is consent_action. Branch on consent.action. Your payload is in consent.body.
tech.os / tech.os_versiontech.operating_system / tech.operating_system_version, lowercase values
user.ab_test_id / user.ab_test_variantuser.experiments, keyed by experiment ID
interaction content_titlecontent_name

Events that used to arrive empty now carry the plan payload when a value exists, including moment.like, moment.unlike, ad_requested, ad_click, the banner events, sdk_init, interaction_view, and interaction_answer. Treat an absent field as not on that event, not as null.

Step 3: Required widget id

The first argument to WidgetRowView, WidgetGridView, WidgetEmbeddedStory, and WidgetEmbeddedVideo is the widget's analytics id. It must match the DOM container's id.

A widget without a stable, host-chosen id still renders and still reports. It reports widget_id: "not_provided" after one console error, so its analytics can't be aggregated across sessions. On the 0.37.x line, a missing id suppressed widget_load entirely.

<div id="<placement-label>" style="width: 100%; height: 500px;"></div>
BlazeSDK.WidgetRowView('<placement-label>', options);

Don't invent <placement-label>. Report each call site. The string is a naming decision, not a code fix. Don't paste a name from the class, the file, or this page. See Ask before you change these.

Pass a stable label for the placement, not for the page load. Keep it identical across reloads and app versions, and use a different one for each placement, because widget metrics are grouped by this value.

Step 4: Web data changes

These changes are specific to Web. The schema is the same on every platform, and what differs here is how the DOM channel delivers it, and where Web reporting becomes more complete or more precise than it was. Nothing errors, so a report built on the previous behavior keeps running on the new numbers.

Fields and values that report differently

What changedWhat it means for your numbers
viewing_modeWeb emits embedded or fullscreen. There is no inline_preview or inline_interactive on Web.
tech.device_typeIncludes desktop, alongside phone, tablet, and unknown.
tech.connection_typewifi, cellular, wired, or unknown. On most browsers it is unknown. Don't parse it as a speed class. The earlier speed values were never valid.
referring.origin_source_idA one-element list on Web, or "not_provided" when the opener has no id. Embedded-hosted player events now carry the embedded element's id. They previously reported "not_provided".
story_page_typeThe CMS page type (content, ad, intro, and so on). It was a homepage or article URL guess. It rides page-level events only. story_start and story_exit don't carry it.
moment_index and other playlist indexesContent only. Ads are excluded, so values shift.
Live timeline fieldsWhile upcoming: video_duration, video_current_time, and video_time_end are absent. While live: duration is 0. An ended stream also masks the timeline for Viewers who never watched it live in the session.
video_time_start / video_time_endWindow semantics aren't finalized. Treat a mapping here as unknown, not as a contract.
next_video_idOnly on video_end.
qualityWeb only. Populated when HLS reports a level.
widget_fold_positionWeb only: above_fold or below_fold, measured at initial load, on load and visible events.
advertiser_id / campaign_idGAM line-item IDs on custom-native ads, from ad_view onward. ad_requested can't carry them. Names stay absent: GAM's web API doesn't expose them in the browser.
ima_ad_tappedDeclared in the tracking plan. Not emitted on Web. The HTML5 IMA SDK reports CLICK only.

Web-only events

EventWhat to do
heartbeatRemoved. It was already sending nothing. Reconstruct watch time from video_current_time and seek events. The config keys videoHeartbeatEnabled and videoHeartbeatIntervalSeconds are ignored.
expand / minimizeRemoved. Use viewing_mode_transition.
share_successRemoved. share_click is the intent. share_select is the destination, with share_destination.
share_selectFires when the share modal resolves to a destination. Moment and Video.
embedded_data_loadFires once per embedded element per session. A refetch or retry stays silent.
ad_exitFires on every exit path out of a custom-native ad, with ad_exit_trigger.
ad.playback_initial_startFires once per ad, when the creative first renders.
moment.seekFires once per scrub burst.
banner_ad_clickFires. Treat the count as a floor. See Ask before.
consent_actionConsent events that didn't arrive will start arriving. Match consent_action, then consent.action.

Behaviors that change event volume

BehaviorWhat it means for your numbers
widget_visible dedups once per (widget_id, session)Repeated viewport enter and exit no longer re-fire. Expect widget_visible counts to drop.
Focus loss and regain are pairedLeaving the tab emits an exit with app_background. Returning emits a start with app_foreground. A CTA that covers the player exits with focus_lost and resumes with focus_regained. A CTA that navigates the same tab unloads the page, so that focus_lost has no matching start.
Closing the tabDesktop tab or browser close reports page_unload on the exit. Moments previously emitted nothing on tab close, so expect moment_exit to rise.
Seeks are burst-batchedOne video.seek or moment.seek per burst, not one per tap. Net-zero scrubs are silent.
Shareshare_click fires when a dialog opens, not on the button press. Presses that never opened a dialog are no longer counted.
cc_on / cc_offOnly a real off/on change reports. Re-selecting the active language, or changing language while captions stay on, reports nothing.
Story and Moment events on ad pagesSuppressed. The same activity is ad.* events.
Prev/next on VideoCloses the outgoing session with video_end (user_skip_next or user_skip_previous). The incoming item starts with video_start_trigger: skip. Session-completion math against 0.37.x isn't comparable.
Non-content Story pagesIntro, outro, end-card, and uploaded-content pages now report, with their own story_page_type. Expect story_page_start / story_page_exit to rise.
IMA lifecycle eventsima_ad_started, paused, resumed, skipped, and the quartile events read the documented IMA getter surface. Expect these counts to appear or rise.
back_to_liveFires when playback catches the live edge from a Viewer action (back_to_live_button or manual_scrub). Automatic catch-up after returning to the tab no longer emits.
remaining_unread_count / remaining_unread_pagesForward-only. Playlist end is remaining_unread_count: 0 on the exit.

Step 5: Verify

  1. Confirm api_scheme_version reads 2.1 in the payloads your app receives.
  2. Confirm event_category values arrive lowercase, and that your handler covers tab and global.
  3. Confirm no handler still matches a legacy value or an unprefixed property key. A handler that never fires is the failure mode here, so assert on a match rather than on the absence of errors.
  4. Confirm each widget containerId is the stable placement label you intended, and that it's identical across two page loads.
  5. Confirm the properties you depend on are present on the events you read them from. A missing property means it doesn't apply to that event, not that it's null.
  6. Confirm consent listeners match consent_action and then branch on consent.action.

Related


Did this page help you?