Technical & privacy FAQ
The deep answers — for engineers and DPOs
How the peer-to-peer architecture works, which interfaces your site talks to, and where every byte goes. If a question is missing, ask us — good questions end up on this page.
Architecture & data flow
What moves where — the part your DPO will read twice.
What travels through your servers — and what never does?
Two very different kinds of data are in play, and they take different routes. Session content — the mirrored page, everything the visitor sees or types, pointer movements, annotations, and the audio of a voice call — travels peer-to-peer between the visitor's and the agent's browsers over an encrypted WebRTC channel. It is never transmitted to, stored on, or readable by our backend.
Our serverless backend handles only what is needed to broker and manage sessions: the WebRTC handshake metadata (“signaling”: session descriptions and network candidates), short-lived support codes, session status records, your workspace and widget configuration, the permission ceiling, callback phone numbers, and your agents' account and availability data. That's the complete list — there is no session-content path to our infrastructure by design.
Is the live mirror a video stream?
No. We mirror the DOM, not pixels: the visitor's browser serializes the page once and then streams only mutations (a node changed, a class toggled, an input updated). The agent's browser rebuilds the real DOM from that stream.
That's why the agent gets a true second cursor and can click a specific element (targets are addressed by node identity, with coordinates only as a fallback), why masked fields can be excluded at the source rather than blurred after the fact, and why the channel stays light enough for mobile connections — diffs, not frames.
How are sessions encrypted?
The co-browsing data channel is encrypted with DTLS, and voice audio with DTLS-SRTP — both are mandatory parts of WebRTC, negotiated directly between the two browsers. All communication with our backend (bootstrap, signaling, dashboard) uses TLS.
There is no mode in which session content is exchanged unencrypted, and no server in the path that could decrypt it.
What happens when a direct peer-to-peer connection can't be established?
Restrictive NATs and corporate firewalls sometimes block direct connections. In that case the encrypted stream falls back to a TURN relay: the relay forwards packets it cannot decrypt — it never sees content in the clear, and it stores nothing. Relay credentials are short-lived and minted server-side per session, so they can't be harvested from the page.
Honest footnote: today's relay (Cloudflare) is globally routed. An EU-pinned relay, so the encrypted fallback provably stays in the EU as well, is on our roadmap and is a pure configuration change in our architecture.
Do browser voice calls go through your servers?
No. The two-way audio rides the same peer connection as the co-browsing channel — peer-to-peer, or through the encrypted relay described above. Our backend only stores the call's ring/answer metadata (who answered, when), which is deleted after 7 days. Not a byte of voice lands on our systems.
And softphone calls to a customer's phone?
The console's built-in softphone (Pro) dials over YOUR OWN provider — Telnyx, Twilio, or any WebRTC-capable SIP trunk. The call audio is a WebRTC stream between the agent's browser and your provider's edge, which bridges it into the phone network. Cobrowse stores only the connection settings you enter (Twilio's stronger secrets server-side only) — we are not in the media path of a single call, and no call records or recordings exist on our side.
That also means the telephony data processing (numbers dialed, call metadata, routing) happens under your existing contract with your provider — Cobrowse does not become a telecoms sub-processor.
Integration & interfaces
What your engineers will actually wire up.
How do I embed Cobrowse?
Two interfaces, one engine. The classic way is a single script tag: <script src="https://cobrowse.loggify.app/cobrowse.js" data-cb-key="cb_…" async></script> — paste it before </body> and the widget renders itself according to your dashboard configuration.
For SPAs and component-based stacks there is a Web Component: <cobrowse-widget api-key="cb_…">. It boots when mounted and tears down when unmounted — so an app that keeps it mounted keeps one session alive across client-side route changes.
Everything about the widget (colors, position, texts and languages, which features it offers) is configured in the dashboard, not in the markup — so a redesign never needs a deploy on your side.
Which endpoints does the snippet talk to?
On load the snippet calls one bootstrap endpoint, which returns your widget configuration, the permission ceiling, connection (ICE) configuration, and current availability. On interaction it calls exactly what the visitor asked for: minting a support code, filing a callback request, starting a browser call, or fetching short-lived relay credentials.
Every one of these endpoints enforces the same gate: your public app key must match, and the request's origin must be on your app's domain allow-list. A request from an unknown domain gets nothing — no widget, no codes, no relay credentials.
The WebRTC signaling itself goes directly to the managed database (Google Firestore) over TLS, not through a middleman endpoint — one reason the snippet needs no CORS or proxy configuration on your site.
Is the API key in the page a secret?
No — and it doesn't need to be. The key in data-cb-key is a public identifier, comparable to a Maps API key with a referrer restriction. The actual gate is the per-app domain allow-list: our backend pairs key and page origin on every request, so a key lifted onto a foreign domain is useless. Wildcard entries like *.example.com cover your subdomains.
Does a session survive page navigation?
Yes, two ways. In an SPA, keep the <cobrowse-widget> element mounted and the session simply continues across client-side routes — same peer connection, nothing to rejoin.
Across a full page load on the same origin (say, a shop handing over to a checkout on another path — even a different framework), the session resumes automatically without asking the visitor for consent a second time: the visitor's browser rejoins the running session, and the agent's console re-establishes the connection seamlessly. Cross-origin handover is on the roadmap.
Can I see it working before putting it on my site?
Yes. The public demo runs the real engine end-to-end in two browser windows — support code, consent, live mirror, rights enforcement. And with clone mode you can co-browse a sanitized copy of your own page (/demo/visitor?site=your-url) before installing anything.
Privacy & control
Consent, the permission ceiling, masking, retention.
When does mirroring start, exactly?
Only after the visitor explicitly consents. The visitor initiates (they request the support code, the callback, or the call), and when an agent connects, a consent card appears on their page — with the agent's real name and photo on it. Until they accept, nothing is recorded and nothing is transmitted. During the session the agent is visibly present on the page, and the visitor can end the session at any moment.
How is the permission ceiling enforced, technically?
Each app has a ceiling on a five-step trust ladder — see, point, guide (scroll), assist (type), act (click) — configured in the dashboard and stored server-side, never in your page markup. It is delivered to the visitor's browser at bootstrap, and the component that executes agent input runs on the visitor's side: it validates every incoming action against the ceiling before performing it.
The consequence: a capability you switched off is not policy, it's physics. The agent's console can request less, but nothing in the agent's browser can grant itself more — and editing the embed tag can't escalate it either, because the ceiling never lives there.
What is masked, and how does masking work?
Password fields are always masked — non-negotiable, not configurable. The characters never enter the mirror stream, so they cannot reach the agent, our servers, or anyone else. Everything else is visible by default (an agent guiding a form needs to see it), and you can exclude any element by adding the data-cobrowse-mask attribute or the cobrowse-mask class.
Masking is symmetric: a masked field is also unreachable for agent-side typing, so assistance stops exactly where visibility stops. The widget's own UI is never mirrored.
What do you retain, and for how long?
Session content: never stored, so nothing to retain. Everything else is operational metadata with an automatic expiry, hard-deleted on schedule: signaling handshake messages after 1 hour, support codes within minutes of expiry, availability heartbeats after 24 hours, browser-call records after 7 days, callback requests (they contain a phone number) after 30 days, and session records — participants, timing, status, never content — after 90 days.
Deletion is the default and needs no request. Long-lived data is only what you'd expect: your workspace, team, and app configuration.
Where is data stored, and who are your sub-processors?
Backend data lives in Google Cloud Firestore in the EU region (europe-west); the application and API run on Vercel; the encrypted relay fallback runs on Cloudflare. That's the complete sub-processor list — three names, each with EU–US Data Privacy Framework certification and Standard Contractual Clauses in place. The list, with what each processes, is part of the DPA (Annex 2) and changes are announced 30 days in advance.
Can Cobrowse staff watch a session?
No — and not because of a policy, but because there is nothing to watch. Session content exists only in the two participating browsers; our systems hold connection metadata and status records. Even with full database access, a session cannot be reconstructed or observed from our side.
Does the widget set tracking cookies?
No. The widget sets no tracking cookies and does no fingerprinting or analytics on your visitors. The only browser storage it uses is a session-scoped entry that keeps an already-consented session alive across a page load — strictly functional, gone when the tab closes.
Compliance & the DPA
The paperwork — as lightweight as the architecture allows.
Do you offer a Data Processing Agreement (Art. 28 GDPR)?
Yes, and the full text is public — you can send it to your DPO before ever talking to us. Signing is fully digital and takes under a minute: the workspace owner opens Settings → Data processing in the dashboard, enters the company's legal name, and signs by typing their full name (Art. 28(9) GDPR permits electronic form).
The signed record is stored immutably — pinned to the exact version signed, with signer, company, and timestamp — and every team member can view and print the countersigned copy at any time. Available on the Pro and Enterprise plans.
Why is your DPA so short?
Because the architecture does the heavy lifting. A processor that stores session recordings needs pages of annexes about them; we need one sentence — content never reaches our systems. What remains is honest, specific, and checkable against this FAQ: the metadata we do process, three sub-processors, a retention table where deletion is automatic, and the technical measures that are product features rather than promises.
Is Cobrowse suitable for regulated industries (banking, insurance, health)?
The building blocks are exactly what regulated support teams need: consent before anything, a capability ceiling enforced on the customer's side, source-level masking for sensitive fields, no session recordings anywhere, and automatic expiry of all metadata. Enterprise plans add custom terms and priority support; if your compliance team has specific requirements, talk to us — the architecture usually answers most of the questionnaire.
Convinced the architecture holds up?
See it run end-to-end in two browser windows — or put the widget on a copy of your own page.