HotupdaterHot Updater
React Native API

Insights Plugin

Report launches, downloads, applies, recoveries, and update failures to your server's Insights with the built-in `insights()` client plugin.

Add the plugin

insights() is a client plugin exported from @hot-updater/react-native. It reports nothing until you add it to plugins in HotUpdater.init; the instance init returns then has hotUpdater.insights:

src/hotUpdater.ts
import { HotUpdater, insights } from "@hot-updater/react-native";

export const hotUpdater = HotUpdater.init({
  baseURL: "<your-update-server-url>",
  requestHeaders: {
    "x-api-key": "<your-api-key>",
  },
  plugins: [insights()], 
});

It sends each report to POST /events under baseURL, with the requestHeaders and requestTimeout of init, to a server that runs the insights() server plugin, as the managed servers do. The Insights guide covers verifying reports and reading them.

Options

OptionTypeDefaultDescription
debugbooleanfalseAlso report from debug builds, where __DEV__ is true.

A debug build sends nothing by default, so development sessions stay out of production Insights. To test reporting from one, create the plugin with insights({ debug: true }). The option has no effect in a release build.

Set the user

Attach your app's ID for the signed-in user to later reports:

import { hotUpdater } from "./hotUpdater";

// After sign-in, or when the app restores a session:
hotUpdater.insights.setUser({ userId: "user-123" });

// On sign-out:
hotUpdater.insights.setUser(null);

setUser stores the ID on the device, and every later report carries it as userId until setUser(null) clears it. It sends nothing by itself; a session restored right after init is still on the first launch report. A number becomes a string, and an empty ID clears the user. A new user counts as a change for launch reports; the installation ID stays the same.

In the Console, Insights → All events searches by user ID or installation ID. Choose an ID that fits your data policy; see Security.

What it reports

ReportSent when
UNCHANGEDThe app launched without applying or recovering an update, or an update check found nothing to install.
UPDATE_DOWNLOADEDThe SDK downloaded, verified, and staged a bundle. The app runs the previous bundle until it restarts.
UPDATE_APPLIEDA staged update became active at launch.
RECOVEREDNative crash recovery rolled back a failed update at launch.
UPDATE_FAILEDAn update check, download, or install failed.

A same-bundle Release adoption, which needs no download, is also reported as UNCHANGED, with the Release it replaced as fromReleaseId.

init reports the launch once native launch verification finishes. Reports of checks, downloads, and failures always follow the launch report of their JavaScript runtime.

Downloads, applies, and recoveries are always sent, unless reporting is paused. Launch reports and failures follow the rules below.

Launch reports

An installation sends UNCHANGED at most once per UTC day. A later launch or update check on the same UTC day sends nothing, unless the channel, app version, bundle, Release, or user ID changed since the day's last report, or a download or failure report reached the server since then.

An UPDATE_APPLIED or RECOVERED report counts as that day's report for the bundle it started. After a download, the same JavaScript runtime sends no UNCHANGED, so the server keeps showing the update as waiting to apply. A launch report the server did not store, such as one that failed its retries, is sent again at the next launch or check.

The server applies the same rule, so Console launch counts are daily active installations. See how the server counts reports.

HTTP responses

The plugin keeps the latest catalog or artifact response (200, 304, or an error) and attaches it to the next report it sends anyway, as metadata.httpResponse. It sends no extra request or report, and keeps only one response per app version and channel, so it is a snapshot, not a request log.

The snapshot has resource, path, status, the raw body (up to 4 KiB, with bodyTruncated), and receivedAtMs. An empty body is "", and one that could not be read is null. Headers, query parameters, native file downloads, and Insights' own requests are left out.

Update failures

UPDATE_FAILED says where and why an update failed. It carries the onUpdateError details the SDK knows:

DetailValues
stagecheck: the Release Catalog request. download: transferring and verifying files. install: patching, extracting, and moving them into place.
reasonnetwork, http, invalid_response, hash_mismatch, signature, patch, extract, storage, or unknown.
errorMessageThe original error message. JSON-encoded text is capped at 2 KiB.
errorStackThe original stack trace, when available. JSON-encoded text is capped at 4 KiB.
resourcecatalog, artifact, manifest, file, patch, or archive.
httpStatusThe response status of an http failure.
originCodeThe storage origin's error code of an http failure, such as AccessDenied, NoSuchKey, or ExpiredToken.
transportWhy no response arrived: timeout, dns, tls, connection, offline, or cancelled.
  • An installation reports each combination of stage, reason, target bundle, and error message at most once per UTC day.
  • The report's eventId is derived from the installation, the UTC day, and that combination, so a server that stores reports by ID keeps a repeated failure once.
  • A check that failed with a network error is not reported: the device was most likely offline.
  • A failure still waiting to be sent, or being retried, is dropped once the same runtime reports UPDATE_DOWNLOADED or UPDATE_APPLIED for the same target bundle.

