Is Dupe.com: Find similar products for less safe?
Dupe.com sends the URL and product metadata from every HTTPS page you visit to Google Analytics.
The extension's content script runs on all HTTPS pages and reports each page view to Google Analytics, including the full URL, product title, price, currency, and image URL. Events are batched in the background service worker and POSTed to Google Analytics using a hardcoded API secret. A separate postMessage handler allows any web page to toggle the extension's side panel overlay before an origin check is enforced.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Product pages you view, plus your account ID, go to a second tracking backend
Separate from its analytics, Dupe.com's worker builds an event per product view (URL, price, brand, category, seller, SKU, rating, session ID, account/temp ID), sent to Tinybird with a per-install token behind a flag on in our Amazon test.
You open a product-detail page on a shopping site, for example, an Amazon or eBay listing, while the extension is installed and enabled.
This fires from background page-visit tracking; you don't need to open the extension's popup or interact with it.
The worker builds an event with the product's URL, price, brand, and category, tags it with your account or temp ID and a session ID, and sends it to Tinybird.
The behavior is gated behind a server-controlled feature flag; in dynamic testing the flag was enabled and the event fired exactly as described here.
| Field | Value | Why it matters | |
|---|---|---|---|
The product page address | https://www.amazon.com/Apple-MacBook-13-inch-256GB-Storage/dp/B0863TXGM3 | The full URL of the product page you viewed, with common ad-tracking parameters (utm_source, gclid) stripped but the rest intact. | |
Price, currency, brand & category | 164 GBP, Apple, Laptops | The product's listed price and currency, plus its brand and category as detected from the page. | |
Rating and review count | 4.6 stars, 62,639 reviews | The product's star rating and total review count as shown on the page. | |
Selected size, color, seller & SKU | Size: 13-inch, Color: Space Gray, Seller: Amazon.com, SKU: B0863TXGM3 | When you've picked a specific size, color, or seller listing, that selection is captured along with the item's SKU. | |
Session ID | s_1783561654549 (illustrative) | An identifier that groups all your page views from a single browsing session together. | |
Your Dupe account ID or temporary ID | a1b2c3d4-e5f6-4a1b-9c3d-7e8f9a0b1c2d (illustrative) | If signed in, your account ID is attached; if not, a persistent temp ID. Either way your product views stay linkable to one identity. |
| Content-Type | application/x-ndjson |
| Authorization | Bearer <redacted> |
{
"event_type": "pdp_view",
"shop_key": "amazon.com",
"url": "https://www.amazon.com/Apple-MacBook-13-inch-256GB-Storage/dp/B0863TXGM3",
"price": 164,
"currency": "GBP",
"category": "Laptops",
"rating": 4.6,
"review_count": 62639,
"timestamp": "2026-07-06T03:55:02.118Z",
"session_id": "<redacted>",
"user_id": "<redacted>",
"temp_user_id": "<redacted>",
"extension_version": "0.3.0",
"browser": "chrome"
}The EventEmitter that batches and sends product-page events, and the code paths that feed it
const TINYBIRD_ENDPOINT = "https://api.us-east.tinybird.co/v0/events?name=extension_events",
TINYBIRD_TOKEN = "<redacted>", // hardcoded bearer token, identical for every install
FEATURE_FLAG_KEY = "extension-tinybird-events",
FLUSH_INTERVAL_MS = 1e4, // 10 seconds
FLUSH_BATCH_SIZE = 20,
BUFFER_STORAGE_KEY = "dupe:event-buffer",
MAX_BUFFERED = 500;
class EventEmitter {
constructor(token) {
this.queue = [];
this.enabled = false;
this.flushTimer = null;
this.token = token || TINYBIRD_TOKEN;
}
async init() {
if (!this.token) return;
try {
this.enabled = !!(await FeatureFlags.getFeatureFlag(FEATURE_FLAG_KEY, 9e5)); // 900s cache, remote-controlled
} catch { this.enabled = false; }
if (!this.enabled) return; // no-op entirely when the remote flag is off
await this.restoreBuffer();
this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
}
async track(event) {
if (!this.enabled) return;
const enveloped = await this.envelope(event);
this.queue.push(enveloped);
if (this.queue.length >= FLUSH_BATCH_SIZE) this.flush();
}
async flush() {
if (this.queue.length === 0) return;
const batch = this.queue.splice(0);
const ndjsonBody = batch.map(e => JSON.stringify(e)).join("\n");
try {
const res = await fetch(TINYBIRD_ENDPOINT, {
method: "POST",
headers: { Authorization: `Bearer ${this.token}`, "Content-Type": "application/x-ndjson" },
body: ndjsonBody
});
if (!res.ok) throw new Error(`TinyBird HTTP ${res.status}`);
} catch { await this.bufferEvents(batch); }
}
async envelope(event) {
const session = (await chrome.storage.session.get("dupe:shopping-session"))["dupe:shopping-session"];
const sessionId = session?.startedAt ? `s_${session.startedAt}` : this.fallbackSessionId;
const user = await Auth.getActiveUser();
const tempUserId = await Auth.getTempUserId();
return {
...event,
timestamp: new Date().toISOString(),
session_id: sessionId,
user_id: user?.uid || null, // your signed-in Dupe account ID, when logged in
temp_user_id: tempUserId, // persistent pseudo-anonymous ID otherwise
extension_version: chrome.runtime.getManifest().version,
browser: "chrome"
};
}
}
const tinybirdEmitter = new EventEmitter();// Fired from the product-detail-page detector on every PDP view:
tinybirdEmitter.track({
event_type: "pdp_view",
shop_key: shopKey,
url: stripTrackingParams(pageUrl),
image_url: metadata?.imageUrl,
title: metadata?.title,
price: metadata?.price,
currency: metadata?.currency,
brand: metadata?.brand,
category: metadata?.category,
selected_color: metadata?.selectedColor,
selected_size: metadata?.selectedSize,
gender: metadata?.gender,
seller: metadata?.seller,
sku: metadata?.sku,
material: metadata?.material,
shipping: metadata?.shipping,
returns: metadata?.returns,
warranty: metadata?.warranty,
has_color: Array.isArray(metadata?.colors) && metadata.colors.length > 0 ? 1 : 0,
has_size: Array.isArray(metadata?.sizes) && metadata.sizes.length > 0 ? 1 : 0,
has_specs: metadata?.specs ? 1 : 0,
specs_json: metadata?.specs ? JSON.stringify(metadata.specs) : null,
has_rating: metadata?.rating != null ? 1 : 0,
rating: metadata?.rating,
review_count: metadata?.reviewCount,
review_summary: metadata?.reviewSummary,
review_snippets_json: Array.isArray(metadata?.reviewSnippets) && metadata.reviewSnippets.length > 0
? JSON.stringify(metadata.reviewSnippets.slice(0, 5))
: null
});
// Internal message handler — any component in the extension can trigger
// a Tinybird event by sending a TRACK_TINYBIRD message, with no field
// validation applied before it's enveloped and queued:
onMessage(Messages.TRACK_TINYBIRD, async ({ data }) => {
tinybirdEmitter.track(data);
return { status: "ok" };
});Once the remote feature flag is enabled, queued events are flushed to Tinybird every 10 seconds on a timer, or immediately if 20 events queue up before the timer fires.
- api.us-east.tinybird.co
Tinybird's Events API. Receives batched product-page events (URL, price, brand, category) tagged with your Dupe account or temp ID, under a token hardcoded in the extension.
Every page you visit, shopping or not, is reported to Google Analytics
Dupe.com's content script runs on every HTTPS page, reading the full URL plus product metadata found.
The background worker batches three views, sends them to Analytics under a persistent install ID.
News visits trigger the same report.
You open any HTTPS webpage while the extension is installed and enabled, a news article, a work tool, anything.
Nothing shopping-related has to happen; simply loading the page is enough to trigger tracking.
A content script running on that page reads the full page address and any product-style metadata it can find, then reports the visit to Google Analytics.
The report goes out even when the page is not a product page and even when you never interact with the extension's UI.
| Field | Value | Why it matters | |
|---|---|---|---|
The full page address | https://www.npr.org/2026/07/01/politics/some-article | Every page you load is captured verbatim, the full URL, whether or not it's a shopping site. | |
Site identifier (derived from the URL) | npr.org | A per-domain key computed from the page address, letting Dupe group all your visits to that domain together. | |
Page title | Breaking News, Latest Updates | NPR | Whatever the page's <title> or Open Graph title is, even on non-shopping pages. | |
Price, currency & image (if found) | 49.99 USD, https://cdn.shop.example/img/sku-4821.jpg | On e-commerce pages, the price, currency, and main image are read from the page's Open Graph tags and sent alongside the URL. | |
Your persistent analytics ID | 550e8400-e29b-41d4-a716-446655440000 | A stable ID tied to your install, letting every page view be linked back to the same identity across browsing sessions. |
Page views aren't sent to Google one at a time. They're queued in local browser storage and the extension automatically flushes the whole queue to Google Analytics as soon as 3 page views have accumulated.
{
"client_id": "<persistent-client-id>",
"user_id": "anonymous",
"events": [
{
"name": "extension_page_viewed",
"params": {
"medium": "extension",
"shopKey": "bbc.com",
"url": "https://www.bbc.com/news",
"isPdp": false,
"standDown": false
}
},
{
"name": "extension_page_viewed",
"params": {
"medium": "extension",
"shopKey": "theguardian.com",
"url": "https://www.theguardian.com/us",
"isPdp": false,
"standDown": false
}
},
{
"name": "extension_page_viewed",
"params": {
"medium": "extension",
"shopKey": "npr.org",
"url": "https://www.npr.org",
"isPdp": false,
"standDown": false
}
}
]
}The content-script tracker and the background handler that ship every page view to Google Analytics
const { isPdp: i, pageChanged: r } = pv(),
n = async s => {
const c = `https://dupe.com/${s}`;
await yi(vi.OPEN_TAB, { url: c })
};
return Ra.useEffect(() => {
typeof i > "u" || (async () => {
const s = of(window.location.href),
c = await Zh(), // extractMetadata(): title, price, currency, imageUrl, brand, description...
d = !!ov.get("stand-down");
Y0.track(Wt.ExtensionPageViewed, {
medium: "extension",
shopKey: s, // per-domain key derived from the URL
url: window.location.href, // the FULL current page address
isPdp: i, // true only on a detected product page
metadata: c, // OG title/price/currency/image when present
standDown: d
}, { skipPosthog: !0 }) // routed to GA only, not to PostHog
})()
}, [i, r]), Ra.useEffect(() => {
// ...separate listener for the "dupe this" context-menu action, omitted...
}, []), nullconst EVENT_BATCH_KEY = "dupe:eventBatchViews",
BATCH_SIZE = 3,
GA_API_SECRET = "<redacted>",
GA_MEASUREMENT_ID = "G-CZFTQBRF2Q",
GA_ENDPOINT = "https://www.google-analytics.com/mp/collect",
normalizeName = e => e.toLowerCase().replace(/\s/g, "_"),
loadBatch = async () => await storage.get(EVENT_BATCH_KEY) || [],
saveBatch = async e => { await storage.set(EVENT_BATCH_KEY, e) },
clearBatch = async () => await storage.remove(EVENT_BATCH_KEY),
postToGA = async (userId, events, clientId = "") => await fetch(
`${GA_ENDPOINT}?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`,
{
method: "POST",
body: JSON.stringify({
client_id: clientId || userId,
user_id: userId,
events: events.map(n => ({ name: normalizeName(n.name), params: { ...n.params } }))
})
}
);
function registerGaTrackHandler() {
onMessage(GA_TRACK, async ({ data: e }) => {
const { name: t, userId: r, properties: n, clientId: s } = e;
try {
const params = { ...n };
const event = { userId: r || "anonymous", name: t, params };
if (t === Events.ExtensionPageViewed) {
const batch = await loadBatch() || [];
batch.push(event);
if (batch.length > 100) batch.splice(0, batch.length - 100);
await saveBatch(batch);
if (batch.length >= BATCH_SIZE) { // flush once 3 page views have queued
const res = await postToGA(r || "", batch, s);
if (res.ok) await clearBatch();
return res.statusText || {};
}
return {};
}
return (await postToGA(r || "", [event], s)).statusText || {};
} catch (err) {
return { status: "error", statusMessage: "An error occurred while trying to track the event" };
}
})
}- www.google-analytics.com
Google's GA4 endpoint. Receives batched page-view events, URL, page title and product metadata, tagged with a persistent client ID, under Dupe.com's own Analytics property.