Is Netflix Party is now Teleparty safe?

Medium risk

Code analysis indicates Teleparty scrapes your complete Netflix viewing history and sends it to its own servers.

The extension code requests Netflix viewing history across all profiles in batches of 10,000 entries, including device names and last-watched timestamps. That data is transmitted to metis.teleparty.com/v1/record. This behavior has not been directly verified through dynamic analysis.

Netflix Partyv5.8.3Chrome Web Store
49Risk
Who publishes it

Delaware C Corporation - 1 other listing from the same operator, none carrying a finding

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
Netflix Party
Declared legal entity
Delaware C Corporation
Registered address
41700 Pacific Coast Hwy, Malibu, CA 90265, US
Registered contact
WP Interactive Media, Inc.

Same operator - 1 listing

Published under a different store account, but sharing the registered address, contact or declared legal entity this one gave the store.

Shared hosts - 5 hostnames

Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.

api.teleparty.com
Also called by 2 other listings, including Netflix Party is now Teleparty
tele.pe
Also called by 2 other listings, including Netflix Party is now Teleparty
teleparty.com
Also called by 2 other listings, including Netflix Party is now Teleparty
redirect.teleparty.com
Also called by 3 other listings, including Netflix Party is now Teleparty
crave.ca
Also called by 6 other listings

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

Streaming-Site URLs and Titles Sent to Teleparty Telemetry

When you start or join a watch party, Teleparty sends an event to metis.teleparty.com: your tab's URL/title, a permanent install ID, and, if signed in, your email.

Mirrored to PostHog.

Titles often reveal the show you're watching.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You start or join a Teleparty on a streaming site (Netflix, Disney+, Hulu, YouTube, HBO Max, etc.).

The same thing happens for several other in-extension events: video_start, video_pause, socket_open, socket_close, party_start, and others.

The extension did this

Teleparty quietly PUTs an event to metis.teleparty.com containing the tab's full URL and title, your install ID, and your email if you're signed in.

The same event is mirrored to PostHog (us.i.posthog.com) with the URL and title additionally repeated as current_tab_url / current_tab_title.

What's in every event PUT to metis.teleparty.com/v1/record
  • Full URL of your active tab
    https://www.netflix.com/watch/81251335?trackId=14170287

    The exact page you're on, including any tracking/session parameters in the URL.

  • Tab title
    The Crown: Season 4: Episode 3 “Fairytale” - Netflix

    The title shown in your browser tab, on Netflix or Hulu this is normally the show and episode you're watching.

  • Install ID (perm_id)
    f6f29d1ca0c13901

    A persistent identifier unique to your Teleparty install, sent with every event so the server can link your activity. See C#2121.

  • Email address (if signed in)
    alice.smith@gmail.com

    If you've signed in to Teleparty (via Google or Firebase auth), the email on your account is included in every event.

  • Display name
    Alice

    The nickname you've set in Teleparty.

  • Firebase user ID
    Yk2qP9XfQTWvHb3a8nGJjLZmTk13

    Internal Firebase auth UID for your Teleparty account, sent only if you're signed in.

  • Subscription plan
    premium_monthly

    Whether you're on free, premium_monthly, or premium_annual.

  • Device + browser fingerprint
    {name: "chrome", version: "5.5.5", os_name: "Linux", platform: "Linux x86_64", install_id: "f6f29d1ca0c13901"}

    Browser name and version, OS name, platform (Linux/Win/Mac), and the install ID repeated as device.install_id.

  • App session ID
    app-sess-71fe-9c12-4a30

    A per-session ID so events fired in the same browser session can be grouped together.

  • Client timestamp (ms)
    1744640329122

    Wall-clock time the event was generated, to the millisecond.

  • Extension version
    5.5.5

    Which version of Teleparty you're running (added explicitly on the PostHog mirror as `extension_version`).

Captured request
PUThttps://metis.teleparty.com/v1/record

200 OK with empty body. Captured live during dynamic analysis: PUT https://metis.teleparty.com/v1/record (body=659 bytes) with page.url, page.name, user.perm_id, device.install_id all present and matching the values stored in chrome.storage.local.