A check that finds no Release for its scope, a bundle in the device's crash history, and a selection that a newer check replaced are not update failures, so the plugin reports none of them.

The Console shows the original message, an expandable stack, and the latest server response beside it. These are errors the device saw, not server logs. Error text is not redacted; only oversized text is cut, with an ellipsis.

Delivery

Reporting never delays startup or an update, and it does not trigger rollback. A report that fails with a network error, a timeout, 429, or a 5xx is retried in the background: up to three attempts in all, about 1 second and then 2 seconds apart, or after the server's Retry-After (at most 30 seconds). Each attempt has its own requestTimeout, and every attempt carries the same eventId, a UUIDv7, so the server counts a retried report once. A report refused with another status, such as 400, is not retried.

Reports leave one at a time in the order the app made them, so a retry never lands after a later report. The plugin logs a warning when a report cannot be delivered. Retries live in memory: a report still pending when the app closes or reloads is lost.

When the server has Insights off

A server without the insights() server plugin has no POST /events route and answers 404. After that answer, the plugin sends nothing more in that JavaScript runtime, and nothing for 24 hours. Reports made during the pause are dropped, not sent later.

A later JavaScript runtime can send reports once at least 24 hours have passed since the 404. Once the server stores a report, reporting continues as usual. Enabling insights() on the server does not require an app update, but the runtime that received the 404 must restart before reporting resumes.

Report format

Each report is one JSON body. This failed download shows every top-level field:

{
  "eventId": "01929f4e-2b7c-7a51-9d3e-5c1f0a6b8e21",
  "type": "UPDATE_FAILED",
  "installId": "5f1c2a9e-7b3d-4c8e-a1f0-2d6b9e4c7a13",
  "userId": "user-123",
  "platform": "ios",
  "appVersion": "1.4.0",
  "channel": "production",
  "cohort": "412",
  "fingerprintHash": null,
  "sdkVersion": "1.0.0",
  "minBundleId": "01929d00-0000-7000-8000-000000000000",
  "fromBundleId": "01929e11-6c2d-7f40-b8a5-3e7d1c9f0a42",
  "fromReleaseId": "01929e11-7a90-7c3e-9f21-8b4d2e6a1c57",
  "toBundleId": "01929f2a-1d5e-7b8c-a3f6-4c9e2b7d0f18",
  "toReleaseId": "01929f2a-2e71-7d4a-8c05-6f1b3a9e2d64",
  "updateStrategy": "appVersion",
  "metadata": {
    "failure": {
      "stage": "download",
      "reason": "http",
      "resource": "file",
      "httpStatus": 403,
      "originCode": "ExpiredToken"
    }
  }
}
FieldValue
userIdThe ID from setUser; absent without one.
minBundleIdThe bundle identifier embedded in the native build.
fromBundleId, fromReleaseIdThe bundle and Release the app ran before the transition; for UPDATE_FAILED, the ones it runs. For UNCHANGED, fromBundleId is null, and fromReleaseId is the Release a same-bundle adoption replaced, or null.
toBundleId, toReleaseIdThe bundle and Release the transition moved to; for UNCHANGED, the ones the app runs; for UPDATE_FAILED, the update's target, or the running bundle for a failed check. toReleaseId is null without a Release.
updateStrategyappVersion or fingerprint; null for UNCHANGED.
metadataOptional httpResponse snapshot for any report, plus details by report type below.
Reportmetadata
UPDATE_DOWNLOADEDdelivery: patch, manifest, or archive, as in onBundleDownloaded; patchFallback: true when a patch was tried but a file or the archive was downloaded instead.
UPDATE_FAILEDfailure: stage and reason, plus errorMessage, errorStack, resource, httpStatus, originCode, and transport when known.

See how the server accepts reports for its size limit and status codes.

Turn reporting off

Leave insights() out of plugins. The app then sends no Insights requests; native launch verification and crash recovery still run. Reports the server already stored stay there.

On this page