How to Embed Third-Party Content Without Compromising User Privacy

May 16, 202511 min read
Editor Showing The Extension Code

The Problem With Modern Web Embeds

You've probably embedded Google Forms, payment processors, or social widgets into your applications dozens of times. It's standard practice, right? But there's a privacy problem hiding in plain sight.

Every third-party embed creates a potential data leak between your applications. Users interact with embedded content in one app, only to find their data mysteriously appears in another completely unrelated application. This happens because third-party cookies don't belong to your domain – they belong to the embedded service.

For developers building privacy-conscious applications or working in regulated industries, this creates serious compliance risks. Traditional solutions either break functionality or require changes you can't control.

This guide shows you a different approach: virtual browser sessions that isolate third-party cookies while maintaining full functionality. You'll see exactly how the tracking happens, why standard solutions fall short, and how to implement a bulletproof alternative.

Here's a Different Approach: Virtual Browser Sessions

Instead of fighting browser privacy restrictions or rebuilding every integration, you can isolate third-party cookies using virtual browser sessions with randomized domains.

This approach works by creating separate, sandboxed environments for embedded content. Each application gets its own virtual session with a unique domain. Third-party cookies remain functional within each session but can't leak between different applications.

Let's examine exactly how this isolation works and see a practical implementation using the Google Forms example that demonstrates the core privacy problem.

It’s completely valid to add third-party embeds to your site, but there’s a catch — cookies.

You have no control over the cookies a third-party embed sets or how it uses them later. That means you can’t guarantee they’re not tracking your users. Even if a given embed seems safe now, nothing stops it from changing behaviour later.

Consider a scenario where a Google Form is embedded into two distinct web applications, like this:

<iframe src="https://docs.google.com/forms/d/<form-id>/viewform?embedded=true"
         width="640" height="800"
         frameborder="0" marginheight="0" marginwidth="0">
</iframe>

The form is filled out— but not submitted — in one application. Then, it’s reopened in the other, and surprisingly, all the input is still there. The form’s state is preserved across both apps.

How Browsers Handle Third-Party Cookies

Because the cookies don’t belong to your app — they belong to Google in our case. More specifically, they’re tied to the docs.google.com domain and are treated by the browser as third-party cookies.

In practical terms, this means that even though the form’s cookies were created while the user interacted with the first web application, those same cookies were available in the second one. Why? Because browsers manage cookies by domain. As long as both apps embed content from docs.google.com, the browser reuses the same cookie data—across sites, without your control.

While this example shows cookies being used to retain form state, the same approach can be extended to tracking user activity across multiple websites.

Standard Privacy Solutions and Their Limitations

  1. Disable third-party cookies completely. The solution has a couple of drawbacks: a) it relies on user action — a web application can’t disable them on its own; b) the third-party embed may not function properly without access to its cookies.
  2. Clear cookies using Clear-Site-Data header (https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Clear-Site-Data). The main drawback of this approach is timing — it can be tricky to determine the right moment to trigger it. And importantly, it requires direct changes in the embed code.
  3. Ask a vendor of a third-party embed to use Cookies-Having Independent-Partitioned-State (https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/Privacy_sandbox/Partitioned_cookies). The obvious challenge here is that the change must be made in an external application you don’t control. On top of that, CHIPS is currently only supported in Chrome, limiting its effectiveness across browsers.

None of these solutions are perfect — but luckily, there’s a simpler and faster alternative!

Instead of embedding Google Forms directly, you can load them through a Webfuse Virtual Browser Session. This approach isolates third-party cookies and protects user privacy.

One way to launch such a session is by generating a magic link, which creates a fully virtualized session with its own randomized domain.

To do this:

  • a Webfuse Space has to be created;

  • REST API key has to be generated;
  • the option called URL mangling should be turned on;

After all these actions are done a magic link is ready to be created.

Now, use the same magic link in both web applications to launch the Google Form inside Webfuse Virtual Browser Sessions. Each app will open the form within its own isolated, randomized domain — ensuring third-party cookies from docs.google.com remain sandboxed and can't leak between sessions.

This way, even though both apps embed the same form, the form’s state — and its cookies — stay separate. No cross-app tracking. No surprises.

<iframe src="https://webfuse.com/+external\_content\_embedded/?magic\_link=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzcGFjZV9pZCI6MSwibmFtZSI6IkpvaG4gRG9lIiw iZW1haWwiOiJqb2huZG9lQGV4YW1wbGUuY29tIiw iY2FuX2hvc3QiOnRydWUsInVybHMiOlsi aHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20iXX0.y07dLzacYKz9PLJL34RZngBvLXRDhQoVvsJyJq-Zevs"
        width="640" height="800"
        frameborder="0" marginheight="0" marginwidth="0">
</iframe>

Voilà!

As mentioned earlier, this behavior is possible because Webfuse modifies and randomizes the domain of the embedded Google Form when URL Mangling is turned on. This prevents the browser from treating the form as a third-party embed tied to docs.google.com.

Google forms executed in the first session got the following domain:

https://83bb438c4d8890efc1182bafff61deb33e5d3c8f-m.webfuse.com

and the following domain in the second session:

https://98c7608284f4b3f344a517e09b67bd2b4ebdb89a-m.webfuse.com

