Documentation Index
Fetch the complete documentation index at: https://setup.despia.com/llms.txt
Use this file to discover all available pages before exploring further.
Android runtime now supports Kotlin
Despia Android now runs on both Java and Kotlin. Previously, the Android layer was Java-only, which blocked integration with modern SDKs that require Kotlin.Adding Kotlin support alongside the existing Java runtime removes that constraint and allows Despia Android builds to integrate with the growing set of SDKs that depend on Kotlin.No dashboard configuration changes are required. Developers integrating Kotlin-based SDKs should rebuild their app to pick up the updated runtime.Vision OCR
Despia apps can now run optical character recognition on any image using the device’s native text engine, returning the extracted text straight to your web app. Recognition happens entirely on-device, so it works offline and never sends image data anywhere, with no server round-trips and no third-party API keys.What ships today:- Multiple image sources. Recognize a hosted HTTPS image, a
data:URI, a raw base64 string, a photo the user picks, or a multi-page document scan, all through one capability. - Native pickers. The
@imagepickertoken opens the system photo library and the@filepickertoken opens an image-filtered file browser, with the selection flowing straight into recognition. - Document scanning. The
@documentscannertoken opens the native document camera with edge detection and perspective correction, recognizing every captured page and returning the combined text in one result. - Structured results. Each result delivers the full text plus a per-line breakdown through the
window.onVisionEventcallback. Lines carry a confidence score on iOS where the engine exposes one. Output is normalized so each line is trimmed and blank runs collapse before delivery. - Parallel recognition. Jobs run independently and are correlated by a caller-supplied
id, so several images can be processed at once. - Script hints. An optional language hint constrains recognition to a known script for non-Latin text, though both platforms auto-detect by default.
despia-native package and requires a fresh native build to pick up the new capability. No dashboard configuration or integration credentials are required.OneSignal push routing and metadata
Push notifications can now deliver a client-side route and arbitrary metadata to the web layer, enabling deep-link navigation and state restoration on notification tap without a full WebView reload.What ships today:- Path-based routing. Send a
pathkey in the OneSignaldataobject (e.g./account/orders/4567?tab=tracking). Despia applies the route viapushStateand firespopstateon notification tap. Most SPA routers react to thepopstateevent and navigate automatically with no additional code required on the web side. - Metadata delivery. Send a
metadatakey in thedataobject with any JSON value. The payload is delivered towindow.onNotificationEventas an object when sent via the REST API, or as a string requiringJSON.parsewhen set via the OneSignal dashboard Additional Data fields. Use it to restore app state on open. window.onNotificationEventhook. For routers that do not react to a syntheticpopstate, or when usingmetadata, definewindow.onNotificationEventat the top of your entry bundle. The host app calls it on every notification tap after applying the URL change. If the handler is not defined when the event fires, the event is lost and will not be replayed.- Legacy
data.urlbehavior unchanged. Pushes that send onlydata.urlcontinue to trigger a full WebView navigation. They now also firewindow.onNotificationEventwithurlin the payload, which is a no-op if the handler is not implemented.
path and metadata to your OneSignal data object to use the new behavior.PostHog analytics bridge
Despia now ships a native PostHog integration, exposing event capture, user identification, group analytics, screen tracking, feature flags, and GDPR consent controls throughdespia() schemes and four injected globals. Analytics run inside the native PostHog SDK, with batching, persistence, and delivery handled outside the WebView.What ships today:- Event capture. The
posthog://capturescheme fires named events with optional URI-encoded JSON properties. Device context (OS, app version, screen size) is attached automatically by the native SDK, and your keys take precedence on any conflict. - Identity and aliasing. The
posthog://identifyscheme links the device to your internal user ID and stores person properties on the PostHog person record. Theposthog://aliasscheme merges anonymous pre-signup history into the identified profile in a single call. - Group analytics. The
posthog://groupscheme associates subsequent events with an organization, team, or workspace for B2B funnels. Group types are created automatically on first use, up to PostHog’s hard limit of five per project. - Screen tracking. The
posthog://screenscheme sends a$screenevent with the provided name, suitable for route-change tracking. - Super and person properties. The
posthog://registerandposthog://unregisterschemes manage session-level super properties appended to every captured event. Theposthog://set_person_propertiesscheme updates the person record without capturing an event. - Feature flags via injected globals. The
despia.postHogFlagsglobal holds all evaluated flag values, keyed by flag name, and updates in place whenever PostHog pushes new values. Reads are synchronous, always current, and require nodespia()call. - Injected identity globals.
despia.postHogDistinctId,despia.postHogSessionId, anddespia.postHogOptedOutare kept live on thedespiaobject and update in place after every relevant scheme fires. - GDPR consent controls. The
posthog://opt_outandposthog://opt_inschemes act as the SDK-level consent cutoff. Events fired while opted out are dropped before they reach PostHog servers, and the cutoff persists across sessions. - Reset on logout. The
posthog://resetscheme clears the distinct ID, person properties, super properties, group associations, and session in a single call.
PK Pass / Wallet
Thewallet://pkpass command is now available, letting your app present the native wallet sheet and add a .pkpass file to the user’s device wallet.What ships today:- Pass sheet trigger. Call
despia('wallet://pkpass?url=...')with a publicly hosted HTTPS URL pointing to a.pkpassfile. The runtime fetches the file and presents the native add-to-wallet sheet. - Callback payloads. Define
window.onWalletEventbefore thedespia()call to receive the outcome. Payloads reportpresented,dismissed, andfailedstates, with typederrorstrings for download failures, invalid passes, and device restrictions.
Expanded Clerk Auth: SSO, MFA, password reset, SMS, magic links, and more
The native Clerk Auth bridge gains a wave of new sign-in methods and a handful of upgrades to existing flows. Everything works identically on iOS and Android.New sign-in methods:- Enterprise SSO. Route the user to their organization’s SAML or OIDC identity provider based on email domain. Returns a normal sign-in or sign-up event depending on whether the user is new to the organization.
- MFA continuation. Continue a sign-in whose first-factor result came back needing a second factor. Supports TOTP and backup codes via single-call verify, plus a prepare step for SMS or email second factors.
- Password reset. Three-step reset flow: send a code, verify the code, set a new password. Works with both email and phone identifiers, the bridge picks the right delivery channel.
- SMS one-time code. Passwordless sign-in via a one-time code sent over SMS. Phone numbers are normalized to E.164, so human-formatted numbers like
+1 (555) 123-4567are accepted directly. - Magic link tickets. Native equivalent of an email magic link. Your backend mints a one-time ticket through Clerk’s API and includes it in an email or push that deep-links into the app, the app forwards the ticket to the bridge to complete sign-in.
- Username and phone sign-in. Password sign-in now accepts any identifier: email, username, or phone. Pass
jane,jane@example.com, or+15551234567and the bridge resolves it. - Sign-up without email. Password sign-up no longer requires an email address. At least one of email, username, or phone is enough, alongside the password. Username-only or phone-only accounts are valid.
- Email code sign-up. The email code flow now supports new-account creation, not just sign-in. Optional first name, last name, and username can be passed when sending the code.
- Auth Sheet and User Profile emit close events. The prebuilt sign-in sheet now fires a completion event when it closes, carrying
userIdandemailAddressif the user signed in, or a dismissed event if the sheet closed without sign-in. The User Profile sheet fires a closed event on dismiss. Apps no longer need to poll the session state after the sheet closes. - Shared pending-flow state. Email code, phone code, password reset, and MFA continuation all hold one in-memory pending sign-in at a time. Starting a new flow cancels any other in flight, and calling a later step before its prerequisite returns a missing-state error.
- Unified completion semantics. Every flow that reaches a successful completion now follows the same pattern: the result payload carries
userId(plusemailAddressif a primary email exists), the JWT refresher starts, attribution fires, andwindow.clerkJWTpopulates a tick later.
WebSockets
WebSocket connections can now be opened and managed through the native runtime, keeping them alive across WebView reloads, app backgrounding, and network loss.What ships today:- Native connection ownership. The connection lives on the native side rather than in the WebView. It persists through JavaScript suspension, app backgrounding, and WebView reloads without any additional handling in your code.
- Durable inbound message storage. Incoming messages are written to disk before your handler sees them. If the handler fails or the app restarts, Despia replays the message on the next resume, reconnect, or reload.
- Durable outbound queue. Sends issued while the socket is offline are saved and flushed in order once the connection is restored.
- Auto-reconnect with backoff. Dropped connections retry automatically with wait times that grow per attempt and cap at 30 seconds. A keepalive ping goes out every 25 seconds to prevent routers and proxies from closing idle sockets.
- Subscribe frame registration. The
websocket://subscribecommand registers a frame Despia sends to the server on every connect and reconnect, enabling server-side cursor replay for any messages missed during a gap. - Connection status. The
websocket://statuscommand returns the live state of a connection, including pending outbound sends and unacknowledged inbound messages.
Native file preview with fileviewer://
A new fileviewer:// URL scheme opens any remote file in a native previewer on both platforms: QuickLook on iOS, and Despia’s own in-app file viewer on Android. Files served behind your existing login work without extra token plumbing, since the download reuses the WebView’s current cookies and User-Agent.What ships today:- Custom Android previewer. Android uses Despia’s own in-app file preview engine, not the system “Open with” chooser. This removes the dependency on whichever third-party file viewer the user happens to have installed and delivers render fidelity on par with QuickLook on iOS.
- True cross-platform parity. The same
fileviewer://call renders identically on iOS and Android. Your users see consistent output regardless of device, OS version, or what apps they have installed. - Authenticated downloads. The native fetch forwards the WebView’s cookie jar and User-Agent automatically, so session-protected file URLs render without query-string tokens or custom headers.
- Broad type coverage. PDFs, images (PNG, JPG, HEIC), plain text, RTF, CSV, and Office documents (DOCX, XLSX, PPTX) are supported. The file extension is taken from the URL path, with a fallback to the response
Content-Type. - Optional theme parameter. Pass
theme=lightortheme=darkalongsidesrcto match the native previewer to your app’s current appearance. Defaults to the system appearance when omitted.
Stripe Payment
Native Stripe PaymentSheet and CustomerSheet integration is now available through thestripe://payment and stripe://manage native feature commands. Apps can charge cards, Apple Pay, Google Pay, and Link through the native payment sheet, and let signed-in customers add, remove, and select among their saved cards through Stripe’s native CustomerSheet, all without leaving the web app or building a native wrapper.Both actions share a single fire-and-listen pattern. Your backend creates the Stripe objects, your page forwards the secrets into despia('stripe://...'), and the Despia runtime fires window.stripeEvent once with the outcome. Route on event.method: paymentSheet for charges, customerSheet for card management.What ships today:- Native PaymentSheet. Call
despia('stripe://payment?publishable_key=...&payment_intent_client_secret=...')to open Stripe’s native payment sheet on top of the WebView. The sheet handles card entry, 3DS, Apple Pay, Google Pay, Link, and any other method enabled on the Payment Intent. - Optional saved cards on payment. Pass
customer_id(cus_...) andephemeral_key_secret(ek_...) onstripe://paymentto attach the Stripe customer so the sheet lists their saved cards and saves the new one for reuse. The pair is all-or-nothing: pass both for saved cards, pass neither for guest checkout (the default and original behavior). Passing exactly one returns amissing paramfailed event with no sheet. Existing integrations without these params continue to work unchanged. - Native CustomerSheet for saved cards. Call
despia('stripe://manage?publishable_key=...&customer_id=...&ephemeral_key_secret=...&setup_intent_client_secret=...')to let the customer manage their saved payment methods. The ephemeral key must be created server-side with the Stripe API version the mobile SDK expects and is short-lived. The optional SetupIntent client secret enables the “add a new card” flow. - Theme.
theme=light,theme=dark, ortheme=automaticcontrols the sheet’s color scheme on both actions. Case-insensitive, unknown values fall back toautomaticwhich follows the device’s system setting. - Accent color.
accent_color=<hex>sets the primary Pay button color and the sheet’s primary accent. Accepts 6-digit hex, 3-digit shorthand, 8-digit hex with alpha, with or without a leading#, and the percent-encoded%23form. The native runtime tolerates raw#characters in any parameter position. Invalid values are silently ignored. - Corner radius.
corner_radius=<pt>sets the general corner radius applied to input fields and buttons together, including the secondary back button. Non-negative numbers only. Invalid, negative, or non-finite values are silently ignored. - Action corner radius.
action_corner_radius=<pt>overrides the primary Pay button corner radius independently of the general radius. When omitted, the Pay button inherits fromcorner_radius. When set withoutcorner_radius, only the Pay button is rounded and the rest of the sheet stays at Stripe’s default. - Single shared callback.
window.stripeEventreceives results from both actions. Branch onevent.methodto route betweenpaymentSheetandcustomerSheet, then branch onevent.statuswithin each. PaymentSheet statuses arecompleted,canceled, orfailed. CustomerSheet statuses areselected,canceled, orfailed, with nocompletedsince managing cards is not a payment. - Stable error contract. A missing or empty required param returns the literal error string
missing paramon either action, which is safe to match on. All other failures return Stripe’s localized SDK message, which should be logged or displayed but never branched on since it is locale-dependent. - iOS and Android parity. The same commands work identically on both platforms with no fork in your web code. The native parser treats
#as an ordinary character rather than a fragment delimiter, so hex colors and any position of the#resolve the same way everywhere.
stripe://manage action governs only Stripe-stored cards used for direct Stripe charges, not App Store or Play Store subscriptions managed through RevenueCat. Surface “Manage cards” only on screens where Stripe is the payment rail.Documentation: Stripe Payment, Manage saved cards.Packages: despia-native.Rebuild your app from the Despia dashboard to receive the new native runtime. No dashboard toggle is required to enable the feature. To integrate on the web side, define window.stripeEvent on your page before firing either action, route on event.method, and call despia('stripe://payment?...') or despia('stripe://manage?...') with the secrets your backend returned.Clerk Auth for Despia
Despia now ships an optional native Clerk authentication bridge for iOS and Android, driven entirely from JavaScript over aclerk:// URL scheme. Apps that need Keychain or Keystore backed sessions, native Sign in with Apple, offline cold-launch, or zero-config server-side auth can opt in. Apps using web auth providers like Supabase, Firebase, NextAuth, Auth0, or Clerk’s own web SDK are unaffected and continue to work out of the box.The same clerk:// URLs, event payloads, status strings, and error codes work identically on iOS and Android. Implementation differences (Apple Sign In sheet vs Custom Tab, Keychain vs Keystore) happen under the hood and never reach the JS layer.What ships today:- Configure and the two globals.
clerk://configurewires up the bridge. The native side writeswindow.clerkJWT(auto-refreshed every 50 seconds, under Clerk’s 60-second TTL) and fires every result throughwindow.onClerkEvent. Same-key configure is a no-op, different keys switch tenant and clear the secure session store. - Prebuilt Auth Sheet.
clerk://authviewopens Clerk’s native sign-in sheet covering email/password, OAuth, magic links, MFA, password reset, and passkeys in one prebuilt UI. - Email and password.
clerk://manual?method=passworddrives manual sign-in and sign-up flows with your own forms while keeping the native session and JWT refresh. - OAuth providers.
method=oauthruns system-browser OAuth viaASWebAuthenticationSessionon iOS and Chrome Custom Tabs on Android. Over 25 providers supported (Google, GitHub, Microsoft, Discord, LinkedIn, Facebook, Slack, Notion, Vercel, and more), with unknown values passed through as Clerk custom providers. - Native Sign in with Apple.
method=appleuses Apple’s nativeASAuthorizationControllersheet on iOS and Apple OAuth via Chrome Custom Tabs on Android. Required by App Review for any iOS app offering third-party social login. - Email one-time code.
method=email_codedrives the two-step OTP flow withaction=startandaction=verify. - SMS one-time code.
method=phone_code(aliassms) ships the same two-step OTP flow over SMS. Phone numbers are normalized to E.164, both code flows share pending state so only one runs at a time. - Passkeys.
method=passkeysigns in with an existing credential or registers a new one to the signed-in account. Saved to iCloud Keychain on iOS or Google Password Manager on Android. - User Profile sheet.
clerk://userprofileopens Clerk’s prebuilt account-management sheet for editing profile, managing emails, phones, external accounts, passkeys, sessions, MFA, and account deletion. - Offline session snapshot.
clerk://statereads the full user and session object from the in-memory cache hydrated from secure storage. No network round-trip, works with the device fully offline. - Zero-config server-side auth.
clerk://ssrwrites the session into the WebView cookie store scoped to your app host pluslocalhostand127.0.0.1. Next.jsclerkMiddleware(), TanStack, and any framework reading standard Clerk cookies sees an authenticated user on the very first request, no Clerk web SDK on the page required. A TTL ladder (50s refresher, 55s cookie, 60s JWT) guarantees the cookie pair is always in a state Clerk middleware accepts without a handshake redirect. An optionalheader=param also injects the JWT on main-frame navigations. - Attribution sync.
clerk://attributionauto-mirrors the Clerk userId into OneSignal and AppsFlyer on every sign-in or sign-up and clears it on sign-out. Enabled by default, overridable via the existingsetonesignalplayerid://andappsflyer://set_user_idroutes. - Sign out and multi-tenant switching.
clerk://signoutrevokes the session, stops the JWT refresher, clears attribution IDs, and writes the signed-out cookie state. Callingconfigureagain with a different publishable key switches tenant and resets everything atomically.
clerk://configure, so apps not using it see no change. Apps that want it need a publishable key from the Clerk Dashboard and a call to clerk://configure on boot. No dashboard configuration changes, native build settings, or rebuild steps are required to access the bridge - it ships in every Despia runtime.Speech recognition
Despia now exposes the platform’s native speech recognizer through two interoperable JavaScript surfaces, so your web app can transcribe audio on-device without a third-party STT service or paid API.What ships today:- URL-scheme bridge. A flat four-event control flow via
speechrecognition://start,stop, andabort, with results delivered to a singlewindow.onSpeechRecognitionEventcallback. Suited to push-to-talk UIs and explicit control flows. - Web Speech API polyfill. A drop-in
window.SpeechRecognitionandwebkitSpeechRecognitionimplementation that matches the standard browser surface. Existing Web Speech code and libraries likereact-speech-recognitionrun unmodified inside your app. - Cross-platform parity. Identical event shapes, error vocabulary, and parameter names on iOS and Android. The same JavaScript runs on both platforms with no branching. Android network failures are folded into the
audio-captureerror code so a single handler covers both runtimes. - Continuous and interim modes. Long-form dictation with live partial transcripts, plus single-utterance push-to-talk, configurable per session via the
continuousandinterimquery parameters. - BCP-47 language selection. Pass any BCP-47 tag like
en-US,de-DE, orja-JP, or omit the language parameter to use the device system locale. - Custom vocabulary biasing. Pass
known_wordsas a comma-separated query parameter, or setrecognition.knownWordson the polyfill, to nudge the recognizer toward product names, jargon, or proper nouns. Backed bySFSpeechAudioBufferRecognitionRequest.contextualStringson iOS 10 and later andEXTRA_BIASING_STRINGSon Android 13 and later, with a graceful no-op on older Android.
speechrecognition:// calls resolve silently and window.SpeechRecognition reads as undefined, since the native handler and the polyfill injection are both compiled into the signed binary.NFC tag reading and writing
Despia now exposes the device’s NFC chip to your web app. Trigger a single-tap read or write through thedespia() scheme, and handle the result through a global window.onNFCEvent callback.What ships today:- Read mode.
despia('nfc://read')activates the system NFC sheet, scans one tag, and fireswindow.onNFCEvent({ type: 'read', id, data })with the tag’s lowercase hex identifier and NDEF payload. - Write mode.
despia('nfc://write?value=...')writes an arbitrary payload to the next tapped tag and fireswindow.onNFCEvent({ type: 'write' })on success. - Cancellation and error handling. The runtime distinguishes user-dismissed sessions (
type: 'dismissed') from platform failures (type: 'error'), so silent resets and retry prompts stay separate. - vCard payloads. Write a contact card by prefixing the value with
VCARD_and joining properties with underscores. The receiving phone recognises the vCard and offers to save the contact natively.
nfc://read and nfc://write resolve silently and window.onNFCEvent never fires.window.virtual.href - 15 years later, a new plugin bridge
After 15 years of routing every plugin call throughwindow.location.href URL redirects, the bridge finally has a successor: window.virtual.href. This is a transparent internal architecture change on both iOS and Android, scoped to the window.despia and despia() paths. No code changes, no rebuilds, no NPM updates, no dashboard configuration - nothing on the developer side moves. The window.despia proxy now forwards to window.virtual.href instead of window.location.href, which removes the platform webview’s URL character limit and eliminates the dropped-call risk caused by the navigation queue collapsing back-to-back navigations.Direct window.location.href plugin calls are unaffected and stay fully supported. Over 7,500 apps - including most projects built with Despia before 2023 - call window.location.href = "myscheme://..." directly rather than going through the window.despia abstraction. That path is now the legacy bridge, but legacy here only means “the older path” - it remains active, fully maintained, and will continue to receive new feature support and improvements over time. Nothing about it is deprecated, and no app on the direct path needs to migrate, now or later.For 15 years, the window.location.href redirect was the only way native code got triggered from the webview. It worked, but it inherited the platform webview’s URL character cap - roughly 2KB on iOS WKWebView and a similar practical ceiling on Android WebView. Large payloads such as base64-encoded images, long AI prompts, or JSON blobs were silently truncated or rejected before native code ever saw them. Firing multiple plugin calls back-to-back was also unreliable, because the navigation queue on both WebKit and Android WebView could collapse them and silently drop the second call. window.virtual.href removes both constraints.Why nothing needs to change in your code. Every Despia NPM package and the despia() helper have always called window.despia, never window.location.href directly. window.despia is a proxy: it used to forward to window.location.href, and it now forwards to window.virtual.href. This migration was planned from day one. window.despia was built as a proxy specifically so this kind of switch could happen invisibly - and today it has. The despia() calls already in your code keep working unchanged on both iOS and Android.What ships today for window.despia and despia() users:- No character limit. Pass multi-megabyte payloads directly on iOS and Android. Full base64 images, long prompts, JSON dumps, and config blobs all go through intact.
- No dropped calls. Fire as many plugin calls in a row as you want, in any order. Every call runs on both platforms.
- Free-form strings. Spaces, special characters, and newlines pass through as-is with no manual encoding required.
- Same syntax as before.
window.virtual.href = "myscheme://..."uses the same scheme and parameter shape as the redirect path it replaces. The string is delivered straight to native code via a message channel instead of going through the URL redirect machinery.
myscheme://method?param=value shape on both bridges, and that is a deliberate choice rather than a legacy quirk. JSON-based bridges have their place, but URL schemes have real advantages worth keeping: one-line calls compose cleanly, they grep and search trivially across a codebase, and they read well to AI coding tools, which handle flat scannable text far better than nested object payloads. The single meaningful argument against the URL approach was always the 2KB cap. With window.virtual.href, that cap is gone - so the syntax keeps every advantage and loses its only real drawback.Who this affects: developers using the despia() NPM helper or assigning to window.despia directly. They get the upgrade automatically on their next deployment.Who this does not affect: developers calling window.location.href = "myscheme://..." directly. That bridge stays exactly as it was, with full support and continued investment in new features and improvements.No dashboard configuration changes, rebuild steps, code changes, or NPM package updates are required. Existing iOS and Android apps both inherit the appropriate behaviour automatically on their next deployment.Action Sheet
A newactionsheet:// scheme triggers a real platform action sheet over your web view, with a single global callback that fires when the user picks a row or dismisses the sheet.What ships today:- Native iOS and Android sheets. Pass a title and a JSON-encoded items array as URL params. The native side handles presentation and iPad anchoring to the centre of the WebView automatically.
- Per-platform icons. Each item accepts
iconIos(any SF Symbol name) andiconAndroid(any Material drawable resource name). Missing or unknown icon names render the row without an icon, no crash. - Destructive styling. Setting
destructive: trueon an item renders it in red, matching the platform cue users expect for irreversible actions like delete, remove, and leave. - Theme override. Pass
theme: 'light','dark', or'system'to control sheet appearance independently of the operating system. Defaults tosystem. - Single global callback. The native side calls
window.onSheetEvent(value)once per sheet with the tapped item’s value, ornullif the user tapped Cancel, tapped outside, or tapped back.
Focus events
Apps can now detect when they enter the foreground or background through globalwindow.focusin() and window.focusout() callbacks. Attach handlers directly to the window object to react to lifecycle changes such as resuming sessions, refreshing data, or pausing media.What ships today:- focusin callback. Fires every time the app returns to the foreground, including the first launch resume. Use it to revalidate session state, refresh auth tokens, or reload content when the user comes back.
- focusout callback. Fires once per background transition. Use it to stop timers, pause video playback, or persist unsaved state before the app is suspended.
navigator.userAgent.toLowerCase().includes('despia') before assigning handlers to avoid running native-only code in browsers.Packages:This feature requires a native rebuild. Add the handlers in your web code, then rebuild your app from the dashboard for the callbacks to fire.Apple Health workouts with per-session statistics
A newhealthkit://workouts scheme fetches workout sessions as a flat array and can attach aggregated quantity statistics computed across each individual workout’s time range. The result lands on window.healthkitWorkouts, separate from the existing multi-type healthkitResponse object, so the new and legacy paths coexist in the same app.What ships today:- Dedicated workouts endpoint. Call
healthkit://workouts?days=Nto retrieve every workout in the last N days as a flat array, withdate,activityType,duration,calories,distance,value, anduniton each record. Authorization forHKWorkoutTypeIdentifieris requested automatically on the first call. - Per-workout statistics. Pass an
included=parameter with a comma-separated list ofHKQuantityTypeIdentifiervalues, each suffixed withAverage,Max,Min, orSum, to compute aggregates scoped to the workout’sstartDatetoendDatewindow rather than the whole day. Results land on asamples[]array on each workout, with each entry exposingkey,value, andunit. - Suffix-driven aggregation.
Averagemaps to discrete average,Maxto discrete max,Minto discrete min, andSumto cumulative sum. An identifier with no suffix defaults to discrete average. Authorization for every base quantity type referenced is requested in a single prompt, with the suffix stripped before the auth call. Invalid identifiers are silently skipped without affecting valid ones in the same call. - PascalCase activity types. The new scheme returns Apple’s
HKWorkoutActivityTypeenum names directly, for exampleRunning,Cycling, andFunctionalStrengthTraining. The legacyreadhealthkit://HKWorkoutTypeIdentifierpath continues to return lowercase values likerunning, so existing UIs reading fromhealthkitResponseare unaffected. - Fully backwards compatible. Every existing
readhealthkit://andhealthkit://read?types=...call keeps working unchanged. Usehealthkit://workoutsfor any workouts-only read or any read that needs per-workout statistics, and the legacy path when batching workouts with other unrelated identifiers in a single call.
healthkit://workouts and read from window.healthkitWorkouts. If your app has not been rebuilt since the new endpoint shipped, trigger a fresh build from the Despia Editor to pick it up.This feature requires a native rebuild. Add the handlers in your web code, then rebuild your app from the dashboard for the callbacks to fire.
Gyroscope sensor
The gyroscope feature ships today, exposing live angular velocity readings alongside magnetic compass heading on a single channel throughgyroscope://start and gyroscope://stop. Every gyro sample carries the latest cached heading, so one callback drives motion-based UI, shake gestures, AR overlays, fitness apps, and compass or Qibla needles.What ships today:- Single-channel gyro and compass.
window.onGyroscopeChangereceivesx,y,zangular velocity in radians per second plusheading,headingAccuracy, andtimestampon every emitted reading. Heading is-1until the magnetometer produces its first fix. - Magnitude threshold.
gyroscope://start?threshold=filters readings natively against√(x² + y² + z²). Pass0to receive every sample for tilt or parallax UI, higher values for shake-style gestures so the JavaScript layer only sees deliberate motion. - Tracking state flag.
despia.gyroscopeActivereflects whether the sensor is running and survives soft close and reopen. Boot code can branch on the flag to resume the live readout or show the start button without guessing. - Resume after soft close. When the user backgrounds and reopens the app, the runtime restores the session using the last threshold and continues invoking
window.onGyroscopeChangeas soon as the handler is reattached. - Calibration prompts. Readings carry a
calibration_requiredstatus when iOS reports the magnetometer needs recalibrating. Heading continues to stream during this state so the page can surface a figure-8 hint and dismiss it whenheadingAccuracyreturns to a good range. - Error handling. Devices without a gyroscope or magnetometer emit a
status: "error"payload witherror: "unavailable", anddespia.gyroscopeActivestaysfalseso fallback UI can take over. - No permission prompt. Heading is delivered without requesting location access. True-north and Qibla conversions can be done in JavaScript using the page’s own coordinates and a local declination value.
despia-native, define window.onGyroscopeChange, and call gyroscope://start to begin receiving readings.This feature requires a native rebuild. Add the handlers in your web code, then rebuild your app from the dashboard for the callbacks to fire.
Screen Radius
The runtime now measures the device’s hardware screen corner radius at page load and exposes it to the web layer, so modals, sheets, cards, and overlays can curve in lockstep with the physical bezel.What ships today:- Automatic injection. No scheme call is required. The runtime reads the top-left corner radius on every supported device and writes it to the page before stylesheets and scripts run.
- JavaScript value.
despia.screenRadiusreturns the radius as a number in CSS pixels, for example47.33on iPhone 14 Pro. Use it for any sizing logic that needs the raw measurement. - CSS variable.
--screen-radiusis scoped to the document root with apxunit, ready to drop intoborder-radiusrules orcalc()expressions for concentric inner corners. - Graceful fallback. The value resolves to
0on devices without rounded screens, on older OS versions, and in non-Despia environments. Pair everyvar(--screen-radius)with a0pxdefault socalc()expressions stay valid in desktop previews and clamp to square corners. - Cross-platform parity. iOS and Android expose identical values in CSS pixels, so feature code does not need to branch by platform.
- despia-native on npm
RevenueCat Customer Center
A newrevenuecat://center scheme opens the native RevenueCat Customer Center sheet, giving users a single in-app surface to restore purchases, manage their subscription, request refunds, and complete feedback surveys without leaving the app.What ships today:- Customer Center scheme. Call the new
revenuecat://centerscheme with anexternal_idparameter to present the native sheet. Layout, copy, and available actions are configured from the RevenueCat dashboard, so no client-side UI work is required. - Event callback. Every interaction inside the sheet streams back through
window.onRevenueCatCenter(event). Switch onevent.eventto handlerestoreStarted,restoreCompleted,restoreFailed,manageSubscriptionsOpened,refundRequested,refundCompleted,feedbackSurveyCompleted,managementOptionSelected, anddismissed. - iOS in-app refunds. iOS users can now initiate refund requests directly from the sheet. The runtime emits
refundRequestedandrefundCompletedevents withproductIdand astatusfield ofsuccess,userCancelled, orerror. - Android refund fallback. Google Play does not allow in-app refund requests, so configure a
customUrlmanagement option in the RevenueCat dashboard and route Android users to amailto:link or your support page from themanagementOptionSelectedevent. iOS continues to use the native flow. - Fully safe restore pattern. On
restoreCompleted,refundCompleted, anddismissed, re-query the native store with the existinggetpurchasehistory://scheme and re-run your entitlement check. The event payload’sactiveEntitlementsarray is informational, the native store query is authoritative.
- despia-native on npm
Extended HealthKit sleep identifier support
HealthKit sleep reads now cover the full set of sleep analysis categories, so apps can ingest data from any HealthKit-compatible device and not just Apple Watch or iPhone sleep tracking.Previously, sleep reads were limited to the basic in-bed and asleep categories produced by first-party Apple sleep tracking. Third-party rings, bands, and mattress sensors that sync richer stage data into the Health app had their detailed values dropped at the read layer.What ships today:- Full stage breakdown. Reads now return REM, core, deep, awake, and unspecified asleep values when the source device provides them, in addition to in-bed and asleep totals.
- Third-party device coverage. Any HealthKit-authorised device that writes sleep samples to the Health app is now readable through the same
despia()sleep call, including Oura, Whoop, Withings, Eight Sleep, Fitbit, and Garmin where the user has enabled Health app sync. - Per-sample source metadata. Each returned sample includes the originating source name and bundle identifier so the app can distinguish between devices when the user has more than one sleep tracker connected.
- Backwards compatibility. Existing integrations that only consume in-bed and asleep totals continue to work without changes. New stage fields are additive.
startDate, endDate, value, and label, where label is one of inBed, awake, asleep, core, deep, or rem. See Apple Health - Sleep data for the full response schema.No dashboard configuration changes, rebuild steps, or integration code changes are required. The expanded data is returned automatically on the next read.Updated NPM package documentation
The despia-native npm package page now links to the new documentation at setup.despia.com, replacing the legacy npm.despia.com content.No integration changes are required. The SDK API is unchanged.Critical alerts
Despia now supports critical alerts, allowing notifications to break through Do Not Disturb and silent mode on both Android and iOS.What ships today:- Android. Critical alerts work out of the box with no additional configuration required.
- iOS. Critical alerts require the Critical Alerts entitlement from Apple. Once granted, enable them in the Despia dashboard under App - Integrations - OneSignal - Critical Alerts, then rebuild a new version in Despia to activate the capability.
PowerSync schema configuration
db.connect() now accepts a schema parameter that tells the sync engine which tables and columns to map from your backend. Previously, schema configuration had to be inferred or handled separately outside the connect call.What ships today:- Schema definition in connect. Pass a
schemaobject directly todb.connect()alongsidefetchTokenandurl. Each key is a table name, each value declares the columns and optional indexes for that table. - Column type mapping. Columns are typed as
'text','integer', or'real', mapping directly to SQLite affinity types. - Optional indexes. Each table accepts an optional
indexesmap of index name to an array of column names, allowing the sync engine to optimise queries at the schema level. - Updated TypeScript types.
ConnectOptionsnow includesschema: PowerSyncSchemaas a required field. ThePowerSyncSchematype is exported directly from@despia/powersync.
1.1.0 and add a schema object to any existing db.connect() calls. No dashboard or backend changes are required.Apple Health realtime observer
HealthKit now supports live data observation. Subscribe to any health or workout identifier and receive a webhook POST to your server whenever the data changes, with background delivery that persists across app restarts.What ships today:- Observer registration. Call
healthkit://observewith a comma-separated list of identifiers, a delivery frequency, and a server URL. Despia registers a nativeHKObserverQueryfor each type and enables background delivery via iOS background task APIs. - Webhook delivery. On every HealthKit update, Despia fetches the latest day of data for the changed type and POSTs it to your server as JSON with
event,userId,timestamp, anddatafields. - Configurable frequency. Set
frequencytoimmediate,hourly,daily, orweeklyto control how aggressively the OS wakes the app to deliver updates. - Observer persistence. Active observers are saved to device storage and restored automatically when the app restarts - no re-registration required.
- Observer state.
despia.observingHealthKitreflects the current array of active identifier strings. It updates after everyobserveorunobservecall. - Selective stop. Call
healthkit://unobserve?types=allto stop all observers, or pass specific identifiers to stop individual types. - User identification. Append your own user ID as a query parameter on the
serverURL (e.g.?user=abc123) to route webhook events directly to the right user record on your server without device ID mapping.
despia-native to the latest version to access the new scheme.Packages:Movement-based location tracking
Thelocation:// scheme now accepts a movement parameter that fires an additional GPS update whenever the device moves a set distance, independent of the time-based buffer interval.What changes:- Distance-triggered updates. Set
movementto a distance in centimetres to receive a GPS point every time the device crosses that threshold. Usemovement=100for 1-metre precision, suited to running, cycling, and navigation use cases. - Combined buffer and movement.
bufferandmovementrun simultaneously.movementdrives updates as the user moves, andbufferacts as a heartbeat fallback when the device is stationary or movement updates are sparse. - Same location object shape. Movement-triggered points are delivered to
window.onLocationChange, stored in the local session, and POSTed to theserverendpoint using the same object shape as time-based points. No handling changes are required.
horizontalAccuracy before calculating distances - discard any point where the value exceeds 10.No dashboard configuration changes are required for foreground use. Background tracking requires the Background Location addon to be enabled in your Despia dashboard.HealthKit workout quantity identifiers
Despia HealthKit now supports workout identifiers that provide quantity data, allowing apps to read and write typed quantity samples associated with workout activity types.What ships today:- Workout quantity identifiers. Apps can now reference quantity-type identifiers tied to specific workout activities, such as active energy burned, distance, step count, and other measurable outputs produced during a workout session.
- Quantity type resolution. The SDK resolves each workout identifier to its underlying
HKQuantityTypeautomatically, so apps receive correctly typed samples without manual type mapping.
Apple Sign In
Despia now supports Sign In with Apple using the Apple JS SDK across iOS, Android, and web, with platform-aware behaviour and full documentation.What ships today:- iOS native - native Face ID sheet. On iOS, the Apple JS SDK with
usePopup: trueopens the native Face ID / Apple ID sheet directly inside WKWebView. Nooauth://bridge is needed on iOS. Theid_tokenis returned to your JavaScript callback with no page redirect, which avoids the blank white screen that causes App Store rejection when using a redirect-based flow. - Android native - oauth:// bridge. On Android, the
oauth://bridge opens Chrome Custom Tabs for the Apple OAuth flow.public/native-callback.htmlreceives theid_tokenandcodein the URL hash and fires the deeplink to close the tab. Theoauth/prefix in the deeplink is required. - Web - Apple JS SDK popup. On web, the Apple JS SDK popup returns the
id_tokendirectly to your JavaScript callback. No redirect flow is used. - Two response mode options for Android. The
fragmentmode redirects the browser directly tonative-callback.htmlwith#id_tokenin the hash - no backend POST handler needed. Theform_postmode sendscode,id_token,state, anduser(name and email JSON, first login only) to your backend, which validates and redirects tonative-callback.htmlwith a session token. - Plain HTML callback file.
public/native-callback.htmlis recommended over a React component for the callback page. React Router can strip the#id_tokenhash fragment on route change, causing tokens to disappear. The plain HTML file bypasses React Router entirely and reads the hash directly from the browser. Chrome Custom Tabs hides the URL bar, so the.htmlextension is never visible to users. - React, Vue, Vanilla JS, and HTML auth page examples. All auth page examples include the
searchParamsdependency array fix to handle the already-mounted page problem, where the deeplink updates the URL without remounting the component and the token handler never re-runs.
.p8 private key expires after 6 months - set a reminder to regenerate it before expiry or Apple Sign In will silently stop working.Full documentation is available at Apple Sign InPackages:A rebuild is required. No dashboard configuration changes are required beyond enabling the Apple JS SDK script tag and configuring your Apple Services ID credentials in your backend auth provider.AppsFlyer integration
Despia now includes a full AppsFlyer integration for install attribution, deep linking, in-app event tracking, and creator affiliate link support on both iOS and Android.What ships today:- Install attribution. The native AppsFlyer SDK is initialised automatically at launch on both iOS and Android. Attribution data is captured at install time, cached on-device, and injected into your web layer on every page load via
despia.appsFlyerAttribution,despia.appsFlyerReferrer, anddespia.appsFlyerUID. No setup code required. - Normalised referrer string.
despia.appsFlyerReferrerprovides a clean source string on every load - values liketiktok_ad,facebook_organic,google_ad, andorganic- ready to use for onboarding personalisation and funnel branching without any parsing. - Deep linking. When a user opens the app via a campaign or creator OneLink URL, Despia resolves the deep link natively and navigates the WebView to the correct path automatically.
deep_link_valuemaps directly to a route in your web app. No navigation code required from the web layer for fresh launches. - Re-engagement callback. For users who already have the app installed and tap a campaign link while the app is open, Despia calls
window.onAppsFlyerDeepLink(data)with the full click event payload so your app can respond without a full page reload. - Creator and affiliate links. Deep link params support
deep_link_sub1throughdeep_link_sub5for creator codes and affiliate IDs. Each creator can receive a unique OneLink URL. Attribution, commission tracking, and welcome personalisation are available from the same payload. - Event bridge. In-app events are forwarded from your web layer to the native AppsFlyer SDK via
despia("appsflyer://log_event?..."). Standardaf_prefixed events map automatically to Meta and TikTok conversion events in their dashboards. Custom events appear in the AppsFlyer dashboard for funnel and retention analysis. - User identification.
set_user_id,set_email, andset_phoneare all supported. Emails and phone numbers are hashed with SHA256 automatically before transmission. - GDPR consent.
set_consentacceptsis_gdprandhas_consentparams and passes them directly to the AppsFlyer SDK consent API. - On-demand data retrieval. Both
get_uidandget_attributionsupport an await pattern -await despia("appsflyer://get_uid", ["appsFlyerUID"])- for cases where you need the value immediately in the same execution flow. - Ad revenue tracking. Coming soon -
despia("appsflyer://log_ad_revenue?...")will allow impression-level revenue reporting back to AppsFlyer from Meta, TikTok, and AdMob to enable LTV and ROAS reporting per acquisition channel.
Local CDN - query all cached files
A newlocalcdn://query method is available on the Local CDN API, returning every cached file in a single call without requiring individual IDs.Previously, retrieving cached file metadata required passing a known list of index IDs to localcdn://read. There was no way to get a full inventory of the cache without tracking IDs separately in application state.What ships today:- localcdn://query. Calling
await despia('localcdn://query', ['cdnItems'])returns the fullcdnItemsarray containing every file currently in the local cache, across all folders. - Same response schema. Each item in the returned array follows the existing response schema -
index,index_full,local_cdn,local_path,cdn,size,status, andcreated_at- identical tolocalcdn://readoutput.
despia-native to the latest version to access the new method.Local server
Despia now ships a local server for iOS and Android. Your web build downloads to the device on first launch and is served from an on-device HTTP server athttp://localhost. From that point on, the app boots with zero network latency and works completely offline.What ships today:- On-device HTTP server. Assets are served from
http://localhost, notfile://or a custom scheme. BrowserRouter, Vue Router, and any routing library that expects a real HTTP origin work without modification. - First-launch hydration. On first open, Despia fetches your web build from your existing hosting (Netlify, Vercel, AWS, or anything else) and caches it on the device. No migration required.
- OTA updates. On startup, Despia fetches
despia/local.jsonand compares thedeployed_attimestamp with the cached value. If it has changed, the new build downloads in the background and applies on next launch. No app store review needed for HTML, CSS, JavaScript, image, or font changes. @despia/localbuild plugin. Available for Vite, Webpack, Rollup, Nuxt, SvelteKit, Astro, Remix, esbuild, and any build system via apostbuildCLI hook. The plugin scans your output directory after each build and generatesdespia/local.jsonautomatically.despia-version-guard. A companion package for gating web UI features behind a minimum native runtime version. Supports React, Vue, Angular, Svelte, and Vanilla JS.
@despia/local— build plugindespia-native— native SDK and JavaScript bridge