GuidesAPI ReferenceRelease Notes
HomeLog InHome
Guides

Migrate to the unified events schema

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 16, 2026.

All platforms emit the same event names, properties, and values in the unified schema. This page is the reference for what changes in the payload: the envelope, the categories, the value remap, and the events and properties that were renamed, removed, or added.

Read 2026 events schema - breaking changes ahead first for the timeline.

The code changes your app needs are platform-specific, because the starting point differs per platform. Use the guide for your platform once you've read this page. Those guides include a list of changes not to make without a person, and they apply even when the change would clear a compile error:

The payload reference on this page applies to every platform.

API reference: iOS | Android | Web

Before you start

  • One schema, every platform: the event names, properties, and values on this page are the same everywhere. Only the host app changes differ.
  • Each SDK version runs one schema. iOS and Android 2.0.x, and Web 2.0.x, emit the unified schema. iOS and Android 1.21.x, and Web 0.37.x, keep the legacy schema. Upgrade and validate at your own pace. From January 2027, new SDK releases ship only the unified schema. Legacy events from older versions still process.
  • There are no deprecation shims. Old names aren't aliased to new ones, so anything you miss goes silent rather than failing.

Incomplete mappings

The full list of properties on each event isn't published yet. Treat a mapping that isn't listed here as unknown, not as unchanged. A few values are still being confirmed against the tracking plan. The date in the banner tells you which version you're reading.

The envelope

These rules changed for every event, alongside the fields in the table below:

  • Each event carries exactly its own properties. A missing property means the property doesn't apply to that event. It doesn't mean null. Fields you used to read off any Video event may only exist on the events that define them, so check before you read.
  • Empty values are omitted. Nothing arrives as {}, "", or 0x0.

Timestamps use the format yyyy-MM-dd HH:mm:ss.SSS. That's deliberate, and it isn't ISO 8601. Both timestamps on an event come from the same instant.

LegacyUnifiedWhat to watch
api_scheme_version: "2"api_scheme_version: "2.1"Accept both while legacy and unified traffic share a pipeline. Queued legacy events still arrive after you upgrade, so the tail is mixed for a while.
containertabRead tab properties. The container object is gone.
geo, page, wsc_internalNo replacementThese were always empty.

Categories

LegacyUnifiedWhat to watch
Story, Moment, Video, Ad, Widget, Interaction, Searchstory, moment, video, ad, widget, interaction, searchEvery event_category value is lowercase. A filter on the capitalized value matches nothing.
sdk_init under Widgetsdk_init under globalA session count filtered on category Widget loses every sdk_init.
NonetabNew category.
NoneglobalNew category.

Value remap

Every enum value is lowercase snake_case. Don't recase your old values and assume they match. Several sets were remapped, so the identity changed, not just the casing.

PropertyLegacy valuesUnified values
content_typeStory, Moment, Videostory, moment, video
audio_stateMute, Unmutemute, unmute
gesture_typeClick, Swipe-Upclick, swipe_up
viewing_modeFullscreen, Inline Preview, Inline Interactivefullscreen, inline_preview, inline_interactive, plus picture_in_picture, casting, out_of_screen. Web emits embedded or fullscreen for the page surface.
*_navigation_typeAutomatic, Manualautomatic, manual
*_navigation_directionForward, Backward, Closeforward, backward, close
widget_typeRow, Gridrow, grid
thumbnail_typeCircle, Rectangle, Maincircle, rectangle, main
thumbnail_formatAnimated, Staticanimated, static
seek_directionForward, Backwardforward, backward
seek_typeSeek Bar, Double Tap, Seek Buttonseek_bar, double_tap, seek_button
playback_speed_typePress Holdpress_hold
device_orientationPortrait, Landscapeportrait, landscape
video_orientation_changed_triggerInitial Request, Button, Device Rotationinitial_request, button, device_rotation
stream_statusLive, Upcoming, Ended, and "" for unknownlive, upcoming, ended, unknown. The empty string is gone.
transition_methodclick, swipe, programmatictap, swipe, programmatic. A tab tap reports tap, and click is the call to action gesture.
cc_stateOff, Unavailable, Unknown, or an ISO codeoff, not_available, unknown, or an ISO code
referring.origin_widget_typeRow, Gridrow, grid, matching widget_type on the same event
tech.device_typePhone, Tablet, Unknownphone, tablet, unknown. Web also desktop.
tech.connection_typewireless, cellularwifi, cellular, plus unknown when connectivity can't be queried. Web also wired.
tech.operating_system, tech.device_brandCapitalized, such as iOS and AppleLowercase, such as ios and apple
tech.screen_resolution1170X2532, with a capital X1170x2532
sdk_typeCapitalized, such as iOS-RTNLowercase, such as ios-rtn. The values are platform-specific: ios, ios-rtn, ios-flutter, ios-rtn-flutter, android, android-rtn, android-flutter.

