Is DeepL: translate and write with AI safe?
DeepL attaches a persistent installation ID and browser fingerprint to every analytics event it sends to its servers.
Each time the user interacts with the extension, a base analytics payload is assembled containing an installationId (a UUID stored in chrome.storage.sync that survives reinstalls), a sessionId, browserInstanceId, browser name, version, platform, language, and userAgent string. When the user is signed in, accountId and organizationId are added. A dapUid cookie read from deepl.com is included as webInstanceId, linking extension events to the user's DeepL website activity. All of this is transmitted to s.deepl.com/chrome/statistics with each analytics call.
Who publishes itDeepL - no other listings under this identity, 3 shared hostnames
DeepL - no other listings under this identity, 3 shared hostnames
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 3 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.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Persistent Installation ID Fingerprints Every Analytics Event
Every DeepL action, translating, opening the popup, changing settings, POSTs to s.deepl.com/chrome/statistics with a persistent installationId synced across devices, plus a device UUID, user-agent, platform.
Signed-in adds accountId.
You translate text, open the DeepL popup, or take any other action inside the extension.
The extension POSTs to s.deepl.com/chrome/statistics with a persistent install UUID, a device UUID, your user-agent, platform, language and, if signed in, your DeepL account ID.
If you have visited deepl.com in this browser, a dapUid cookie value is also attached, linking your extension events to your website session.
| Accept | application/json |
| Content-Type | application/json |
{
"eventId": 3,
"extensionVersion": "1.72.0",
"sessionId": "b2f1c3d4-7e8a-4b9c-a0d1-2e3f4a5b6c7d",
"sessionType": 2,
"installationId": "1aca7a42-9c0a-4e64-adc3-3052987062ec",
"browserInstanceId": "70e3f0aa-491a-4b3f-9457-09a55f5d4df5",
"originStore": "chrome",
"userInfos": {
"userType": 3,
"translatorServiceType": 0,
"apiServiceType": 0,
"docTranslatorServiceType": 0,
"writeServiceType": 0
},
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
"platform": "Linux",
"browser": "Chrome",
"browserVersion": "146.0.0.0",
"browserLang": "en-GB",
"clientExperiments": []
}| Field | Value | Why it matters | |
|---|---|---|---|
Your installation ID | 1aca7a42-9c0a-4e64-adc3-3052987062ec | A UUID assigned at install and stored in synced browser storage, following you across every device where your Chrome profile is signed in. | |
Your browser instance ID | 70e3f0aa-491a-4b3f-9457-09a55f5d4df5 | A second UUID stored locally in the browser. Identifies this specific browser installation independently of your account. | |
Session ID | b2f1c3d4-7e8a-4b9c-a0d1-2e3f4a5b6c7d | A UUID generated at the start of each browser session. Refreshed every 30 minutes of inactivity. | |
Your DeepL account ID | usr_8d4f2a1c9e7b3d5f | Sent when you are signed into a DeepL account. Links all events to your registered identity. | |
Website session link (dapUid) | dap_a9f3b2c1d4e5f678 | The dapUid cookie value from deepl.com, attached as webInstanceId, connecting extension activity to your deepl.com browsing history. | |
Full user-agent string | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 | Your browser's complete user-agent, including OS version, browser version, and rendering engine details. | |
Platform and browser language | Linux / en-GB | Your operating system platform and the language your browser is configured to use. | |
Extension version | 1.72.0 | Which version of the DeepL extension you have installed. Consistent across all events. |
The base analytics event assembly, every analytics call goes through this function.
// Called before every analytics event. Assembles a base event object
// with persistent identifiers, browser fingerprint, and account info,
// then POSTs it to s.deepl.com/chrome/statistics.
async function buildAndSendAnalyticsEvent(eventCustomizer) {
let installationId, sessionId, browserInstanceId, accountId, organizationId;
let userType, sessionType, translatorServiceType;
// Read persistent UUIDs from storage
const settings = await readFromSyncStorage(["session", "installationId", ...]);
sessionId = settings.session.id; // refreshed every 30 min
installationId = settings.installationId; // permanent, synced across devices
browserInstanceId = await readBrowserInstanceId(); // from chrome.storage.local
accountId = await readAccountId(); // DeepL account if logged in
const browserInfo = getBrowserInfo(); // userAgent, platform, browser, browserVersion, browserLang
const dapUidCookie = await chrome.cookies.get({ name: 'dapUid', url: 'https://www.deepl.com' });
const webInstanceId = dapUidCookie ? dapUidCookie.value : undefined;
// Base payload attached to EVERY analytics event
const baseEvent = {
eventId: undefined, // overwritten by each specific event
extensionVersion: '1.72.0',
sessionId,
sessionType,
installationId, // persistent UUID from install
browserInstanceId, // second persistent UUID
originStore: getOriginStore(chrome.runtime.id),
userInfos: {
...(accountId && { accountData: { accountId, ...(organizationId && { organizationId }) } }),
userType,
translatorServiceType,
...
},
userAgent: browserInfo.userAgent,
platform: browserInfo.platform,
browser: browserInfo.browser,
browserVersion: browserInfo.browserVersion,
browserLang: browserInfo.browserLang,
clientExperiments: await getExperiments(),
...(webInstanceId && { webInstanceId }), // deepl.com cookie value
};
const event = eventCustomizer ? eventCustomizer(baseEvent) : baseEvent;
return fetch('https://s.deepl.com/chrome/statistics', {
method: 'POST',
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
body: JSON.stringify(event),
credentials: 'omit',
});
}Assigned once at install, synced to every device on your profile. Attached to every analytics POST, linking all usage to one ID.
chrome.storage.sync key 'installationId'1aca7a42-9c0a-4e64-adc3-3052987062ec
- s.deepl.com
Receives every analytics event as a JSON POST to /chrome/statistics. Operated by DeepL SE (Cologne, Germany). No third-party sharing is described for this endpoint.
Translation requests send site context to DeepL analytics
Source-code analysis shows a completed translation builds an analytics event for DeepL's statistics endpoint: hostname, installation/session IDs, user type, language direction, character count and trigger.
Traffic tests couldn't capture it.
You request a translation from the extension.
This can be inline text translation or another translation flow that completes through the service worker.
The extension prepares a translation analytics event for DeepL.
The event combines the page hostname with installation, session, browser, language, and character-count fields.
| Field | Value | Why it matters | |
|---|---|---|---|
Page hostname | de.wikipedia.org | Shows which website was open when you translated text. | |
Installation ID | d154b8e5-5f28-434b-beae-e816fa0d50b3 | Lets DeepL associate translation events from the same browser extension install. | |
Session ID | session-20260712-8f4b2c | Groups activity from the same browser session. | |
User type | free account | Describes whether the extension sees the account as anonymous, free, or paid. | |
Translation details | German to English, 240 characters, inline translation | Records the language direction, amount of text, and how the translation was started. |
| Accept | application/json |
| Content-Type | application/json |
The content script supplies the hostname and the service worker posts the analytics event
EA.update({
translationState: v.ongoingTranslation,
translatedSnippet: "",
originalSnippet: a,
itaTranslateTrigger: n ? C : E
}), wH({
type: ty,
payload: {
sourceText: a,
targetLanguage: t,
domainName: window.location.hostname,
trigger: n ? C : E,
websiteLanguage: e.websiteData.websiteLanguage
}
})function $b(event) {
return r(this, void 0, void 0, function*() {
return Gb(base => Object.assign(Object.assign({}, base), {
eventId: BS.EVENT_ID_TRANSLATION_REQUEST,
translationRequestData: Object.assign({
extensionVersion: "1.93.0",
domainName: event.domainName,
userId: event.installationId,
translationTrigger: tE(event.trigger),
translationData: {
sourceLang: event.websiteLanguage,
targetLang: event.targetLanguage,
sourceLength: event.characterCount
}
}, event.translationPerformance && { translationPerformance: event.translationPerformance }, {
nicheIntegration: TE(event.nicheIntegration || Mn)
})
}));
});
}function Gb(buildEvent) {
return r(this, void 0, void 0, function*() {
const browser = Uo();
const clientExperiments = yield cR();
const baseEvent = Object.assign({
eventId: void 0,
extensionVersion: "1.93.0",
sessionId: sessionId,
installationId: installationId,
browserInstanceId: browserInstanceId,
userAgent: browser.userAgent,
platform: browser.platform,
browser: browser.browser,
browserVersion: browser.browserVersion,
browserLang: browser.browserLang,
clientExperiments
}, webInstanceId && { webInstanceId });
const event = buildEvent ? buildEvent(baseEvent) : baseEvent;
return fetch("https://s.deepl.com/chrome/statistics", {
method: "POST",
headers: { Accept: "application/json", "Content-Type": "application/json" },
body: JSON.stringify(event),
credentials: "omit"
});
});
}- s.deepl.com
DeepL statistics endpoint receiving translation analytics JSON.
Selected page text is sent to DeepL translation endpoints
Source-code analysis shows that translating selected, input, or page text has the content script collect it and pass it to the service worker, which forwards it to DeepL's JSONRPC path.
Traffic tests didn't capture a request.
You select text or ask the extension to translate page content.
The extension needs the source text to produce the translation result.
The extension sends the selected or collected page text to DeepL's translation service.
The request includes language settings and the page hostname.
| Field | Value | Why it matters | |
|---|---|---|---|
Selected or page text | Guten Tag, bitte ubersetzen Sie diesen Absatz. | Contains the text you asked the extension to translate, which may include personal or work content if that text is on the page. | |
Page hostname | de.wikipedia.org | Shows which website the translated text came from. | |
Target language | EN-US | Shows the language you requested for the translation result. | |
Character count | 240 characters | Records how much text was included in the translation request. |
| Content-Type | application/json |
The content script collects text and the service worker forwards it for translation
const y = wj(p.map(e => e.text));
v = o ? [yield kn({
action: fn.dlRequestImproveWriting,
payload: Object.assign(Object.assign({
requests: y.simplifiedHtml.map(e => ({ text: e })),
domainName: window.location.hostname,
websiteLanguage: f,
sourceLang: a
}, s && { isHtml: !0 }), { nicheIntegration: i })
})] : yield kn({
action: yn.dlRequestInputTranslation,
payload: {
requests: y.simplifiedHtml.map(e => ({ text: e })),
domainName: window.location.hostname,
websiteLanguage: f,
targetLang: r,
platformBehavior: i
}
});const QL = request => r(void 0, void 0, void 0, function*() {
return yield request.payload.isWriteRequest ? JL(request) : (request => r(void 0, [request], void 0, function*({ payload: { domainName, requests, targetLang, websiteLanguage, formality, platformBehavior, skipDapReporting } }) {
yield $N();
const { installationId, targetLanguageUserInput } = yield MN(["targetLanguageUserInput", "installationId"]);
const selectedTargetLanguage = targetLang || targetLanguageUserInput;
const response = yield DL({ sourceTexts: requests.map(e => e.text) || [], selectedTargetLanguage, formality });
const translatedTexts = response.processedTranslation.map(e => e.text);
const detectedSourceLanguage = response.processedTranslation[0].detected_source_language;
skipDapReporting || PN(() => $b({
installationId,
trigger: platformBehavior !== Mn ? D : C,
websiteLanguage: detectedSourceLanguage,
targetLanguage: selectedTargetLanguage,
characterCount: requests.reduce((sum, item) => sum + item.text.length, 0),
domainName,
nicheIntegration: platformBehavior || Mn,
glossaryTrackingInfo: response.glossaryTrackingInfo
}));
return [{ detected_source_language: detectedSourceLanguage, text: translatedTexts.flat().join(""), texts: translatedTexts }];
}))(request);
});- www2.deepl.com
DeepL free translation JSONRPC endpoint for extension translation requests.
- api.deepl.com
DeepL Pro translation JSONRPC endpoint for extension translation requests.
+1 more finding not shown
Where it sends data
Destinations our analysis observed DeepL 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.
- s.deepl.com
DeepL sends data to s.deepl.com. No other extension we have analysed sends data here.