Headers
Content-Type
application/json
Body
{  "name": "video_start",  "app": {    "name": "chrome_ext",    "version": "5.5.5"  },  "app_session": "app-sess-71fe-9c12-4a30",  "client_timestamp_ms": 1744640329122,  "device": {    "name": "chrome",    "version": "5.5.5",    "type": "browser",    "manufacturer": "Linux x86_64",    "model": "",    "os_name": "Linux",    "os_version": "",    "install_id": "f6f29d1ca0c13901"  },  "page": {    "name": "The Crown: Season 4: Episode 3 “Fairytale” - Netflix",    "url": "https://www.netflix.com/watch/81251335?trackId=14170287"  },  "user": {    "id": "Yk2qP9XfQTWvHb3a8nGJjLZmTk13",    "email": "alice.smith@gmail.com",    "name": "Alice",    "signed_in": true,    "plan": "premium_monthly",    "on_trial": false,    "firebase_id": "Yk2qP9XfQTWvHb3a8nGJjLZmTk13",    "perm_id": "f6f29d1ca0c13901",    "uuid": "e83b1d7e-9b22-4f59-9e2c-1c9b3eaa01a4"  }}
The code that does this

The code that does this, lifted from the shipping extension.

Readable version

User block + payload assembly + send (We handler)

// In the We() event handler, after computing the active tab and current user state:const userBlock = {  id:          auth.currentUser?.uid ?? permId,  email:       auth.currentUser?.email,         // present only if signed in  name:        settings.userNickname,  signed_in:   isSignedIn,  plan:        plan,                             // 'free' | 'premium_monthly' | 'premium_annual'  on_trial:    onTrial,  firebase_id: auth.currentUser?.uid,  perm_id:     permId,                           // persistent install ID — see claim #2121  uuid:        appUuid};event = this.Ms(pageBlock, event, userBlock, permId);// Mirror to PostHog with the URL and title added explicitly:try {  const phEvent = Object.assign({}, event);  phEvent.extension_version = MANIFEST_VERSION;  phEvent.browser           = 'chrome';  phEvent.is_prod           = true;  if (activeTab) {    phEvent.current_tab_url   = activeTab.url;    phEvent.current_tab_title = activeTab.title;  }  if (destination === Destinations.BOTH || destination === Destinations.POSTHOG) {    PostHog.sendEvent(event.name, permId, phEvent);  }} catch (_) {}

Ms() merges page/user/device/app into the event

// Ms() assembles the metis event by mutating a copy of the partial event:Ms(pageBlock, partialEvent, userBlock, permId) {  const device     = this.Ls(permId);              // {name, version, type, manufacturer, model, os_name, os_version, install_id: permId}  const appSession = this.Zt.getAppSession();      // 'app-sess-...'  const tsMs       = Date.now();  const app        = { name: 'chrome_ext', version: MANIFEST_VERSION };  const event      = Object.assign({}, partialEvent);  event.app                ||= app;  event.app_session        ||= appSession;  event.device              = device;  event.client_timestamp_ms = tsMs;  if (event.page == null) event.page = pageBlock;   // pageBlock = { url: tab.url, name: tab.title }  event.user                = userBlock;  return event;}

Gs() does the PUT to metis

// Gs() is the actual outbound HTTP call:async Gs(event) {  const res = await fetch('https://metis.teleparty.com/v1/record', {    method:  'PUT',    headers: { 'Content-Type': 'application/json' },    body:    JSON.stringify(event)  });  if (res.status < 200 || res.status >= 300) throw new Error(res.statusText);}
Where the events end up
    • metis.teleparty.com

    Teleparty's own telemetry endpoint (path /v1/record). Receives the full URL + tab title + user/install ID for every party, video, and socket event.

    • us.i.posthog.com

    PostHog product analytics SaaS. Receives a mirror of the same events with current_tab_url/title attached. The PostHog write key is hardcoded in the extension.

Reproduce it yourself

Wraps fetch() in Teleparty's service worker so every PUT to metis.teleparty.com is logged to the DevTools console with the full event body, letting you see your own URL/title/email being shipped, in real time, without any extra tooling.

Requires
  • Chrome with Developer mode enabled
  • Teleparty (oocalimimngaihdkbihfgmpkcpnmlaoa) installed
teleparty-event-monitor.js · js
// teleparty-event-sniffer.js// Paste this into the DevTools console of Teleparty's background service worker// (chrome://extensions → enable Developer mode → click the 'service worker' link// under "Netflix Party (Teleparty)"). Then start a watch party on Netflix /// Disney+ / etc. and watch the events scroll past.(function () {  const origFetch = self.fetch.bind(self);  self.fetch = async function (input, init) {    try {      const url = typeof input === 'string' ? input : (input && input.url) || '';      const isMetis   = url.includes('metis.teleparty.com/v1/record');      const isPosthog = url.includes('us.i.posthog.com');      if ((isMetis || isPosthog) && init && typeof init.body === 'string') {        let parsed;        try { parsed = JSON.parse(init.body); } catch { parsed = init.body; }        const tag = isMetis ? '[TELEPARTY → metis]' : '[TELEPARTY → posthog]';        console.groupCollapsed(`${tag} ${init.method || 'GET'} ${url}`);        console.log('event name :', parsed && parsed.name);        console.log('page       :', parsed && parsed.page);        console.log('user       :', parsed && parsed.user);        console.log('device     :', parsed && parsed.device);        console.log('full body  :', parsed);        console.groupEnd();      }    } catch (_) {}    return origFetch(input, init);  };  console.log('[teleparty-event-sniffer] installed — start a watch party to see events');})();
How to run it
  1. 1Open chrome://extensions, enable Developer mode.
  2. 2Click 'service worker' under Teleparty.
  3. 3Paste the script in Console.
  4. 4Start/join a party on Netflix, Disney+, Hulu, or YouTube.
  5. 5Each PUT and PostHog mirror logs with full body.