Exit triggers

Don't recase these. Stories completed is not stories_completed. Swipe Down is not in the unified vocabulary.

LegacyUnified
Auto Skipauto_skip
Skipskip
Swipeswipe
Swipe DownNo value. Swipe to dismiss reports user_swipe_to_dismiss. Swiping between playlists reports swipe.
Video Finishedvideo_finished
Close Buttonclose_button
App Closeapp_close
URL Expirationurl_expiration
Stories completedstory_completed
App Backgroundapp_background
Viewing Mode Transitionviewing_mode_transition
User Skip Nextuser_skip_next
User Skip Previoususer_skip_previous
Inlineinline
User Swipe To Dismissuser_swipe_to_dismiss
Nonefocus_lost

Start triggers

Don't recase these. Moments Container is not moments_container.

LegacyUnified
Widgetwidget
Deeplinkdeeplink
Entry Pointentry_point
Auto Play from Widgetauto_play_from_widget
Notificationnotification
App Foregroundapp_foreground
Inlineinline
Moments Containermoments_tabs
Skipskip
Swipeswipe
Auto Skipauto_skip
Viewing Mode Transitionviewing_mode_transition
Noneauto_advance
Nonefocus_regained

Values that changed meaning

  • Stories completed became story_completed, singular. It marks one Story finishing all its pages, not a playlist completing. For whole-playlist completion, check remaining_unread_count = 0 on the same event.
  • Moments Container became moments_tabs. A standalone container and a tabs component now report the same start trigger. Tell them apart with tab.total_tabs_count, which is 0 for a standalone container.

Auto advance on Moments

Moments report auto_skip where Videos and Stories report auto_advance. A funnel built on auto_advance across all three content types misses Moments. This is under review, so check this page before you build on either value.

Renamed events

Legacy eventUnified eventWhat to watch
playback_play / playback_pauseuser_initiated_playback_play / user_initiated_playback_pauseThe name now states that system pauses are excluded.
ad_playback_play / ad_playback_pauseThe same action names under event_category: adFilter on the category to isolate ad pauses.
container_data_loadtab_data_loadAlso fires for tabbed containers, which emitted nothing here before, so volume is higher than the event it replaces.
search_clicksearch_opened
video_orientation_changedvideo_orientation_changed_trigger

Removed events

Removed eventWhere the signal went
moments_playlist_start / moments_playlist_exitmoment_start and moment_exit. Count unique moments_session_id for sessions, and read moment_start_trigger for what opened the feed. Duration is the first moment_start to the last moment_exit in one session.
casting_started / casting_endedviewing_mode_transition where viewing_mode is casting. Duration is cast_duration on the transition away, so you no longer pair a start and an end yourself.
pip_enabled / pip_disabledviewing_mode_transition where viewing_mode is picture_in_picture. There's no duration counterpart, so derive it from consecutive transitions.
cta_visible / cta_dismissedThe cta_shown boolean on story_start, moment_start, and video_start, plus cta_config. cta_dismissed has no replacement.
audioThe audio_state property on every Story, Moment, and Video event. A toggle between two events is invisible, so treat toggle counts as a lower bound.
Interaction echoes of share_click, audio, play, and pauseinteraction emits interaction_view and interaction_answer only. The Viewer action is still reported under story or moment, so correlate by content ID.
forced_playback_play / forced_playback_pause, and their ad variantsNo replacement. A pause caused by your app, a tab switch, an ad, or backgrounding produces no event, so a forced against user ratio is no longer computable.
search_suggestions_shownsearch_suggestions_clicked still fires. For suggestion click-through, count those clicks against search_opened, which fires when the search screen opens.
buffer_start / buffer_endRemoved, no replacement.

Removed properties

Removed propertyWas onWhat to do
playback_action_methodStory, Moment, Interaction, AdNo replacement. Press and hold against tap pause is no longer distinguishable.
current_mode, next_modeThe viewing_mode objectviewing_mode is a single value, the mode being moved to. Read the previous mode from the preceding event.
interaction_textInteractionNo replacement. This carried the poll or quiz question text, so join to your CMS by interaction_id if you need it.
audio_stateInteractionGone from Interaction events only. Read it from the Story, Moment, or Video event covering the same content.
gesture_typeMoment, VideoNo data is lost: it was never populated on those players, which have a single call to action gesture. Still sent on Story and Ad events.
cta_config.typeStory, Moment, VideoNo action. It was never populated.
story_composition_typeStoryNo replacement. Nothing ever populated it.
is_likedStoryNo replacement. Stories have no like state. Still sent on Moment and Video.
from_tab_title, to_tab_title, tab_navigation_typeMomentRead tab_transition, which carries current_tab_*, next_tab_*, and transition_method.

