SDK Data Collection

Exactly what the Capacitor plugin and Electron updater send from end-user devices, what is stored, how long, and what you must disclose.


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.

Capacitorcapacitor.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.

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.

  1. That you use a third-party OTA update service, naming DeployYourApp as a processor or service provider acting on your behalf.
  2. 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.
  3. The technical fields: platform, app version, SDK version, CPU architecture, and the dates of first and most recent contact.
  4. 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.
  5. 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.
  6. Retention. See below, and state your own policy.
  7. International transfers, if our hosting region is outside your users' region.
  8. 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.app with 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.


Previous
Authentication
Next
Incident Response