Is Tactiq: AI note taker for Google Meet, Zoom and MS Teams safe?
Tactiq is medium risk. After sign-in, observed requests send your email, name, Firebase user ID, and Mixpanel device ID to Mixpanel. One request updates a profile; another links the device ID to the signed-in user. The source also bundles session-recording code.
Who publishes itTactiq HQ Pty Ltd - no other listings under this identity, 1 shared hostname
Tactiq HQ Pty Ltd - no other listings under this identity, 1 shared hostname
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 1 hostname
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.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Tactiq sends signed-in identity to Mixpanel
After sign-in, observed requests send your email, name, Firebase user ID, and Mixpanel device ID to Mixpanel.
One request updates a profile; another links the device ID to the signed-in user.
The source also bundles session-recording code.
You sign in to Tactiq with Google.
The observed flow reached the Tactiq setup page after the OAuth grant.
The extension posts profile and identify events to Mixpanel.
Those requests contain the email address, full name, Firebase user ID, and Mixpanel device ID.
This local value is the signed-in account ID that the extension then uses in the Mixpanel identity calls.
chrome.storage.local key 'v2_user_id'{
"v2_user_id": "rBg5H0XK7CbCyOHgbaK4NWmsXlf1"
}| Field | Value | Why it matters | |
|---|---|---|---|
Your email address | lennyyeeman@gmail.com | This directly identifies the signed-in account using the extension. | |
Your full name | Lenny Yeeman | This adds a real-world name to the analytics profile for the signed-in account. | |
Your account ID | rBg5H0XK7CbCyOHgbaK4NWmsXlf1 | This lets the analytics profile connect extension events to the same signed-in account over time. | |
Your browser device ID | $device:8858dfd6-254f-4a35-a22e-098b9faf5ecd | This links the browser installation that was previously tracked separately to the signed-in account. | |
Your account telemetry | plan_tier=free; version=3.1.5804; webstore=Chrome; isLoggedIn=true | This describes the extension session and account state attached to the login event. |
Service worker initializes Mixpanel and identifies the signed-in user
function kV() {
let e = Be()?.getState().global.installationId;
!we() && console.log(`Starting Mixpanel with device ID [${e}]...`), $s.init(we() ? "01ed30ca1f6ac4cd0d4c3f59d96dbbb8" : "9a56981e0851ed25934f43c9cc43dced", {
...e ? {
device_id: e
} : {}
})
}
function MV() {
return wt() ? void 0 : window
}
var CV = new Set;
function yhe(e, t) {
return `${e}::${JSON.stringify(t,Object.keys(t).sort((r,n)=>r.localeCompare(n,void 0,{sensitivity:"base"})))}`
}
function In(e, t = void 0, r = void 0, n = !1) {
xV(e, t);
try {
if (n) {
let i = yhe(e, t ?? {});
if (CV.has(i)) return xV(`Skipping duplicate event: ${e}`, t), !0;
CV.add(i)
}
if (wt()) She(e, t, r);
else {
let i = hp(),
a = typeof t == "object" ? {
...t,
platformVersion: i
} : {
status: t,
platformVersion: i
};
Yr({
type: "tactiq.trigger-analytic",
event: e,
properties: a,
windowLocation: MV()?.location
})
}
} catch (i) {
console.error(i)
}
return !0
}
function vhe() {
return navigator.userAgentData?.brands.find(e => e.brand !== "Chromium" && e.brand.indexOf("Brand") === -1)
}
function Ew() {
return Be()?.getState().user?.id ?? Be()?.getState().global.savedUserId ?? Be()?.getState().global.installationId
}
function RV(e = {}, t = void 0) {
let r = Be()?.getState(),
n = t ?? r?.user?.id,
i = r?.transcriptions?.currentMeetingJoinTime,
a = vhe(),
o = i ? Math.floor((Date.now() - (i || 0)) / 1e3) : 0,
s = TV(SV(r), vV(r)),
u = wV(s),
c = {
event: "",
userId: n,
...n ? {
distinct_id: n
} : {},
isLoggedIn: !1,
widget: !0,
version: chrome?.runtime?.getManifest()?.version,
webstore: _he(),
browserLanguage: MV()?.navigator?.language,
browserName: a?.brand,
browserVersion: a?.version,
duration_seconds: o,
plan_tier: u,
session_id: Ko(),
...e
};
if (!n) {
let l = Be()?.getState().global.savedUserId;
l ? (c.userId = l, c.hasBeenLoggedOut = !0) : (c.isAnonymous = !0, c.anonymousId = Be()?.getState().global.installationId)
}
return c.installationId = Be()?.getState().global.installationId, c.isLoggedIn = !c.isAnonymous && !c.hasBeenLoggedOut, c.userId && (c.distinct_id = c.userId), c
}
async function She(e, t = void 0, r = void 0, n = void 0) {
try {
await PV();
let i = RV(t, n);
i.event = e, r && (qa(r) || $a(r)) && (i.pathnameSHA512 = (0, Fg.sha512)(r.pathname)), r?.host && (i.host = r.host, i.platform = dp(r));
let a = OV(r);
a && (i.meetingId = a), $s.track(e, JSON.parse(JSON.stringify(i)))
} catch (i) {
console.error(i)
}
} var Pg, _n, DV, Ug = !1,
Bg = (e, t) => {
_n = e, DV = t, Pg && !Ug && LV().catch(r => console.error(r))
},
LV = async () => {
if (!Ug && !(!_n?.uid || !_n.email)) {
Ug = !0;
try {
we() || console.log("Logging into analytics..."), kV(), $s.identify(_n.uid), $s.people.set({
$email: _n.email,
$name: _n.displayName
}), await Wt.updateContext({
userId: _n.uid,
email: _n.email,
domain: _n.email.split("@")[1] ?? "anonymous",
anonymousId: DV
}), await Ng(_n.uid)
} catch (e) {
Ug = !1, console.error(e)
}
}
}, PV = async () => (Pg || (Pg = (async () => {
kV(), _n?.uid && _n.email && await LV()
})()), Pg); async function f5(e) {
let t = await yc(pr(), e.token);
if (t?.user?.uid) Bg(t.user, Be().getState().global.installationId), In("Logged In");
else throw new Error("Could not log in")
}- api-js.mixpanel.com
Mixpanel receives the profile update, identify event, and login telemetry from the extension.