placement_id, placement_name, widget_fold_position, and widget_page_type are not in this table because they are not removed. They are Web tracking-plan fields that never arrived on iOS or Android. Don't delete Web reads of them.

Renamed and moved properties

Legacy propertyUnified propertyWhat to watch
origin_widget_idorigin_source_id and origin_source_typeBoth are lists now. See Referring origin is a list.
labels_expressiondata_source_type and data_source_value
search_session_id, query_id, result_id on Story, Moment, and Videoreferring.search_session_id, referring.search_query_id, referring.search_result_idThe search_ prefix on the last two is new. The same names stay on Search events themselves.
container_id and other container_* propertiestab_id and the matching tab_* properties
video_session_triggervideo_start_triggerPer-item, not per session.
story_id, story_title, moment_id, moment_title on Ad eventscontent_id, content_titleTold apart by content_type on the same event.
widget_namewidget_idThe two carried the same value.
Interaction content_titleinteraction.content_nameSame value, the tracking plan's name for it.
next_video_id next_video_idThe trailing space in the legacy key is fixed. Remove the workaround if you trimmed it yourself.

Referring origin is a list

origin_source_id and origin_source_type are index-aligned lists that name every surface on the way into the session, root first. A widget tap that lands in a Moments tabs component reports ["<widget_id>", "<tab_id>"] and ["widget", "moments_tab"], so the tabs journey stays attributable to the widget click instead of starting at the tab.

Journeyorigin_source_idorigin_source_type
Widget to player["<widget_id>"]["widget"]
Widget to Moments tabs["<widget_id>", "<tab_id>"]["widget", "moments_tab"]
Host-opened tabs["<tab_id>"]["moments_tab"]
Deep link or notification["<source_id>"]["entry_point"]
  • Join widgets on the first element. Use origin_source_id[0] where origin_source_type[0] is widget.
  • A missing identifier is reported, not dropped. A deep link or notification where you passed no source ID reports the literal value not_provided, so pass one if you want the attribution.
  • Widget fields stay scalar. origin_widget_type, widget_builder_metadata, and the search identifiers are single values whenever a widget is in the chain. data_source_type and data_source_value describe the tab's own content.

Search attribution moved here too. search_session_id, search_query_id, and search_result_id now ride every player event for the rest of the session, rather than only the start event.

Properties that changed meaning

This is the group that nothing warns you about, because the property still arrives under the name your code already reads.

PropertyWasNow
cta_showntrue whenever the content carried a call to action, even when your player style had it switched offtrue only when the content carries one and your style displays it
cta_config{} when the content carried an empty objectOmitted unless at least one of text, color, or url has a value
follow_button_visibletrue whenever the content had a followable entitytrue only when it does and your style shows the follow control
story_index, total_stories_count, video_index, total_videos_count, next_story_id, next_video_id, next_moment_id, remaining_unread_countCounted the ads the player interleaves, so totals and indexes shifted once an ad preceded the current item. next_video_id could return an ad's IDDescribe content only
widget_size, thumbnail_size, advertiser_name0x0 or "" when unknownOmitted when unknown
origin_source_id, origin_source_typeOne string each, naming the single surface that opened the sessionLists naming every surface, root first
origin_source_type for a Moments tabmoments_tabsmoments_tab, one surface per element. The start trigger value moments_tabs is unchanged.

New events

EventCategoryFires when
tab_data_loadtabA tab's or a standalone container's content finishes loading. Once per tab ID per session, and not on a fetch that returns nothing.
tab_transitiontabThe active tab changes. Not on the first selection, and not on tapping the active tab. Carries current_tab_*, next_tab_*, and transition_method.
playback_initial_startadThe first rendered frame of a custom native ad in the Moments or Stories player.

description_expanded and description_collapsed already existed on some platforms. They now fire at most once per Moment view. Don't add them as new events.

Standalone container vs tabs component

tab_data_load fires for both. A single-tab tabs component reports 1, so it stays distinguishable.

PropertyStandalone containerTabs component
total_tabs_count01 or more
tab_idThe container's own IDThe tab's ID
tabs_source_idThe container's own IDThe tabs component's ID
tab_index0The tab's position, or -1 for a tab hidden from the visible list
tab_titleAbsentThe tab's title

New properties

