This page describes exactly what the DeployYourApp Capacitor plugin
(@deploy-your-app/capacitor-update-manager) and Electron updater
(@deploy-your-app/electron-update-manager) transmit from your end users' devices, and what the
server does with it.
It is written for developers completing a privacy policy, an app store privacy questionnaire, or a DPIA. Everything here describes what the shipped code does. Where a field is transmitted but discarded, or a capability does not exist, it says so.
Short version: the SDK sends a random device UUID and a set of version strings on every
update check, plus batched analytics events. Analytics is on by default, and the update
lifecycle events are automatic on every platform. Set analyticsEnabled: false to turn it off.
Roles
- You are the data controller for your end users' data.
- We are your processor. We handle this data on your instructions under our Data Processing Agreement.
- We do not sell this data, do not use it for advertising or profiling, do not train models on it, and do not combine it across customers.
Update check — POST /api/update
Sent on app launch and on the checkInterval timer (default 600 seconds), unless you disable
automatic checks.
Fields the server stores
These are written to the device record and are visible to you in the dashboard.
| Field | Example | Where it comes from |
|---|---|---|
deviceId |
9f1c8b2e-... |
Random UUID generated by the SDK on first run and persisted locally. Not an IDFA, GAID, ANDROID_ID, MAC address, or hardware serial, and not derived from any of them |
platform |
android, ios, electron-windows, electron-macos, electron-linux |
Build target |
nativeVersion |
1.4.0 |
The installed app binary version, stored as the device's app version |
pluginVersion |
0.1.0 |
The SDK version |
arch |
arm64, x64 |
CPU architecture, where the SDK reports it (Electron) |
| First seen / last seen | timestamps | Set by the server on first contact and on every check-in |
| Channel subscription | production |
A row linking the device to the channel it checked against |
The device's IP address terminates at our server. It is used for rate limiting and appears in operational logs. It is not written to the device record.
Fields transmitted but discarded
The server validates the request body against a strict Zod schema. Anything not in that schema is stripped before any database write.
| Field | Sent by | Example | What happens to it |
|---|---|---|---|
currentVersion |
iOS, Android | 1.4.2 |
Used in-request to decide whether an update applies, then discarded |
currentBundleId |
iOS, Android | builtin |
Discarded |
nativeBuild |
iOS, Android | 142 |
Discarded |
customId |
iOS, Android | "" |
Discarded. setCustomId() persists the value on-device, but the server does not store it |
osVersion |
Android only | 14 |
Discarded |
device |
Android only | Google Pixel 8 |
Discarded. Manufacturer and model are stripped the same way |
The device table has columns named osVersion, locale, customId, and currentBundleId.
Nothing in the current server build writes them, so they stay null. They appear in the device
CSV export as empty values. If a future release begins storing any of them, this page and the
privacy policy will be updated first.
What the SDK never collects
Precise or coarse geolocation. Advertising identifiers. Contacts, calendar, photos, camera, or microphone. Phone number, IMEI, or SIM data. Installed-app lists. Browsing history outside your app. The contents of your app's own data storage. The SDK requests no runtime permissions for any of this because it does none of it.
Analytics — POST /api/stats
Analytics is enabled by default. analyticsEnabled defaults to true on iOS, Android, web,
and Electron. Events are buffered on device and flushed in batches — every 20 events
(analyticsBatchSize) or every 30 seconds (analyticsFlushInterval), whichever comes first, up
to 100 events per request.
What each event contains
| Field | Description |
|---|---|
eventType |
A string, for example page_view, error, or update_download_complete |
eventData |
An arbitrary JSON object. Its contents are entirely determined by your app |
bundleVersion |
The bundle version active when the event was recorded, or builtin |
timestamp |
ISO 8601, recorded on device |
| device | Associated with the same deviceId as the update check |
Events whose type starts with update_ are additionally written to a structured table holding
the bundle ID, deployment ID, failure reason, download duration in milliseconds, byte count,
error message, and version.
eventData is your responsibility
This is the single most important line on this page. eventData is passed through to our
database as-is. Whatever your app puts in it, we store. If you call
trackError({ message, stack }) with a stack trace containing a user's email address, that email
address is stored. If you call trackPageView({ path: '/patients/12345' }), that path is stored.
Before shipping, audit every trackEvent, trackPageView, and trackError call for personal
data, and strip or pseudonymise anything you do not intend to send.
Automatic events
The Capacitor plugin on iOS and Android, and the Electron updater, all emit the same ten
update lifecycle events automatically whenever analyticsEnabled is true. You do not have to
call anything for these to be sent.
| Event | Emitted when |
|---|---|
update_check |
An update check starts |
update_available |
The server returns a bundle |
update_download_start |
Download begins |
update_download_complete |
Download finishes |
update_download_fail |
Download fails |
update_verify_pass |
Checksum and signature checks pass |
update_verify_fail |
A checksum or signature check fails |
update_apply |
A bundle is activated |
update_app_ready |
The app signals it started successfully on the new bundle |
update_rollback |
The device reverts to the previous bundle |
Their eventData contains version strings, bundle and deployment identifiers, byte counts,
durations, and failure reasons. No user content.
The web/PWA implementation of the plugin is the exception: it sends only the events your app
passes to trackEvent(), trackPageView(), or trackError().
On-device storage
The SDK persists a small amount of data locally, inside your app's own sandbox. This is first-party storage, not a cookie, and not shared with any other app.
| Value | iOS (UserDefaults) |
Android (SharedPreferences) |
Web / PWA (localStorage) |
|---|---|---|---|
| Device identifier | dya_device_id |
dya_device_id |
dya_device_id |
| Selected update channel | dya_channel |
dya_channel |
dya_channel |
| Custom ID, if you set one | dya_custom_id |
dya_custom_id |
dya_custom_id |
| Version override, if set | dya_version_override |
dya_version_override |
dya_version_override |
| Bundle metadata | dya_current_bundle, dya_next_bundle, dya_previous_bundle, dya_bundle_meta, dya_launch_attempts |
app preferences | not applicable |
| Downloaded bundle files | app data directory | app data directory | not applicable |
The iOS plugin ships a privacy manifest (PrivacyInfo.xcprivacy) declaring UserDefaults access
with reason code CA92.1 and NSPrivacyTracking set to false. Its NSPrivacyCollectedDataTypes
array is currently empty, so it does not itself declare the device identifier — your app's own
privacy manifest and App Store declaration must cover that.
Note for EU/UK apps: storing an identifier on a user's device engages Article 5(3) of the ePrivacy Directive as implemented locally. Whether you need consent depends on whether the storage is strictly necessary for a service the user requested. Update delivery is a reasonable candidate for the strict-necessity exemption; analytics generally is not. Take your own advice on this.
Turning analytics off
Analytics is opt-out, not opt-in. One config key disables it.
Capacitor — capacitor.config.json:
{
"plugins": {
"DeployYourApp": {
"appId": "com.example.app",
"analyticsEnabled": false
}
}
}
Electron:
import { ElectronUpdateManager } from '@deploy-your-app/electron-update-manager';
const updater = new ElectronUpdateManager({
appId: 'com.example.app',
analyticsEnabled: false,
});
With analyticsEnabled: false, no events are buffered, the flush timer never starts, and
POST /api/stats is never called — including the automatic update lifecycle events. Update
checks and update delivery are unaffected.
Runtime consent
There is no runtime toggle API. analyticsEnabled is read from configuration at
initialisation. If your app needs per-user consent, gate construction or configuration on the
stored consent value.
Disabling update checks entirely
Set autoUpdate: false and checkInterval: 0, and never call checkForUpdate(). The SDK then
contacts our servers only when you explicitly ask it to.
What to disclose in your privacy policy
If you ship our SDK, your own privacy notice should cover the following. Adapt the wording; do not copy it verbatim without checking it against what your build actually enables.
- That you use a third-party OTA update service, naming DeployYourApp as a processor or service provider acting on your behalf.
- The device identifier. A randomly generated identifier stored on the device to deliver the correct update to it. State that it is not an advertising identifier and is not used for tracking across apps or sites.
- The technical fields: platform, app version, SDK version, CPU architecture, and the dates of first and most recent contact.
- Analytics, if you leave it enabled. Cover both the automatic update lifecycle events and
the categories of event data your app sends. Only you know what is in
eventData. - Your legal basis for each purpose. Update delivery and security patching commonly sit under contract or legitimate interests; analytics commonly requires consent in the EU/UK.
- Retention. See below, and state your own policy.
- International transfers, if our hosting region is outside your users' region.
- How users exercise their rights — they contact you, not us. We forward any request that reaches us directly and will not answer it on your behalf.
For app store questionnaires (Apple privacy nutrition labels, Google Play Data safety), the SDK's baseline contribution is a Device or Other ID, collected and linked to the app installation but not to a user identity, used for App Functionality. If you leave analytics on, add Diagnostics and, if your events carry them, Crash Data and Product Interaction. Declare data collected rather than tracked: we do not link it to third-party data for advertising.
Retention, deletion, and export
- Retention. Device records and analytics events are kept for the life of your organization. There is no automatic expiry job.
- Deleting an app does not delete them. App deletion is a soft delete: the app stops serving updates and disappears from the dashboard, but its device and analytics rows remain.
- The automated erasure path is organization deletion. A deleted organization is purged in full — devices, analytics events, and stored bundle objects — 183 days (approximately 6 months) after deletion. The purge removes every bundle object from storage before deleting the organization row, and defers the row deletion if any object could not be removed.
- Per-device deletion is not self-serve. To action an individual end user's erasure request,
email
info@deployyour.appwith the device identifier and we will delete the records manually. - Export. Device records and their update history export to CSV from the dashboard and the
API, covering access and portability requests. Account holders can additionally export
everything we hold on them from
GET /api/me/export. - Getting the device identifier. Call
getDeviceId()in your app and surface it in a support or settings screen. Without it we cannot locate a specific user's records — the identifier is a random UUID and we hold no mapping to a real-world identity.
Security of the data in transit and at rest
- All SDK traffic is over HTTPS.
- Bundles are verified on-device by SHA-256 checksum, and by RSA signature when you configure a
publicKey. See Code signing. - With bundle encryption enabled, payloads are encrypted on your machine before upload; we store only ciphertext and cannot decrypt it. See Bundle encryption.
- The device endpoints are rate limited: 60 update checks and 100 analytics requests per minute, keyed by IP address for unauthenticated device traffic.
We hold no SOC 2, ISO 27001, PCI DSS, or HIPAA certification or attestation. Do not represent to your own customers that we do.