Although the cookies set within virtual browser sessions are considered third-party, they aren’t shared across sessions — because each one runs under a unique, randomized domain. This isolation ensures that the form state in one session isn’t accessible in another.

So, it was shown that using Webfuse a user can not be identified by tracking third party cookies, but there are other ways to identify a user, for example checking various fingerprints, such as User-Agent, TLS, canvas rendering and many others.

To help mitigate these risks, Webfuse offers several privacy-focused features:

  • It can set a default User-Agent string for all sessions.
  • It can block access to detailed information about the client’s device.
  • It can overwrite or strip certain HTTP request headers to reduce fingerprinting surface.

But not everything can be masked and if a user’s interests on a web are revealed then web pages might start spamming with advertisements. In this case Webfuse can offer an extension which blocks both ads and analytical tools.

Webfuse extensions are similar to browser extensions although they are not the same.

Creating Custom Extensions for Enhanced Privacy Control

Let’s consider how a simple Webfuse extension can be created for filtering ads out.

Extension creation starts with creating a `manifest.json` file.

{
  "manifest_version": 3,

  "name": "AdBlock filtering extension",

  "version": "1.0",

  "content_scripts": [{
    "js": ["inject_adblock.js"]
  }],

  "background": {
    "service_worker": "adblock_bkg.js"
  }
}

where inject_adblock.js is JS which is injected into an existing web application when it’s loaded and adblock_bkg.js is a JS worker which contains ads blocking logic.

Since the extension has to intercept HTTP requests and as there is the only way to do that from a web-page is to use Service Worker, therefore that’s exactly what inject_adblock.js does.

navigator.serviceWorker.register('/static/cobro/extensions/adblock.js', { scope: '/' })
  .then(() => {
    console.log('Adblock service worker registered');

    // A service worker message listener
    navigator.serviceWorker.addEventListener('message', function({ data }) {
      const { type } = data;

      // A service worker sends a message to check URL
      if (type === 'check_url') {
        console.log('< CHECK URL: content', data.uuid, data.url);
        // A check is re-dispatched to a background worker
        browser.runtime.sendMessage(data);
      }
    });

    browser.runtime.onMessage.addListener((message, sender) => {
      // A check result is received from a background worker
      if (message.type === 'check_url') {
        console.log('> CHECK URL: content', message.uuid, message.result);
        // A service worker gets informed about a check result
        navigator.serviceWorker.controller.postMessage(message);
      }
    });
  })
  .catch(error => {
    console.log('Adblock service worker failed: ', error);
  });

The background worker adblock_bkg.js uses the https://github.com/talmobi/ad-block-js library andhttps://easylist.to/ list to match ads URLs.

import rules from './adblock-rules.js'; // EasyList

// ad-block-js

...

// Create adblock filter

const adblock = create();

rules.forEach(rule => adblock.add(rule));

browser.runtime.onMessage.addListener((message, sender) => {
  if (message.type === 'check_url') {
    const { uuid, url } = message;
    console.log('< CHECK URL: background', uuid, url);
    // Check URL
    browser.tabs.sendMessage(null, { type: 'check_url', uuid, result: adblock.match(url) });
  }
});

where 'adblock-rules.js' contains EasyList rules:

export default [
  "[Adblock Plus 2.0]",
  "-ad-manager/$~stylesheet",
  "-ad-sidebar.$image",
  "-ad.jpg.pagespeed.$image",
  "-ads-manager/$domain=~wordpress.org",
  "-ads/assets/$script,domain=~web-ads.org",
  "-assets/ads.$~script",
  ...
];

The service worker adblock.js intercepts HTTP requests and sends a message to a background worker to check a URL.

let uuid = 0;
const requestEvents = {};

self.addEventListener('activate', event => {
  console.log('ACTIVATE');
  event.waitUntil(self.clients.claim());
});

self.addEventListener('message', event => {
  const { data } = event;
  if (data.type === 'check_url') {
    const { uuid, result, url } = data;
    const requestEvent = requestEvents[uuid];
    delete requestEvents[uuid];

    console.log('< CHECK URL, service_worker:', uuid, result);
    if (result) {
      console.log('REQUEST blocked: ', url);
      const newResponse = new Response('This is a custom response!', {
        status: 403,
        statusText: 'Forbidden',
        headers: { 'Content-Type': 'text/plain' },
      });

      // Respond with the new response
      requestEvent.respondWith(newResponse);
    } else {
      requestEvent.respondWith(self.fetch(requestEvent.request));
    }
  }
});

self.addEventListener('fetch', event => {
  const { url } = event.request;
  console.log('FETCH', url);
  requestEvents[++uuid] = event;
  self.clients.matchAll().then(clients => {
    clients[0].postMessage({ type: 'check_url', uuid, url });
  });
});

Now it’s time to register the extension in a Webfuse Space.

Now let’s execute, for example cnn.com in a Webfuse session and check that requests will be blocked.

Implementation Considerations and Performance Impact

So, as it was shown the extension works and does its job, but let also summarise pros and cons of this approach.

Pros:

  • The extension is installed per a Webfuse session and not per a browser. When a session is started the extension is already in place and no user interaction is needed to install it, which would be the case if it was a browser extension.

Cons:

  • The service worker can be registered from a web-page itself therefore the extension has to inject JS into the page and initialise the service worker every time the page is loaded.

Related Articles