Don't add a handler for a property that's already listed under Properties that changed meaning or Renamed and moved properties. Those already arrived. This table is only fields that didn't.

Now availableWhereReplaces
remaining_unread_pagesStoryComputing remaining pages yourself
is_likedMoment, VideoA lookup against your own like state
user.followed_entitiesStart, exit, and load events onlyA lookup against follow state. It's kept off high-frequency events on purpose.
follow_subject_id, follow_subject_type, follow_subject_provider_nameMomentInferring which entity the follow control offered
custom_action_button_shown, custom_action_button_config.*Story, MomentA CMS config lookup for custom action buttons. Not available on Video.
content_ratio, is_last_page, story_page_typeStoryContent lookups. story_page_type is on page-scoped events, not on story_start.
loop_settings.infinite_loop, loop_settings.loop_and_advanceMomentPlayer config lookups for loop analysis
tabs_enabled, tabs_configuration, initial_tab_title, active_tab_titleMomentThere was no way to know the tab context
cast_durationVideoPairing a casting start and end yourself. It's wall-clock seconds, so a pause or a seek during the cast doesn't change it.
back_to_live_trigger, video_time_start, video_time_endVideoValues that were computed but never sent
cc_stateVideoIt was only on Story and Moment before
gesture_typeStoryIt was declared but never populated
content_nameWidgetFetching the clicked item's name separately
content_page_id, story_page_content_extra_infoInteractionPopulated from the Story page
data_source_config, widget_builder_metadatareferringInferring how a view was sourced. Recommendation sources report the generation the backend served, such as for_you_v1, falling back to for_you when the response carries no version.
session_id, content_id, content_title, content_type, audio_state, content_extra_info, backoffice_campaign_data on ad_requestedAdJoining an ad request to the content that made it

video.quality arrives null. The player doesn't expose rendition quality. Widget placement fields (placement_id, placement_name, widget_fold_position, widget_page_type) never arrive on iOS or Android. They stay in the Web tracking plan.

Ads in the tracking plan

Ads used to sit outside the plan and were unaffected by schema changes. Ad events now ride the same 2.1 envelope under event_category: ad, trimmed per event like every other domain, including custom native, IMA, and banner.

Ad pauses are reported only when the Viewer causes them, so the forced variants are gone. Banner events attribute to the Story the banner was created for, not whichever Story is on screen when the ad network calls back.

Behavioral rules

These rules hold on every platform. They're easy to miss reading a property list, and reports built on the old behavior can quietly disagree with them.

RuleWhat it means
Start and exit alternatePer player, exactly one *_start is open at a time and one *_exit or video_end closes it. A loop restart of the same item is the only intended repeat start.
focus_lost pairs with focus_regainedA player covered without closing, such as a host tab switch, a modal, or the call to action web view, exits with focus_lost and resumes with focus_regained in the same session.
app_background and app_foreground are symmetricAn app_foreground start is sent only where an app_background exit was. Entering picture in picture or a route picker produces neither.
Load events fire once per sessionwidget_load fires once per widget_id, and tab_data_load once per tab_id. Refetches, retries, and re-renders don't re-fire, and a fetch that returns nothing fires nothing.
Navigation type comes from the actionTimer and auto-advance transitions report automatic. Taps, swipes, and accessibility navigation report manual. It's never derived from the viewed percentage.
Exit triggers say why playback endedstory_completed means the Story finished, whether or not another follows.
Start triggers describe the item, not the sessionThe first item carries the entry trigger. Later items report how they were reached: swipe, skip, or auto_advance. A loop start carries the trigger of the current viewing.
Viewed percent is per viewing segment*_duration_viewed_percent covers the segment since the matching start, not the whole item across a background or focus split. Sum the segments of one session ID for total exposure.
playback_initial_start fires once per item viewNever again on a loop or a replay of the same item.
Description events are capped per Moment viewdescription_expanded and description_collapsed fire at most once each. Loops don't reset the cap; moving to another Moment does.
share_click fires when the sheet opensNever on a tap that failed to open one.
Rapid seeks aggregateA burst of seeks is one seek event carrying the accumulated seek_total_time. A burst closes after one second idle, on a direction reversal, or on an item change.
Fullscreen to inline is a transitionLeaving fullscreen back to inline is video_end then video_start, both with viewing_mode_transition, in the same video_session_id. It's never close_button.
Story event order is fixedstory_start precedes the first story_page_start, and the last page's story_page_exit precedes story_exit.
onEventTriggered fires exactly onceOnce per event, in emission order. Nothing is replayed.
Like and follow fire on the transitionNot as a toggle count.

Related


Did this page help you?