Permanent Tracking ID Minted on Install, Sent With Every Event

On first run, Teleparty requests a fresh user ID from data3.netflixparty.com, saved as userId.

That ID goes into every event: metis.teleparty.com, data3.netflixparty.com/log-event, /log-experiment, PostHog, and the uninstall URL too.

Severity
Medium unwanted
Type
Unexpected
CWE
CWE-359
Source
Dynamic sandbox
What actually happens
You did this

You install Teleparty for the first time (or your previously stored userId is missing/invalid).

The extension did this

The extension calls home to data3.netflixparty.com/create-userId, gets back a fresh ID, and saves it permanently to local storage and into your uninstall URL.

From that point on, the same ID is sent on every analytics event so the operator can correlate everything you do across sessions and browser restarts as one user.

Captured request
GEThttps://data3.netflixparty.com/create-userId?browser=chrome

200 OK with response body "f6f29d1ca0c13901", a 16-character hex identifier. The extension immediately stores it as chrome.storage.local.userId. (On Edge the same call is made with `?browser=edge`.)

Headers
Accept
*/*
What's stored on your device

Written on first run. userId is the identifier in every analytics event, persisting until uninstall or manual clear.

Location
chrome.storage.local
Contents
{  "date": "Mon Apr 14 2026 14:12:09 GMT+0000 (Coordinated Universal Time)",  "userId": "f6f29d1ca0c13901",  "recentlyUpdated": true,  "recentlyUpdated3": true}
Where the same `userId` is sent, every name it travels under
  • metis.teleparty.com / v1/record
    user.perm_id = "f6f29d1ca0c13901"

    Sent in every event as `user.perm_id`, `user.id` (when not signed in), and `device.install_id`. See claim #2120 for the full event body.

  • data3.netflixparty.com / log-event
    {"userId": "f6f29d1ca0c13901", "eventType": "socket_open", "sessionId": "sess-9c12-4a30"}

    PUT request whose body is `{userId, eventType, sessionId}`, fired on various legacy/older event hooks.

  • data3.netflixparty.com / log-experiment
    {"permId": "f6f29d1ca0c13901", "event": "experiment_viewed", "name": "new_invite_modal", "version": 2}

    POST request body `{permId, event, name, version}`, used for A/B test bucketing on the same identifier.

  • us.i.posthog.com
    distinct_id = "f6f29d1ca0c13901"

    Sent as the PostHog `distinct_id`, the field PostHog uses to collapse all events into a single user profile.

  • https://www.teleparty.com/uninstall
    https://www.teleparty.com/uninstall?userId=f6f29d1ca0c13901&browser=chrome&version=5.5.5

    Bound via setUninstallURL when the ID is minted. Uninstalling opens this URL with your userId baked in, tied to your tracked identifier.

The code that does this

The mint-on-install code and the validity check that re-mints on every startup.

Readable version

Xe() — mint userId, store it, bake it into the uninstall URL

// Runs the first time the extension boots without a userId in storage.async function mintUserId() {  const browser = navigator.userAgent.toLowerCase().includes('edg') ? 'edge' : 'chrome';  console.log('browser: ' + browser);  const res    = await fetch('https://data3.netflixparty.com/create-userId?browser=' + browser);  const userId = await res.text();   // server returns a 16- or 36-char identifier as the response body  const now    = new Date();  if (isUserIdValid(userId)) {    // Persist the ID forever (until uninstall + storage wipe).    chrome.storage.local.set({      userId,      recentlyUpdated:  true,      recentlyUpdated3: true,      date:             now.toString()    }, () => console.log('Settings saved'));    // Bake the same ID into the uninstall URL so the operator learns when    // *this specific user* uninstalls.    chrome.runtime.setUninstallURL(      'https://www.teleparty.com/uninstall' +      '?userId='  + userId +      '&browser=chrome' +      '&version=' + encodeURIComponent(chrome.runtime.getManifest().version)    );  }}

isUserIdValid() — the entire 'validity' check

// The only sanity check on the server-issued ID is its length.function isUserIdValid(s) {  return s.length === 16 || s.length === 36;}

Re() — startup bootstrap, re-mints if missing/invalid

// Runs every time the background service worker starts.function bootstrapUserId() {  try {    storage.getAllItemsAsync().then(items => {      if (items.userId && isUserIdValid(items.userId)) {        // Refresh the uninstall URL with the existing ID + current version.        chrome.runtime.setUninstallURL(          'https://www.teleparty.com/uninstall' +          '?userId='  + items.userId +          '&browser=chrome' +          '&version=' + encodeURIComponent(MANIFEST_VERSION)        );      } else {        console.log('userId undefined/invalid in local storage -> now setting');        mintUserId();   // re-mint via /create-userId      }    });  } catch (_) {}}

Same userId pushed to data3.netflixparty.com/log-event

// Older code-path: PUT a {userId, eventType, sessionId} record.async function logEvent(t) {  try {    const body = {      userId:    await this.getUserId(),   // returns the persistent permId      eventType: t.eventType,      sessionId: t.sessionId    };    console.log('event: ' + JSON.stringify(body));    await fetch('https://data3.netflixparty.com/log-event', {      method:  'PUT',      headers: { 'Content-Type': 'application/json' },      body:    JSON.stringify(body)    });  } catch (e) {    console.log('log event error : ' + e);  }}

Same id (as permId) pushed to log-experiment for A/B bucketing

// A/B test bucketing keyed on the same persistent identifier.async function logExperiment(t) {  try {    const body = {      permId:  await this.getUserId(),      event:   t.eventType,      name:    t.experimentName,      version: t.experimentVersion    };    console.log('event: ' + JSON.stringify(body));    await fetch('https://data3.netflixparty.com/log-experiment', {      method:  'POST',      headers: { 'Content-Type': 'application/json' },      body:    JSON.stringify(body)    });  } catch (e) {    console.log('log exp error : ' + e);  }}
Hosts involved in the lifecycle of this identifier
    • data3.netflixparty.com

    Mints the ID at /create-userId on first run; receives it back at /log-event (field userId) and /log-experiment (field permId). Operated by Teleparty (formerly Netflix Party).

    • metis.teleparty.com

    Receives the same identifier as user.perm_id, user.id (when not signed in), and device.install_id on every event, see claim #2120.

    • us.i.posthog.com

    PostHog SaaS analytics. The same identifier is the PostHog distinct_id, so all of your events collapse into one PostHog profile.

    • www.teleparty.com

    Receives the identifier via the registered uninstall URL. Removing the extension opens teleparty.com/uninstall with userId, browser, version, informing the operator.

Reproduce it yourself

Dumps the persistent userId (and the matching uninstall URL) that Teleparty has stored locally, so you can verify with your own eyes that the same string is what the extension is sending out as perm_id / install_id / distinct_id.

Requires
  • Chrome with Developer mode enabled
  • Teleparty (oocalimimngaihdkbihfgmpkcpnmlaoa) installed
teleparty-userid-dump.js · js
// teleparty-userid-dump.js// Paste into the DevTools console of Teleparty's background service worker// (chrome://extensions → enable Developer mode → click the 'service worker'// link under "Netflix Party (Teleparty)").(async function () {  const items = await new Promise(res => chrome.storage.local.get(null, res));  console.group('[teleparty-userid-dump]');  console.log('userId in chrome.storage.local :', items.userId);  console.log('recentlyUpdated                :', items.recentlyUpdated);  console.log('recentlyUpdated3               :', items.recentlyUpdated3);  console.log('date first stored              :', items.date);  console.log('uninstall URL Chrome will open :',              await new Promise(res => chrome.runtime.getUninstallURL                ? res(chrome.runtime.getUninstallURL())                // older Chrome: surrogate by reconstructing                : res('https://www.teleparty.com/uninstall?userId=' + items.userId)));  console.groupEnd();  console.log('Tip: this exact userId is what Teleparty sends as perm_id / install_id /');  console.log('     distinct_id on every analytics event. Run the teleparty-event-sniffer');  console.log('     from claim #2120 alongside this to see it leaving the browser.');})();
How to run it
  1. 1Open chrome://extensions, enable Developer mode.
  2. 2Click 'service worker' under Teleparty.
  3. 3Paste the script in Console.
  4. 4Read the printed userId, date first stored, and uninstall URL, what Chrome visits, ID baked in, on removal.

Where it sends data

Destinations our analysis observed Teleparty contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • metis.teleparty.com

    Teleparty sends data to metis.teleparty.com. 2 other extensions we have analysed send data here.

Updated 30 September 2026oocalimimngaihdkbihfgmpkcpnmlaoa