# Accessibility in Userpilot in-app messages Source: https://docs.userpilot.com/accessibility/accessibility-in-userpilot-in-app-messages Learn how Userpilot supports accessible in-app messages by aligning with WCAG 2.1 Level AA standards, helping teams build inclusive user experiences. ### What is web accessibility? Web accessibility means designing and building digital content, such as websites, web apps, and mobile apps, so that people of all abilities, including those with visual, auditory, motor, or cognitive impairments, can perceive, understand, navigate, and interact with it. It’s not just about compliance; accessible design improves usability for everyone, from older adults to keyboard-only users. At Userpilot, our engineering team started by reviewing WCAG and identifying areas to improve accessibility in our in-app messages. ### Which web content accessibility guidelines apply? The [**Web Content Accessibility Guidelines (WCAG)**](https://www.w3.org/WAI/standards-guidelines/wcag/) provide the global standards for this. They’re built on four key principles: * **Perceivable:** Content must be visible or audible; for example, images should include alt text. * **Operable:** All interface elements should work via keyboard and other input methods. * **Understandable:** Content and navigation should be clear, with the page language defined in the code. * **Robust:** Code should follow web standards to work across browsers and assistive tools. For example, one specific success criterion is Contrast (Minimum) - SC 1.4.3 in WCAG 2.1, which requires a contrast ratio of at least 4.5:1 for normal text. You can consult the full standard here: [WCAG 2.1 Recommendation](https://www.w3.org/TR/WCAG21/). In practice, many organisations target **WCAG 2.1 Level AA** conformance (which covers many of the key accessibility issues). ## How Userpilot supports accessibility Userpilot currently aligns with key **WCAG 2.1 Level AA** principles by offering essential accessibility features in in-app messages. These include: * Keyboard navigation * Screen reader support * Color contrast > [!NOTE] > > **Multi-language accessibility** - Userpilot automatically sets the **language attribute** on HTML elements, enabling screen readers to detect the correct language and read text with accurate pronunciation and context. ### Screen reader support Screen readers are tools that help people with visual impairments understand what’s on a webpage by reading the content out loud. To make this work smoothly, we make sure all text is easy to read and navigate, and we use **ARIA labels** and **Alt text** to describe what each element does or what state it’s in. Combined with solid keyboard navigation, full support for screen readers ensures everyone can easily access and interact with our in-app messages. ![Demo of screen reader support in a Userpilot flow](https://mintcdn.com/userpilot/GA0IHF4qsG5PlKgJ/accessibility/assets/images/2025-10-28-flow-screen-reader-support.gif?s=61b1a5bbbf9f7502cd873ec1a86c34ce) ### Color contrast Userpilot encourages maintaining **high color contrast** between text and backgrounds and avoiding color as the sole indicator of meaning (e.g., success vs. error). In your in-app messages, ensure contrast ratios follow **WCAG 2.1 standards**, **4.5:1** for normal text and **3:1** for large text. Since Userpilot allows full control over fonts and colors through its **Themes**, you can easily adjust styles to meet these accessibility requirements. ![Theme editor showing font contrast ratio and WCAG pass/fail indicators](https://mintcdn.com/userpilot/GA0IHF4qsG5PlKgJ/accessibility/assets/images/2025-08-03-theme-editor-contrast-ratio.png?fit=max\&auto=format\&n=GA0IHF4qsG5PlKgJ\&q=85\&s=3c840653e83d6fd73ee501293e9b1fc9) ## Accessibility features built into Userpilot All Userpilot content automatically includes key accessibility features, no setup needed. These enhancements make your in-app messages easier to navigate and more inclusive for everyone. ### Semantic headings Userpilot uses proper heading structures (like H2, H3, and paragraph tags) to create logical reading content elements. Screen readers rely on this hierarchy to help users move through content smoothly and understand its structure. ### ARIA Labels ARIA (Accessible Rich Internet Applications) labels are automatically applied to describe each element’s purpose. This helps users who rely on assistive technologies, such as screen readers, better understand what each element does. ### Roles The right HTML roles (for example, role="button" or role="dialog") are included by default so assistive tools can accurately interpret and announce how each element behaves. ![Resource Center widget with corresponding ARIA roles and labels shown in DevTools](https://mintcdn.com/userpilot/GA0IHF4qsG5PlKgJ/accessibility/assets/images/2025-10-28-resource-center-aria-roles-devtools.png?fit=max\&auto=format\&n=GA0IHF4qsG5PlKgJ\&q=85\&s=e31c4bf379c5893ebbe2cc8dcd8971c3) ## In-app messages that support accessibility in Userpilot Userpilot’s accessibility features apply across **all in-app experience types**, ensuring inclusivity throughout the user journey. Accessibility is supported in: * **Flows** * **Spotlights** * **Embeds (Cards and Banners)** * **Checklists** * **Resource Center** * **NPS** * **Surveys** Each of these experiences inherits accessibility enhancements such as customizable **alt text for images**, compliant **color contrast ratios**, and **language attributes**, helping you create consistent and inclusive product experiences. > [!NOTE] > > 1. To make videos accessible in **Flows**, **Checklists**, or the **Resource Center**, add `aria-label` to the embed code and include a brief video description for screen readers. > > ![Embed Video dialog with an aria-label describing the video for screen readers](https://mintcdn.com/userpilot/GA0IHF4qsG5PlKgJ/accessibility/assets/images/2025-10-28-embed-video-aria-label.png?fit=max\&auto=format\&n=GA0IHF4qsG5PlKgJ\&q=85\&s=66c42bd451a47754f5d34a9d391ff4da) > > 2. For images in **Flows** or **Resource Center posts**, click the **accessibility** icon and add a short description to make the image readable by screen readers. > > ![Image accessibility panel with an alt text field](https://mintcdn.com/userpilot/GA0IHF4qsG5PlKgJ/accessibility/assets/images/2025-10-28-image-accessibility-alt-text.png?fit=max\&auto=format\&n=GA0IHF4qsG5PlKgJ\&q=85\&s=183b2efa48cad8e08a61183757989cae) ### Keyboard navigation Keyboard navigation plays a crucial role in making your in-app messages accessible. Many users with visual or auditory impairments rely on their keyboards along with screen readers to explore content and interact with elements. You can test this yourself by enabling your device’s built-in screen reader and navigating through your in-app experiences using the standard keyboard shortcuts. As you move through each element, you’ll hear how focus shifts across the interface and how every item is announced aloud. * **Tab** → Move **forward** through interactive elements (links, buttons, inputs, etc.) * **Shift + Tab** → Move **backward** through elements * **Enter/Spacebar** → **Activate** the selected element (like clicking a button or link) * **Arrow keys** → Navigate **within** components such as dropdown menus, radio buttons, sliders, or tabs * **Esc (Escape)** → **Close** modals, tooltips, or dropdowns ![Demo of keyboard navigation announcing focus through a Resource Center panel](https://mintcdn.com/userpilot/GA0IHF4qsG5PlKgJ/accessibility/assets/images/2025-10-28-keyboard-navigation-demo.gif?s=ad81bda54990fdd56263a8b942583425) > [!NOTE] > > For **tooltip** and **driven action steps**, enabling the autofocus option helps users navigate smoothly with the keyboard by clearly indicating the active element. > > ![Tooltip settings with the autofocus toggle enabled](https://mintcdn.com/userpilot/GA0IHF4qsG5PlKgJ/accessibility/assets/images/2025-10-28-tooltip-autofocus-setting.png?fit=max\&auto=format\&n=GA0IHF4qsG5PlKgJ\&q=85\&s=9a86acf832188f0ea173e2af9a41ec51) # Cloning Content Across Applications Source: https://docs.userpilot.com/admin-console/duplicating-and-cloning-content-across-applications Clone surveys, checklists, flows, themes, and design library groups from one Userpilot app into your others to reuse content and keep experiences consistent. Clone surveys, checklists, flows, themes, and design library groups from one Userpilot application into your others. Reuse content across apps instead of rebuilding it from scratch, and keep experiences consistent across every app you manage. ## Overview If you manage more than one application in Userpilot, you can copy content from one app into your others instead of recreating it manually in each one. This works for surveys, checklists, flows, themes, and design library groups, and you can copy into a single app, several apps, or all of them at once. The content itself comes across intact, while app-specific settings reset so you can tailor them for each destination. ## Use cases * **Multi-region or multi-brand setups:** if you run separate apps for different regions, brands, or divisions, build a flow, checklist, or survey once and copy it everywhere instead of rebuilding it per app. * **Consistent branding across apps:** copy a theme or design library group so every app shares the same look and feel. * **Launching a new app:** instantly seed a newly added application with your proven content rather than starting from an empty workspace. * **Reducing manual error:** maintain one source of truth and copy it out, instead of risking typos or missed updates when recreating content by hand. ## Get Started #### **Copy a single item to other apps** 1. On any content item, click the **⋮** menu and select **Duplicate**. 2. In the duplicate window, turn on **Duplicate to other applications**. 3. Choose your destination(s) in the app picker — pick individual apps, or select **All applications**. 4. *(Optional)* Enter a custom name for the copied content. 5. Confirm. You'll get per-app success/failure feedback. #### **Copy several items at once (Surveys, Checklists, Flows)** 1. Select one or more items from the list. 2. Click **Clone to other applications**. 3. Pick your destination app(s) and confirm. ![Screenshot 2026 08 20 At 14 49 55](https://mintcdn.com/userpilot/W6_A-SWa1K5Miv1m/images/Screenshot-2026-08-20-at-14.49.55.png?fit=max\&auto=format\&n=W6_A-SWa1K5Miv1m\&q=85\&s=ba5a0a5310384e282cd6e77a3a587712) #### **What you can clone** | Content type | Duplicate in the same app | Bulk duplicate | Clone to other apps | | - | - | - | - | | Surveys | Yes | Yes | Yes | | Checklists | Yes | Yes | Yes | | Flows | Yes | Yes | Yes | | Themes | Yes | No | Yes | | Design library groups | Yes | No | Yes | ### Permissions The cross-app option appears only if you have access to more than one application—it's available to users who manage multiple apps via the Admin Console. If you have access to a single app, you'll see the standard same-app duplicate options only. ### **What doesn't get transferred over?** To keep each app correctly scoped, the following settings *do not* get transferred over when cloning content across multiple applications; * Trigger * Targeting (pages/domains) * Audience * Goals (flows only) These will need to be reconfigured after being cloned. The content itself — steps, questions, design — comes across intact. **Further information:** * **Checklists that trigger a flow:** the triggered flow is cloned (or reused) in the destination app automatically. * **Themes & design library groups:** if a matching one already exists in the destination, it's reused rather than duplicated — so you won't get (copy) clutter. Design library cloning covers Colors, Shadows, Spacing, Typography, and Assets. * **Plan limits:** each app has its own plan limit. Once an app reaches its limit, content won't duplicate into it. Nothing is partially copied.  # Admin Console Source: https://docs.userpilot.com/admin-console/overview All users who have Userpilot installed on more than one application have access to the Admin Console. Additional applications can be added to your plan. > [!WARNING] > > All users who have Userpilot installed on more than one application have access to the Admin Console. Additional applications can be added to your plan. ### Overview The Admin Console in Userpilot gives you centralized control and visibility over all of your applications, users, and account settings. From the admin console, users can create reports and dashboards using data from all of their applications, manage user roles and permissions, configure authentication, and manage subscription information at an organizational level. ### Use Cases * **Cross-application analytics:** Create reports and dashboards that bring together data from multiple applications to gain a complete view of performance. For example, compare active users between apps, track core feature adoption across products, or view all users engaging with your entire product suite. * **Centralized user and role management:** Add teammates, assign roles, and adjust permissions for any application from a single location, ensuring each person has the right level of access based on their role in the organization. * **Security configuration:** Configure and enforce SSO or two-factor authentication across all applications to maintain consistent security standards organization-wide. ### Get started If your organization already has multiple applications within Userpilot, the Admin Console will be automatically enabled. Open the application switcher menu in the navigation bar and select **View Admin Console**. From within the Admin Console, use the same menu and select **Exit Admin Console** to return to your previous application. ![View Admin Console button in the application switcher menu](https://mintcdn.com/userpilot/nVhZLATAUfLtc8CV/admin-console/assets/images/2026-07-01-view-admin-console-button.png?fit=max\&auto=format\&n=nVhZLATAUfLtc8CV\&q=85\&s=48de10b135dc08dda6902d714b6a03a6) ### Aggregated Analytics In the Admin Console, you can build reports and dashboards that combine data from all your applications into a single view. This unified approach helps you better understand user behavior across platforms and uncover meaningful insights with full context. When adding a metric to a report, start by selecting the application it belongs to. This allows you to include events from multiple applications in one report, making cross-app comparisons easier and more insightful. ![Select app dropdown when adding a metric to a report](https://mintcdn.com/userpilot/nVhZLATAUfLtc8CV/admin-console/assets/images/2026-07-01-select-app-dropdown-report-metric.png?fit=max\&auto=format\&n=nVhZLATAUfLtc8CV\&q=85\&s=99f0f03b3c5b0334984a0cce82c5f494) You can also view key product usage metrics aggregated across all your applications in the **Product Usage Dashboard**. This dashboard helps you quickly understand: * The total number of unique users and companies active across all applications * The top-performing pages and events across your entire product suite * The most engaged users and companies, based on activity across all applications ![Product Usage Dashboard with aggregated analytics](https://mintcdn.com/userpilot/nVhZLATAUfLtc8CV/admin-console/assets/images/2026-07-01-product-usage-dashboard-admin-console.png?fit=max\&auto=format\&n=nVhZLATAUfLtc8CV\&q=85\&s=bc49d39164d03f602493260df2dc6f50) ### Manage Applications and Teammates Easily manage user roles, permissions and applications from one centralized location within the Admin Console. **Manage Applications:** After adding additional app tokens to your account, kickstart the set-up process directly from Userpilot by specifying the application name and who you would like to provide access to. **Manage Teammates:** From the **Teammates** tab, you can invite new users to any application, assign appropriate roles, and grant access to the Admin Console as needed. ![Managing applications and teammates in the Admin Console](https://mintcdn.com/userpilot/nVhZLATAUfLtc8CV/admin-console/assets/images/2026-07-01-admin-console-manage-teammates.png?fit=max\&auto=format\&n=nVhZLATAUfLtc8CV\&q=85\&s=06fef13ad3c23234cb60351b3cb450f8) **Manage Authentication:** From the **Authentication** tab, you can enhance security and control by enabling [SSO](/configure/team-and-roles/sso), [two-factor authentication (2FA)](/configure/team-and-roles/2fa), [SCIM directory sync](/configure/team-and-roles/scim), and [JIT provisioning](/configure/team-and-roles/jit) across all of your Userpilot applications. ### Manage Userpilot Subscription and Add-ons Manage your Userpilot plan and any add-ons across all your applications from the **Billing** section of the Admin Console. Here, you can view and update key subscription details, including: * Monthly Active User (MAU) usage across applications * Organization information * Recent invoices In the **Add-ons** tab, you can enhance your plan by enabling additional features, which will be applied to all connected applications. ![Add-ons tab in the Admin Console billing section](https://mintcdn.com/userpilot/nVhZLATAUfLtc8CV/admin-console/assets/images/2026-07-01-admin-console-add-on-usage-limits.png?fit=max\&auto=format\&n=nVhZLATAUfLtc8CV\&q=85\&s=574412efab837217ed058c9b4bd9c03c) # Overview Source: https://docs.userpilot.com/agent-a-overview See how people are using your AI agents, where conversations succeed or break down, and how agent usage connects to the rest of your product data. > **Set-up Guidance** > > For information on connecting your agent data to Userpilot, go to [Agent SDK Installation](/agent-sdk-installation). ## Overview Agent Analytics extends Userpilot's analytics to your AI agents. Instead of digging through raw chat logs or a separate LLM dashboard, it gives you one place to answer questions like: How many people are talking to the agent? What are they trying to do? Where is it failing them? And is using the agent connected to people sticking around? ## Use Cases * **Prove agent ROI** — connect agent usage to activation, adoption, and retention instead of treating the agent as a black box. * **Spot where the agent is failing users** — surface fallback responses, failed actions, escalations, and frustrated users without reading every transcript. * **Understand what people actually use the agent for** — see the topics and entry points driving conversations, and feed that into your roadmap. * **Investigate a specific issue** — jump from a topic, a failure signal, or a user's profile straight into the full conversation transcript. * **Report and segment on agent usage** — bring agent conversation data into Report Builder, Segments, Sessions, and Engagement alongside the rest of your product data. ## Getting Started To get started, simply navigate to the Agent Anlytics tab under **Agents** in the main **Analytics** navigation, alongside **Saved Agent Views**. ![Image](https://mintcdn.com/userpilot/AZ3arJet3uUVYold/images/image-52.png?fit=max\&auto=format\&n=AZ3arJet3uUVYold\&q=85\&s=2a52952f0d6d02331591bf7fe071b69b) ## Filters and controls To narrow down the data that surfaces within the dashboard, specify conditions that you're particularly interested in. * **Time range** – select a preset range or a custom date range. * **Comparative period** – compare the selected range to the previous period, or to a custom period of your choosing. * **Agent** – filter to one agent or several. Defaults to **All agents**. * **Conditions** – use the same user and company filters you rely on elsewhere in Userpilot, or conversation filters based on the metadata captured on the `Conversation started` event — topic, trigger, feedback, and more. ## Key stats ![Image](https://mintcdn.com/userpilot/AZ3arJet3uUVYold/images/image-53.png?fit=max\&auto=format\&n=AZ3arJet3uUVYold\&q=85\&s=06ab545fa362c3c0673313287829930c) Seven key stats give you an at-a-glance read on how your agent is performing — who's using it, how satisfied they are, and whether it's bringing them back. Click into any stat to see exactly which conversations or users are behind the number. | Stat | What it measures | | :- | :- | | **Unique users** | The number of people who had at least one conversation with the agent in the selected period. | | **Unique companies** | The number of companies with at least one conversation with the agent in the selected period. | | **Conversations** | The total number of conversations held with the agent. | | **Prompts** | The total number of prompts users entered into the agent. | | **Satisfaction rate** | The percentage of submitted ratings that are positive (thumbs up ÷ total ratings). | | **Frustrating conversations** | The number (and share) of conversations where at least one AI-detected frustration signal was found. | | **Retention rate** | The percentage of users who came back and used the agent again at least once within the selected range. | ## Reports ![Image](https://mintcdn.com/userpilot/AZ3arJet3uUVYold/images/image-54.png?fit=max\&auto=format\&n=AZ3arJet3uUVYold\&q=85\&s=217ce665b9d16d72f26f0bdc07e454ae) Beyond the key stats, a set of reports helps you dig into agent usage in more depth: * **Users / Companies** Identify exactly who's getting the most value from the agent, whether you care about individual power users or which accounts are relying on it most. * **Entry points** Understand what's actually driving people to the agent — which pages or URLs are prompting the conversation — so you know where it's earning its place in the experience. * **Feedback** See how satisfied people are, broken down by topic, so you can tell which use cases the agent is nailing and which need work. This builds as your agent collects feedback. * **Frustrations** Understand what's frustrating your users and why, broken down by topic and by the specific behaviour pattern driving it. Patterns the AI detects include: | Signal | What it looks like | | - | - | | Profanity or offensive language | Swearing or abusive language directed at the agent. | | Strong negative sentiment | Explicit frustration or anger, e.g. "I hate this," "this is broken." | | Blaming the agent or product | Direct accusations, e.g. "you messed this up." | | ALL-CAPS emphasis | Sentences typed in all caps. | | Excessive punctuation | Repeated punctuation, e.g. "why??!!" | | Exaggerated letter repetition | Stretched-out words, e.g. "whyyyy." | | Repeated question rephrasing | The same question asked again in different words. | | Direct correction or rejection | Explicitly telling the agent it's wrong. | | Confrontational questioning | Aggressive "why" or "how" challenges. | * **Path of interaction** See what leads people to the agent and what they do right after, so you can tell whether it's solving problems in the flow of work or pulling people out of one. * **Conversation length** Gauge how much effort conversations take, by prompt count and duration — a useful signal for spotting overly long or complicated interactions. * **Agent impact on retention** Understand whether talking to the agent is connected to people coming back, by comparing retention for users who did and didn't interact with it. ## Saving and sharing a view Save a view when you want to come back to a specific cut of the data — a particular agent, time range, or set of conditions — without rebuilding it each time. Saved views can be organized into folders and favorited for quick access. Share a view when you want teammates, or people outside your account, to see the same analysis. ![Image](https://mintcdn.com/userpilot/AZ3arJet3uUVYold/images/image-55.png?fit=max\&auto=format\&n=AZ3arJet3uUVYold\&q=85\&s=e44f77a9738fd4ecf7b5b42877f25292) ## Who can access Agent Analytics Everyone with access to Userpilot can see the general Agent Analytics screens — Overview, Topics, and Failure Signals — regardless of role. Access to individual conversations (viewing, sharing, deleting) and to managing connected agents is controlled separately, under **Settings > Roles > Analytics > Agent Analytics**. ## Using Agent Analytics data elsewhere in Userpilot Every agent interaction is tracked as a `Conversation started` event, the same way any other event is in Userpilot — which means it's available outside of Agent Analytics too: * **Segments and Conditions**, to build audiences based on agent usage. * **Report Builder**, to build custom reports. * **Sessions**, as a key event filter. * **Engagement (flows, banners, etc.)**, in trigger settings and audience conditions. The event carries rich metadata you can filter and report on, including: | Property | Description | | - | - | | Agent / Agent ID | The agent involved, by name and by a stable ID that doesn't change if the agent is renamed. | | Length | Number of prompts in the conversation. | | Duration | Total time the user spent waiting for agent replies (idle pauses between exchanges are not counted). | | Topic | The topic the conversation was classified into. | | Feedback | Positive or negative, if given. | | Frustration signal / Failure signal | The specific signal identified, if any. | | Platform | Desktop or mobile. | | Version / Model | The agent version and underlying LLM model in use at the time. | # Agent Conversation Transcripts Source: https://docs.userpilot.com/agent-conversation-transcripts Read the full transcript of any agent conversation — exactly what the user said, how the agent replied, and any signals detected along the way. ## Overview The Conversation Transcript View gives you a full-screen read of a single agent conversation, laid out alongside the user's context (page, company, platform) and the specific signals detected within it. ## Use Cases * **Investigate a support case or complaint** by reading the exact exchange between a user and the agent. * **Audit agent quality** by reviewing conversations flagged with a failure or frustration signal. * **Verify a session replay or export a conversation** for further review outside Userpilot. ## Getting Started You can open a full conversation transcript from three places: * A conversation row inside a **Topics** slideout. * A conversation row inside a **Failure signals** slideout. * A conversation row in a user's Conversations tab on their profile. ## What you can do from here 1. **Follow the exchange exactly as it happened** — every prompt and reply in order, including if the user changed page mid-conversation, and how long the agent took to respond to each message. 2. **Understand who you're looking at, without leaving the page** — the user's identity, company, platform, and location sit alongside the conversation, and you can jump straight to their profile. 3. **Tell whether this is a one-off or a pattern** — see how many other conversations match the same topic or failure signal, or pull up everything else this user has talked to the agent about. 4. **Jump straight to the moments that matter** — filter down to just the positive or negative feedback, frustration, failure, or slow-response moments in the conversation, instead of reading the whole thing to find them. 5. **Search within a long conversation** to find a specific word or phrase without scrolling. ![Image](https://mintcdn.com/userpilot/AZ3arJet3uUVYold/images/image-61.png?fit=max\&auto=format\&n=AZ3arJet3uUVYold\&q=85\&s=d207a0c81c25387274c352cd108a0954) * Signals identified throughout a conversation include: | Signal | What it flags | | :- | :- | | Slow response | The agent took longer than usual to reply. | | Positive feedback | The user gave positive feedback on a reply. | | Negative feedback | The user gave negative feedback on a reply. | | Frustration signal | AI-detected frustration, e.g. offensive language. | | Failure signal | AI-detected failure, e.g. a fallback response. | ## Data capture and privacy settings From the [Data Capture & Privacy Settings](https://run.userpilot.io/settings/data-capture-privacy) page, you can turn capture of conversation content on or off separately for production and staging, and set rules to keep sensitive data out of what's stored — useful if your agent might encounter things like card numbers or other PII in the course of a conversation. * **Exclude keywords** – define a keyword or phrase to watch for. Any message that matches is masked within the conversation — since a keyword alone can't reliably bound where a sensitive value starts and ends — and you can scope this to user messages, agent messages, or both. ## FAQs > **Why don't I see a "View Replay" button?** > > It only appears when a session replay actually exists for that conversation. If replay wasn't enabled, or the conversation is too old, there won't be one to view. > **Who can share or delete a conversation?** > > This is controlled by your role's permissions under Settings > Roles > Analytics > Agent Analytics. Account Owners and Admins have both by default; ask your account admin if you need access. > **Can I export a single conversation?** > > Yes — use **Export data** from the Actions menu in the transcript view. # Agent SDK Installation Source: https://docs.userpilot.com/agent-sdk-installation Install Agent Analytics tracking, send AI agent messages and feedback, and configure conversation privacy controls in Userpilot. > **Agent Analytics** measures your AI agent's conversations inside your product: who uses the agent, what they ask, how satisfied they are, and where the agent fails. You send the conversation as it happens; Userpilot automatically derives **topics, frustration signals, failure signals, and slow-response flags** — you never classify anything yourself. Everything ties to the same user identity as the rest of your Userpilot analytics, so conversations show up on user profiles and in Segments, Reports, Sessions, and session replays. # Before you start You need two things you almost certainly already have: 1. The **Userpilot SDK snippet** installed on your pages. 2. A call to `userpilot.identify(userId, ...)` — agent events attach to this user. Events fired before identify are queued locally and sent right after it. # Step 1 — Turn the module on The agent module is off by default. Enable it in your Userpilot settings object: ```jsx theme={null} window.userpilotSettings = { token: "YOUR_APP_TOKEN", agent: { enabled: true // master switch for agent analytics } }; ``` # Step 2 — Report messages as they happen There is **no "start conversation" call**. The first message you send with a new `conversationId` creates the conversation automatically — just use your own chat-session id. **When the user sends a prompt:** ```jsx theme={null} userpilot.agent.trackMessage({ conversationId: "conv-8412", // your chat/session id (required) messageId: "msg-1", // your message id (recommended for retries) agentId: "support-bot", // stable agent id (required on every message) role: "user", // "user" or "agent" (required) text: "How do I export my data?", // Send these on the FIRST message of each conversation: agentName: "Support Bot", trigger: "Widget clicked", // what opened the chat platform: "web" }); ``` **When the agent replies:** ```jsx theme={null} userpilot.agent.trackMessage({ conversationId: "conv-8412", messageId: "msg-2", agentId: "support-bot", role: "agent", text: "You can export from Settings → Data…", responseTimeMs: 2400, // optional: powers slow-response detection model: "gpt-4o" // optional }); ``` Optional per-message extras: `inputTokens`, `outputTokens`, `metadata` (your own string, number, boolean, or null values), `ts` (epoch ms). If you omit `messageId`, the SDK generates one; provide your own for safe retries and to link feedback to a reply. > The browser SDK attaches page, browser, device, and session context automatically. Userpilot derives topic, frustration, and failure classifications from the conversation data; do not send those fields yourself. ## Step 3 — Report thumbs up / down ```jsx theme={null} userpilot.agent.trackFeedback({ conversationId: "conv-8412", value: "positive", // "positive" or "negative" (required) ratedMessageId: "msg-2", // which reply was rated (optional) reason: "Solved it immediately" // optional free text }); ``` That is the whole client API: **two calls**. No start, no end — duration and prompt counts are computed from the messages themselves. # Send agent events from your backend (optional) You can send agent events from your backend instead of the browser SDK, or use both. This is useful when tokens, model names, response times, and full replies are only available where inference runs. Send events over HTTP to `POST /v1/agent/events` with these headers: * `Authorization: Token YOUR_WRITE_TOKEN` (your account write token — same as `/v1/track`) * `Content-Type: application/json` Body — a single item or a batch of up to **100**: ```json theme={null} { "events": [ { "act": "message", "user_id": "u-123", "conversation_id": "conv-8412", "message_id": "msg-2", "role": "agent", "agent_id": "support-bot", "text": "You can export from Settings…", "model": "gpt-4o", "input_tokens": 512, "output_tokens": 128, "response_time_ms": 2400 } ] } ``` Rules of thumb: * `user_id` **must be the same id you pass to** `userpilot.identify` — that is what joins conversations to profiles and segments. Anonymous users are client-transport-only. * Invalid items are rejected with a `422` and per-item errors; valid requests return `202` and are processed asynchronously. * Optionally pass `"context": { "pathname", "hostname", "session_id" }` — forward the SDK session id if you want session-replay links on server-sent conversations. > **Recommended setup (hybrid)** > > Your backend sends all `message` events (it owns text, tokens, model, and latency); the browser SDK sends `feedback` (it owns page and session context). If both sides report the *same* message, use the same `message_id` on both and send `text` from only one side. The reports merge into one message, so retries and dual reporting do not double-count it. # Privacy controls Choose how conversation text is handled before you start sending it. These controls apply to message `text` and optional feedback `reason`; IDs, roles, and ratings are still sent. | Control | What happens | Where it applies | | :- | :- | :- | | `agent.captureText: false` | Drops all free-text fields before they leave the browser. | Browser SDK only | | `agent.redaction` callback | Replaces sensitive parts of text before they leave the browser. | Browser SDK only | | **Enable capture of conversation content** | Turns text capture on or off for an environment. Turning it off also prevents the browser SDK from sending text after it receives the setting. | Browser SDK and backend events | | **Mask patterns** | Replace matched spans with `[REDACTED]`. | Browser SDK and backend events | | **Excluded keywords** | Hide the entire matching user or agent message. Feedback reasons are not excluded. | Browser SDK and backend events | To redact text in the browser, add a `redaction` callback to `window.userpilotSettings.agent`: ```jsx theme={null} window.userpilotSettings = { token: "YOUR_APP_TOKEN", agent: { enabled: true, redaction: (text, field) => text.replace(/internal-project-name/gi, "[REDACTED]") } }; ``` The callback receives the original string and its field name (`"text"` or `"reason"`) and must return the string to send. In this example, `"Show internal-project-name activity"` becomes `"Show [REDACTED] activity"`. The callback runs for both user prompts and agent replies, as well as feedback reasons, when text capture is enabled. It does not inspect `metadata` or process events posted directly by your backend. Admin controls are configured in Userpilot without code and enforced at ingestion for both producers. Mask patterns and excluded keywords are also sent to the browser SDK so it can apply them before sending or queueing text. For backend events, redact before posting if the original text must not reach Userpilot; admin masking takes place after the HTTP request arrives. If text capture is off, usage and feedback ratings still work, but text-based AI insights such as topics and frustration analysis are unavailable. # What you'll see in Userpilot | Where | What | | :- | :- | | Agent Analytics dashboard | Users, conversations, prompts, satisfaction, frustrations, retention, entry points | | Topics / Failure signals tabs | Conversations auto-grouped by AI (you can add your own topics) | | User profile → Conversations | Conversations and their transcripts, with redacted or hidden text where privacy controls apply. If text capture is off, there is no full transcript. | | Segments / Reports / Sessions / Engagement | The **Conversation started** event with agent, trigger, topic, feedback, and more as filters | > **Good to know** > > Conversations that were opened but never got a message are not tracked, and the SDK caps agent events at 5,000 per page session as a safety valve. # Userpilot MCP Server Source: https://docs.userpilot.com/ai/userpilot-mcp-server Connect AI assistants like Claude to your Userpilot account to explore analytics, look up users and companies, and create segments, reports, and surveys. ## Overview The Userpilot MCP server connects AI assistants to your Userpilot account. Once connected, you can ask questions in plain language, such as "How did my signup funnel perform last month?", and the assistant reads the answer directly from your Userpilot data. You can also ask it to create things for you, such as segments, reports, dashboards, and survey drafts. MCP (Model Context Protocol) is an open standard that AI tools use to talk to other products. Userpilot hosts the server for you: you connect from the AI tool you already use and sign in with your regular Userpilot login. There is nothing to install or configure inside Userpilot. ## Use Cases * **Ask analytics questions**: "What's the completion rate of my onboarding funnel this quarter?" or "Show retention for users who used the export feature." * **Explore users and companies**: "Which companies were most active this week?" or "Summarize this user's recent activity." * **Build as you think**: "Create a segment of users who completed onboarding but never opened reports" or "Build a dashboard for my activation metrics." * **Draft surveys**: "Draft a short survey asking users why they canceled." You review and publish it in Userpilot. * **Look things up while you work**: "What audience does the 'New user tour' flow target?" or "List the events we track on the checkout page." ## What you need * A Userpilot account. The MCP server is available on all plans, and your plan's feature limits apply the same way they do in the app. * An AI tool that connects to MCP servers. Claude lists Userpilot in its connectors directory. ChatGPT, Cursor, Microsoft 365 Copilot, and other tools connect with the server address. The server address is: ```text theme={null} https://mcp.userpilot.io/mcp ``` ## Connect your AI tool **Claude** Userpilot is listed in the [Claude connectors directory](https://claude.ai/directory/userpilot). You connect it once in Claude on the web or in the Claude desktop app, and it then works in the Claude mobile apps and in Claude Code too. **1. Find Userpilot in the directory** Go to **Customize → Connectors** and select **Discover**. In the desktop app, select **Customize** in the sidebar, then **Connectors**. Search for "Userpilot", then select **Userpilot**. **2. Connect Userpilot** Select **Connect to Claude**. On Team and Enterprise plans, an organization Owner adds Userpilot once with **Connect for your team**. Members then connect with their own Userpilot login. If Userpilot isn't added for your organization yet, you see **Request** or a note to ask an Owner instead. **3. Sign in and choose permissions** Claude opens the Userpilot sign-in. Log in as you always do (SSO included), tick the access you want to grant, and approve. ![Choosing what the assistant can access](https://mintcdn.com/userpilot/6zF71bmmLMTEchXr/images/mcp/permission-screen.png?fit=max\&auto=format\&n=6zF71bmmLMTEchXr\&q=85\&s=1515dda6ebb272e1b7eaaf7e524b6af7) > [!TIP] > > Userpilot shows as **Connected** in your connectors. **4. Turn it on in a chat** In a chat, select **+** in the message box, then **Connectors**, and switch on Userpilot. Claude may ask for your approval before it uses a Userpilot tool. Select **Allow once** or **Always allow**. > [!TIP] > > Claude can now use Userpilot in that chat. **Claude Code** If you sign in to Claude Code with your Claude account, the Userpilot connector you connected in Claude is already available. Run `/mcp` to see it. Claude Code doesn't load your Claude connectors when it runs with an API key or through a cloud provider such as Amazon Bedrock. In that case, add the server yourself: **1. Add the server** ```bash theme={null} claude mcp add --transport http userpilot https://mcp.userpilot.io/mcp ``` **2. Authenticate** Run `/mcp` inside Claude Code, select **userpilot**, and complete the browser sign-in and permission screen. > [!TIP] > > Claude Code lists the Userpilot tools once the connection is active. **ChatGPT** Custom connectors are available on paid ChatGPT plans and need developer mode switched on. On Business and Enterprise plans, a ChatGPT workspace owner may need to allow custom connectors first. **1. Turn on developer mode** Open **Settings → Apps & Connectors → Advanced settings** and switch on **Developer mode**. ![Turning on developer mode in ChatGPT settings](https://mintcdn.com/userpilot/6zF71bmmLMTEchXr/images/mcp/chatgpt-developer-mode.png?fit=max\&auto=format\&n=6zF71bmmLMTEchXr\&q=85\&s=6ce09616480d7f561cac2e61e9912207) **2. Add the connector** Still under **Apps & Connectors**, create a new connector. Name it "Userpilot", paste the server address above into the **MCP Server URL** field, and choose **OAuth** as the authentication method. ![Adding the Userpilot connector in ChatGPT](https://mintcdn.com/userpilot/6zF71bmmLMTEchXr/images/mcp/chatgpt-add-connector.png?fit=max\&auto=format\&n=6zF71bmmLMTEchXr\&q=85\&s=0a57a3ec2b80468305f41e75cc364be0) **3. Sign in and choose permissions** Save the connector. Your browser opens the Userpilot sign-in — log in as you always do, then tick the access you want to grant and approve. **4. Enable it in a chat** Open a new conversation and switch on the Userpilot connector from the composer's tools menu. For deep research, pick Userpilot as a source. ![Switching on the Userpilot connector in a ChatGPT conversation](https://mintcdn.com/userpilot/6zF71bmmLMTEchXr/images/mcp/chatgpt-enable-in-chat.png?fit=max\&auto=format\&n=6zF71bmmLMTEchXr\&q=85\&s=e4a9b1fc050d773649bac5775d546332) > [!TIP] > > ChatGPT can now use Userpilot in that conversation. ChatGPT's connector settings change often. If the menus no longer match, the parts that stay the same are: create a **custom MCP connector**, choose **OAuth**, and use the server address above. **Cursor** **1. Add the server** Open **Cursor Settings → MCP → Add new MCP server**, or edit `~/.cursor/mcp.json` yourself: ```json theme={null} { "mcpServers": { "Userpilot": { "url": "https://mcp.userpilot.io/mcp" } } } ``` If that file already exists, add the `userpilot` entry inside the existing `mcpServers` object rather than replacing the file, so your other servers stay. To use Userpilot in one project only, create `.cursor/mcp.json` in that project folder instead. **2. Reload Cursor** Restart Cursor, or refresh the list from **Settings → MCP**. The `userpilot` server appears there. ![The Userpilot server listed in Cursor's MCP settings](https://mintcdn.com/userpilot/6zF71bmmLMTEchXr/images/mcp/cursor-mcp-list.png?fit=max\&auto=format\&n=6zF71bmmLMTEchXr\&q=85\&s=9a5678e353fc8a348ef6554858451316) **3. Sign in and choose permissions** Use the sign-in prompt on the `userpilot` entry. Your browser opens the Userpilot sign-in — log in as you always do, then tick the access you want to grant and approve. ![Choosing Userpilot permissions when connecting from Cursor](https://mintcdn.com/userpilot/6zF71bmmLMTEchXr/images/mcp/cursor-permissions.png?fit=max\&auto=format\&n=6zF71bmmLMTEchXr\&q=85\&s=3264e027f560dd03adb39696319b6c3b) **4. Check the tools loaded** Back in **Settings → MCP**, the `userpilot` entry lists the Userpilot tools it can use. ![Userpilot tools loaded in Cursor](https://mintcdn.com/userpilot/6zF71bmmLMTEchXr/images/mcp/cursor-tools.png?fit=max\&auto=format\&n=6zF71bmmLMTEchXr\&q=85\&s=fe1222c7a901dc843b5296b9c88437ff) > [!TIP] > > Cursor's agent can now use Userpilot in chat. **Microsoft 365 Copilot** Connecting Userpilot to Microsoft 365 Copilot goes through **Microsoft Copilot Studio**, where you build and publish the agent that end users chat with. **1. Create an agent** Sign in to [Copilot Studio](https://copilotstudio.microsoft.com/). On the **Home** page, under **Start building from scratch**, select **Create an agent**, then give it a name, for example "Userpilot". ![Creating a new agent from Copilot Studio's Home page](https://mintcdn.com/userpilot/KrqTowuXyuKilH1d/images/mcp/copilot-studio-home.png?fit=max\&auto=format\&n=KrqTowuXyuKilH1d\&q=85\&s=e9b22e2c8f4d1d57ebb3721aa50cf3ce) If your organization already has an agent you want to use instead, open it from **Agents** in the left navigation and skip to the next step. **2. Add the MCP tool** In the agent, select **Tools** in the left navigation, then **Add a tool** and **New tool**. Search for `Model Context Protocol` and select it, and the **Add a Model Context Protocol server** dialog opens. ![Adding a new Model Context Protocol server in Copilot Studio](https://mintcdn.com/userpilot/KrqTowuXyuKilH1d/images/mcp/copilot-studio-mcp-wizard.png?fit=max\&auto=format\&n=KrqTowuXyuKilH1d\&q=85\&s=28a9ec1a6e87b8faf4fa49d2a9cc3cb7) Name it "Userpilot", write a short description, and paste the server address above into **Server URL**. Under **Authentication**, choose **OAuth 2.0**, then **Dynamic discovery**. Userpilot supports OAuth 2.0 dynamic client registration, so there's nothing else to configure. ![Choosing OAuth 2.0 with Dynamic discovery for the Userpilot MCP server](https://mintcdn.com/userpilot/KrqTowuXyuKilH1d/images/mcp/copilot-studio-mcp-oauth-dynamic.png?fit=max\&auto=format\&n=KrqTowuXyuKilH1d\&q=85\&s=7b61aea40406093722f07b034980af45) Select **Create**, then **Next**. **3. Sign in to Userpilot** On the **Add tool** dialog, select **Create a new connection**. Your browser opens the Userpilot sign-in: log in as you always do (SSO included), then tick the access you want to grant and approve. ![Choosing what the assistant can access](https://mintcdn.com/userpilot/6zF71bmmLMTEchXr/images/mcp/permission-screen.png?fit=max\&auto=format\&n=6zF71bmmLMTEchXr\&q=85\&s=1515dda6ebb272e1b7eaaf7e524b6af7) Select **Add to agent** to finish. > [!TIP] > > The Userpilot tool appears under the agent's **Tools** section. **4. Publish and add to Microsoft 365 Copilot** Select **Publish** at the top of the agent page, then **Publish** again to confirm. Go to **Channels**, select the **Teams and Microsoft 365 Copilot** tile, keep **Make agent available in Microsoft 365 Copilot Chat** checked, then select **Add channel**. ![Enabling the Teams and Microsoft 365 Copilot channel for the agent](https://mintcdn.com/userpilot/KrqTowuXyuKilH1d/images/mcp/copilot-studio-publish-teams-m365.png?fit=max\&auto=format\&n=KrqTowuXyuKilH1d\&q=85\&s=c6378271ef4046d3bedc734df7733da6) Once connected, open [Microsoft 365 Copilot](https://copilot.cloud.microsoft/), enter **@**, select your agent from the list, and start asking it about your Userpilot data. > [!TIP] > > Microsoft 365 Copilot can now use Userpilot when your agent is mentioned. **Other AI tools** Any tool that supports remote MCP servers with OAuth sign-in can connect: 1. Add a remote (HTTP) MCP server in the tool's settings and paste the server address above. 2. When the tool prompts you, sign in with your Userpilot login and approve the permissions. The exact menu names vary by tool. Look for "MCP servers", "connectors", or "integrations" in its settings. ## Choose what the assistant can access When you connect, Userpilot shows a permission screen. You decide what the assistant can see and change: | Permission | What it allows | | - | - | | Read your product data, analytics, and reports | The foundation, always required: reports, dashboards, events, pages, features, flows, surveys, segments, and analytics results. | | Read individual user and company profiles | Profile details and activity for specific users and companies. Leave it unchecked to keep individual records out of AI conversations. | | Read session replay data | Session replay lists and activity summaries. | | Create and update user segments | New segments, and changes to existing ones. | | Create and update reports and dashboards | New reports and dashboards, and changes to existing ones. | | Create and update surveys (saved as drafts) | Survey drafts for you to review. Nothing reaches your users until you publish. | | Keep this connection active without re-login | Stay signed in between conversations. Without it, access ends about 15 minutes after each sign-in. | A few things to know: * **You can change your mind later.** To grant a permission you skipped, connect again from your AI tool: the permission screen appears and remembers what you approved before. * **Write permissions need the read permission.** The screen enforces this automatically. * **Changes are real.** Segments, reports, and dashboards the assistant creates or updates take effect immediately, just as if you made the change in the app. ## What the assistant can and can't do With the permissions you grant, the assistant can: * Run and read analytics: funnels, trends, retention, paths, saved reports, and dashboards. * Look up your product setup: flows, surveys, events, pages, features, segments, and more. * Read user and company lists and profiles. * Create and update segments, reports, dashboards, and survey drafts. * Go beyond the basics: when a question needs it, the assistant can search hundreds of additional read-only operations. It can never: * Build or publish in-app content such as flows and banners (content building stays in the Userpilot builder). * Delete anything. * Access team management, billing, account settings, or other administration areas. * Do anything your own Userpilot role doesn't allow. ### Working with multiple apps If your account has more than one app, the assistant can list the ones you have access to and ask which one to use. It remembers your choice for the connection, and you can tell it to switch at any time. ## Security and privacy * **Your login, your permissions.** The connection runs under your own Userpilot user. The assistant can only see and change what your role allows, in the apps you have access to. * **You control the scope.** The permission screen decides what is reachable; everything outside it is refused by Userpilot's servers, not by the assistant's judgment. * **No destructive access.** Delete operations and administration areas are not available through the connection at all. * **Built-in limits.** Rate limits automatically contain runaway or abusive usage. * **Disconnect anytime.** Remove the connector in your AI tool to stop new access. Without the "keep active" permission, access also expires on its own shortly after each sign-in. > [!NOTE] > > Data the assistant reads through this connection is processed by your AI provider (for example, Anthropic for Claude) under that provider's terms. Review your organization's AI policies before connecting tools to customer data. ### FAQs > **Which AI tools work with the Userpilot MCP server?** > > Any tool that supports remote MCP servers with OAuth sign-in. Claude, Claude Code, ChatGPT, Cursor, and Microsoft 365 Copilot are documented step by step above. For other tools, use the server address and follow the tool's own instructions for adding an MCP server. > **The assistant says it wasn't granted a permission. What do I do?** > > You skipped that permission when connecting. Connect again from your AI tool. The permission screen appears with your earlier choices remembered; tick the missing one. > **The assistant says a workspace is no longer accessible.** > > Your access to that app changed. Ask the assistant to list your workspaces and pick one you can access. > **Can the assistant change my data without me knowing?** > > Only within the write permissions you granted, and never beyond them: segments, reports, dashboards, and survey drafts. It cannot delete anything, publish content, or touch account settings. If you grant no write permissions, the connection is read-only. > **Who on my team can connect?** > > Anyone with a Userpilot login. On Claude Team and Enterprise plans, an organization Owner adds Userpilot from the directory once. Each member then connects and signs in as themselves, and their connection mirrors their own role and app access. > **The assistant doesn't see new or updated Userpilot tools.** > > Your AI tool keeps its own copy of the tool list. In Claude, open the Userpilot connector's settings and use **Refresh tools**; disconnecting and reconnecting alone does not refresh the list. > **How do I revoke a connection completely?** > > Remove the connector in your AI tool. If you need a connection revoked on the Userpilot side (for example, for a teammate who left), contact support. # Welcome to Lia Source: https://docs.userpilot.com/ai/welcome-to-lia Chat with Lia, Userpilot's AI agent, to analyze your product data, run ready-made skills, and create reports, dashboards, checklists and surveys. Lia helps you to the insight and take action quickly. ## Overview Lia is Userpilot's built-in AI agent. It reads your product data and helps you understand what's happening, why it's happening, and what to do next. You can ask Lia questions in plain language, or run ready-made analyses called skills. Lia works with the data already in your Userpilot account, including events, features, users, companies, segments, flows, surveys, and NPS. There's nothing extra to set up. ![Lia Home Chat 1](https://mintcdn.com/userpilot/vLNSmwQR_UdVVCBb/Lia-home-chat-1.png?fit=max\&auto=format\&n=vLNSmwQR_UdVVCBb\&q=85\&s=3151670bbc251af1e1c2e3cd8a9bce1c) ## What You Can Do with Lia * **Ask questions about your product.** For example, "How is activation trending this month?" or "Which accounts are using Invoices the most?" * **Run in-depth analyses.** Use skills to get a structured breakdown of retention, adoption, conversion, sentiment, and more. * **Get recommendations.** Lia points out drop-offs, risks, and opportunities, then suggests next steps. * **Build from the results.** Ask Lia to create a segment, report, dashboard, or survey based on what it found. ## Chatting with Lia 1. Open Lia from **the top of the navbar**. 2. Type your question in the chat box and press **Enter**. 3. Lia pulls the relevant data and replies with an answer, key numbers, and suggested next steps. 4. Keep the conversation going with follow-up questions, like "Break that down by plan" or "Compare it to last quarter." **Tip:** Be specific. Mention the feature, flow, segment, or time range you care about for more accurate answers. ![Lia Chat](https://mintcdn.com/userpilot/vLNSmwQR_UdVVCBb/Lia-chat.png?fit=max\&auto=format\&n=vLNSmwQR_UdVVCBb\&q=85\&s=f1f5446aa6b32fb9e1d418c5696dee50) ### Using Skills Skills are pre-built analyses that follow a proven structure, so you get a complete, consistent answer every time. There are two ways to run a skill: * **Ask naturally.** If your question matches a skill, Lia runs it for you. For example, "Why are users churning?" runs the Analyze retention skill. * **Run it directly.** Type **/** in the chat to see all available skills, then pick one (for example, `/analyze-activation`). ![Lia Skills Command](https://mintcdn.com/userpilot/vLNSmwQR_UdVVCBb/Lia-skills-command.png?fit=max\&auto=format\&n=vLNSmwQR_UdVVCBb\&q=85\&s=0c46a60ae30ff82368191bd5ec332c64) ### Available Skills | Skill | Command | What it does | | :- | :- | :- | | Analyze activation | `/analyze-activation` | Shows how your activation rate is trending, where users drop off on the path to activation, and how long it's taking them to get there. | | Analyze feature adoption | `/analyze-feature-adoption` | Shows how many users are reaching the value moment of a specific feature, where they drop off on the way there, and whether the ones who adopt keep coming back. | | Trending users and companies | `/trending-users` | Surfaces the users and accounts whose engagement is accelerating fastest, identifies what's driving the rise, and turns the insight into an action. | | Declining users and companies | `/declining-users` | Surfaces the users and companies losing momentum fastest, identifies what stopped and when, and flags which declines signal genuine churn risk versus natural dips. | | Analyze feature launch | `/analyze-feature-launch` | Measures how a feature is landing in the weeks after launch: adoption velocity, where users drop off on the path to value, whether any attached in-app content is working, and what to do next. | | Analyze segment | `/analyze-segment` | Gives a complete behavioral picture of a segment: who they are, what they do, how they retain, and what makes them meaningfully different from your overall user base. | | Segments comparison | `/segment-comparison` | Puts two or more segments side-by-side across the same behavioral dimensions and surfaces what actually separates them, not just the numbers, but what the gap means. | | Analyze account health | `/analyze-account-health` | Gives a single-account view across usage trends, feature breadth, key-user activity, sentiment, and contract context: everything needed to assess health and identify risk or expansion signals before a call or decision. | | Analyze retention | `/analyze-retention` | Shows how well users return to the product over time, finds where the biggest drop-off happens, and identifies which behaviors separate users who retain from those who don't. | | Summarize user sentiment | `/summarize-sentiment` | Reads across your NPS scores and survey responses to surface how users feel, what themes are driving sentiment, and whether things are getting better or worse. | | Analyze a survey | `/analyze-survey` | Breaks down a specific survey's results: completion rate, score distributions, question-level drop-off, open-text themes, and how results compare to prior runs if the survey has been run before. | | Conversion analysis | `/conversion-analysis` | Analyzes how users are converting through a defined journey: step-by-step conversion rates, drop-off points, time to complete, segment differences, and the actual paths users take around the critical step. | | Analyze dashboard | `/analyze-dashboard` | Reads across all the reports and widgets in a dashboard, finds the overall narrative, identifies what's healthy vs. concerning, and prioritizes what needs action. | | Analyze engagement | `/engagement-analysis` | Evaluates how a piece of Userpilot content (a flow, checklist, banner, or spotlight) performed and whether it moved the behavior it was designed to influence. | ### Key Notes * Lia only uses data from your own Userpilot account. Your data is isolated and never shared with other customers. * Lia can only see what's tracked in Userpilot. If a feature or event isn't tagged, Lia can't analyze it. * Surveys created by Lia are saved as drafts. Review and publish them yourself when you're ready. * Segments, reports, and dashboards created by Lia are saved to your account right away. ### FAQs > **Do I need to set anything up to use Lia?** > > No. Lia works with your existing Userpilot data out of the box. > **Can Lia make changes to my account?** > > Lia can create segments, reports, dashboards, and draft surveys when you ask it to. It won't publish surveys or change live content on its own. > **What's the difference between chatting and using a skill?** > > Chat is best for quick questions and follow-ups. Skills are best when you want a complete, structured analysis of a specific topic. For any questions or concerns please reach out to [**support@userpilot.com**](mailto:support@userpilot.com) # Authentication Source: https://docs.userpilot.com/api-references/authentication Authenticate your API requests securely using API tokens. Userpilot APIs use API tokens to authenticate requests. You can view and manage your API key in the **Environment Page** of your Userpilot dashboard. ## How to Use Your API Key Include your API key in the `Authorization` header of every API request: ```http theme={null} -H 'Authorization: Token {{API_KEY}}' ``` > [!WARNING] > > Your API key carries many privileges. Keep it secure and do not share it in publicly accessible areas. ## Where to Find Your API Key 1. Log in to your Userpilot dashboard. 2. Go to the **Environment Page**. 3. Copy your API key from the API Key section. > [!NOTE] > > All API requests must be made over HTTPS. ## Security Best Practices * Never expose your API key in client-side code or public repositories. * Rotate your API keys regularly. * If you suspect your API key has been compromised, please reach out to [support@userpilot.com](mailto:support@userpilot.com) for regenerating it. # Bulk Identify/Update Companies Source: https://docs.userpilot.com/api-references/bulk-updates/companies Bulk identify and update company profiles using Userpilot's HTTP API for large-scale company data synchronization. The Bulk Identify Companies API allows you to create or update multiple company profiles in a single request. This is ideal for large-scale B2B data synchronization, initial company imports, or batch updates from your CRM or database systems. > [!WARNING] > > **PII Data Notice** > > Before passing any Personally Identifiable Information (PII) such as names, emails, or other sensitive user data to Userpilot: > > * **Security & Compliance:** Verify internally with your legal, security, and compliance teams that doing so aligns with your organization's data privacy policies and applicable regulations (e.g., GDPR, CCPA). > > * **Data Type Mapping:** Ensure correct data type mapping for all properties. Userpilot supports String, Numeric, and Date types. Make sure dates are in ISO8601 format and numeric values are properly formatted. Incorrect type mapping can cause issues with segmentation, analytics, and debugging. > > * **Debugging:** Proper data formatting is crucial for effective debugging and troubleshooting. Verify that your data maps correctly to Userpilot's expected formats before implementation. ## Endpoint ```http theme={null} [POST] https://analytex.userpilot.io/v1/companies/bulk_identify ``` > [!NOTE] > > The endpoint URL uses the `analytex` environment. For EU data residency, use > `analytex-eu` instead. See [Environment > Settings](https://run.userpilot.io/environment) for your specific endpoint. ## Headers | Header | Value | Required | | - | - | - | | `Content-Type` | `application/json` | Yes | | `Authorization` | `Token {YOUR_API_KEY}` | Yes | | `Accept` | `application/json, text/plain, */*` | Yes | ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `companies` | array | Yes | Array of company objects | | `company_id` | string | Yes | Unique identifier for the company | | `metadata` | object | No | Key-value pairs describing the company (primitives only) | ## JSON Payload Example ```json theme={null} { "companies": [ { "company_id": "company_001", "metadata": { "subscription_status": "active", "subscription_plan": "enterprise", "deal_size": "500000" } }, { "company_id": "company_002", "metadata": { "subscription_status": "trial", "subscription_plan": "basic" } } // ... Additional companies up to 10,000 total ] } ``` > [!TIP] > > **Optimize Your Payload**: Use the full 10,000 record limit to maximize processing efficiency. The example above shows the structure for individual records - you can include up to 10,000 such records in a single request. ## Response A successful call returns a job object: ```json theme={null} { "job_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "status": "queued", "submitted_at": "2025-02-02T12:35:10Z" } ``` > [!TIP] > > **Monitor Your Job**: Use the job ID from the response to track the status of your bulk update job. Remember that only one bulk update job (user or company) can run at a time. *** ## File Upload (NDJSON) For very large updates, you may upload NDJSON files containing company profiles. ### Endpoint ```http theme={null} [POST] https://analytex.userpilot.io/v1/companies/bulk_identify ``` ### Headers | Header | Value | Required | | - | - | - | | `Content-Type` | `multipart/form-data` | Yes | | `Accept` | `application/json, text/plain, */*` | Yes | | `Authorization` | `Token {YOUR_API_KEY}` | Yes | ### Request Body Submit the file using multipart/form-data. Include a key called `file` with your NDJSON file: ```bash theme={null} curl -X POST "https://analytex.userpilot.io/v1/companies/bulk_identify" \ -H "Authorization: Token {YOUR_API_KEY}" \ -H "Content-Type: multipart/form-data" \ -F "file=@/path/to/your/file.ndjson" ``` > [!TIP] > > Each line in the file should be a valid JSON object. > [!WARNING] > > File size is limited to 50 MB. Each request can contain up to 10,000 > companies. *** ## Rate Limits The Bulk Updates API implements rate limiting to ensure efficient processing of large-scale company data operations while maintaining system stability. These limits help optimize performance for bulk company profile updates and synchronizations. ### Rate Limit Details * **Job Limitation**: **One job at a time** - The system processes only one bulk update job at a time (either user or company bulk update) * **Company Processing**: **1,800 companies per minute** service consumption rate * **JSON Payload**: **Up to 10,000 records per request** * **File Upload**: **Up to 50 MB file size** ### Error Responses When you exceed these limits, the API returns: * **`409 Conflict`**: Returned when attempting to create a new job while another is in progress * **`413 Payload Too Large`**: Returned when file size exceeds 50 MB or payload exceeds 10,000 records ### Best Practices > [!TIP] > > **Check Job Status**: Always verify no existing bulk update jobs are running before initiating a new one. > > **Optimize Batch Sizes**: Use the full 10,000 record limit for JSON payloads to maximize efficiency. > > **Monitor Processing**: Track your company processing speed to stay within the 1,800 companies per minute limit. > > **Handle File Uploads**: For large datasets, use file uploads (up to 50 MB) instead of JSON payloads. *** ## Limitations * File size up to 50 MB (for file uploads). * JSON/NDJSON list up to 10,000 companies per request. * Only primitive types (string, number, boolean, null) are supported in metadata. * A company will not appear on the dashboard unless at least one user is associated with it. *** ## Best Practices * **Validate Your Data:** Ensure each record includes the required `company_id` and that metadata is formatted correctly. * **Monitor Jobs:** Always use the job monitoring endpoints to check the status of your bulk updates. * **Rate Limits and Retries:** If you experience rate limits or timeouts, batch your requests and monitor job statuses before submitting more. *** ## Troubleshooting * **Authentication Errors:** Verify your API token and that it is sent in the `Authorization` header. * **Invalid Payload:** Ensure your JSON/NDJSON is well-formed and required fields are present. * **Job Failures:** Use the job status endpoint to inspect error messages for failed records or processing issues. # Bulk Identify/Update Users Source: https://docs.userpilot.com/api-references/bulk-updates/users Bulk identify and update user profiles using Userpilot's HTTP API for large-scale user data synchronization. The Bulk Identify Users API allows you to create or update multiple user profiles in a single request. This is ideal for large-scale data synchronization, initial user imports, or batch updates from your CRM or database systems. > [!WARNING] > > **PII Data Notice** > > Before passing any Personally Identifiable Information (PII) such as names, emails, or other sensitive user data to Userpilot: > > * **Security & Compliance:** Verify internally with your legal, security, and compliance teams that doing so aligns with your organization's data privacy policies and applicable regulations (e.g., GDPR, CCPA). > > * **Data Type Mapping:** Ensure correct data type mapping for all properties. Userpilot supports String, Numeric, and Date types. Make sure dates are in ISO8601 format and numeric values are properly formatted. Incorrect type mapping can cause issues with segmentation, analytics, and debugging. > > * **Debugging:** Proper data formatting is crucial for effective debugging and troubleshooting. Verify that your data maps correctly to Userpilot's expected formats before implementation. ### Endpoint ```http theme={null} [POST] https://analytex.userpilot.io/v1/users/bulk_identify ``` > [!NOTE] > > The endpoint URL uses the `analytex` environment. For EU data residency, use > `analytex-eu` instead. See [Environment > Settings](https://run.userpilot.io/environment) for your specific endpoint. ### Headers | Header | Value | Required | | - | - | - | | `Content-Type` | `application/json` | Yes | | `Accept` | `application/json, text/plain, */*` | Yes | | `Authorization` | `Token {YOUR_API_KEY}` | Yes | ### Request Body | Field | Type | Required | Description | | - | - | - | - | | `users` | array | Yes | Array of user objects | | `user_id` | string | Yes | Unique identifier for the user | | `company_id` | string | No | Unique identifier for the company (if applicable) | | `metadata` | object | No | Key-value pairs describing the user (primitives only) | ### JSON Payload Example ```json theme={null} { "users": [ { "user_id": "user_001", "company_id": "comp001", "metadata": { "last_ticket_created": "2025-02-01T10:20:30Z", "tickets_created": "15", "tickets_resolved": "14", "tickets_pending": "1", "avg_resolution_time": "2h", "customer_satisfaction": "4.8", "escalation_count": "1" } }, { "user_id": "user_002", "company_id": "comp002", "metadata": { "last_ticket_created": "2025-01-31T09:15:00Z", "tickets_created": "8", "tickets_resolved": "7", "tickets_pending": "1", "avg_resolution_time": "3h", "customer_satisfaction": "4.5", "escalation_count": "0" } } // ... Additional users up to 10,000 total ] } ``` > [!TIP] > > **Optimize Your Payload**: Use the full 10,000 record limit to maximize processing efficiency. The example above shows the structure for individual records - you can include up to 10,000 such records in a single request. ### Response A successful call returns `202 Accepted` with a job object: ```json theme={null} { "job_id": "bulk:jobs:{APP_TOKEN}:9536d797-7039-4d30-8c11-ac437367d732", "status": "queued", "start_time": "2025-02-02T12:34:56.000000", "end_time": null, "total_rows": 0, "file_size": 84, "filename": "{APP_TOKEN}_identify_user_9536d797-7039-4d30-8c11-ac437367d732.ndjson", "links": "/v1/background_jobs/bulk:jobs:{APP_TOKEN}:9536d797-7039-4d30-8c11-ac437367d732", "type": "identify_user" } ``` > [!TIP] > > **Monitor Your Job**: Use the job ID from the response to track the status of your bulk update job. Remember that only one bulk update job (user or company) can run at a time. > [!NOTE] > > Row-level validation (e.g. a missing `user_id`) doesn't fail the request itself: the job is still accepted and queued. Invalid rows fail individually during processing; check the job status endpoint for a `reason_for_failure` field explaining what went wrong with each failed row. *** ## File Upload (NDJSON) For very large updates, you may upload NDJSON files containing user profiles. ### Endpoint ```http theme={null} [POST] https://analytex.userpilot.io/v1/users/bulk_identify ``` > [!NOTE] > > The endpoint URL uses the `analytex` environment. For EU data residency, use > `analytex-eu` instead. See [Environment > Settings](https://run.userpilot.io/environment) for your specific endpoint. ### Headers | Header | Value | Required | | - | - | - | | `Content-Type` | `multipart/form-data` | Yes | | `Accept` | `application/json, text/plain, */*` | Yes | | `Authorization` | `Token {YOUR_API_KEY}` | Yes | ### Request Body Submit the file using multipart/form-data. Include a key called `file` with your NDJSON file: ```bash theme={null} curl -X POST "https://analytex.userpilot.io/v1/users/bulk_identify" \ -H "Authorization: Token {YOUR_API_KEY}" \ -H "Content-Type: multipart/form-data" \ -F "file=@/path/to/your/file.ndjson" ``` > [!TIP] > > Each line in the file should be a valid JSON object. > [!WARNING] > > File size is limited to 50 MB. Each request can contain up to 10,000 users. ### Response A successful call returns the same job object shape as the JSON payload endpoint above, with one difference: `filename` reflects the name of the file you uploaded instead of an auto-generated name. *** ## Rate Limits The Bulk Updates API implements rate limiting to ensure efficient processing of large-scale user data operations while maintaining system stability. These limits help optimize performance for bulk user profile updates and synchronizations. ### Rate Limit Details * **Job Limitation**: **One job at a time** - The system processes only one bulk update job at a time (either user or company operations) * **User Processing**: **1,800 users per minute** service consumption rate * **JSON Payload**: **Up to 10,000 records per request** * **File Upload**: **Up to 50 MB file size** ### Error Responses The API returns the following error responses: * **`401 Unauthorized`**: Returned when the API token is missing, invalid, or isn't a write/admin token * **`409 Conflict`**: Returned when attempting to create a new job while another is in progress * **`413 Payload Too Large`**: Returned when file size exceeds 50 MB or payload exceeds 10,000 records ### Best Practices > [!TIP] > > **Check Job Status**: Always verify no existing bulk update jobs are running before initiating a new one. > > **Optimize Batch Sizes**: Use the full 10,000 record limit for JSON payloads to maximize efficiency. > > **Monitor Processing**: Track your user processing speed to stay within the 1,800 users per minute limit. > > **Handle File Uploads**: For large datasets, use file uploads (up to 50 MB) instead of JSON payloads. *** ### Limitations * File size up to 50 MB (for file uploads). * JSON/NDJSON list up to 10,000 users per request. * Only primitive types (string, number, boolean, null) are supported in metadata. *** ### Best Practices * **Validate Your Data:** Ensure each record includes the required identifiers (`user_id`) and that metadata is formatted correctly. * **Monitor Jobs:** Always use the job monitoring endpoints to check the status of your bulk updates. * **Rate Limits and Retries:** If you experience rate limits or timeouts, batch your requests and monitor job statuses before submitting more. *** ### Troubleshooting * **Authentication Errors:** Verify your API token and that it is sent in the `Authorization` header. * **Invalid Payload:** Ensure your JSON/NDJSON is well-formed and required fields are present. * **Job Failures:** Use the job status endpoint to inspect error messages for failed records or processing issues. * **Stuck Jobs:** If a job's processing time is taking much longer than expected, contact [support@userpilot.com](mailto:support@userpilot.com) for help. # Delete Users and Companies Source: https://docs.userpilot.com/api-references/delete/overview Delete users and companies from Userpilot to comply with data privacy regulations. Userpilot supports data deletion to comply with the [right to be forgotten](https://gdpr.eu/article-17-right-to-be-forgotten/) under [GDPR](https://gdpr-info.eu/). Under this right, a user can request to delete all their data. Userpilot supports this right for all users, not just those in Europe. You can use the Deletion APIs to schedule a job to delete user or company data. *** ## Authorization Userpilot API uses API tokens to authenticate requests. You can view your API key in the Environment Page. **Authentication Method:** Include your API key in the `Authorization` header: ```http theme={null} -H 'Authorization: Token {{API_KEY}}' ``` > [!NOTE] > > All API requests must be made over HTTPS. > [!WARNING] > > Your API key carries many privileges, so be sure to keep them secure! Do not share your secret API keys in publicly accessible areas. *** ## Version You must send the API version in the headers: ```http theme={null} -H 'X-API-Version: 2020-09-22' ``` *** ## Endpoints For most users, the HTTP API endpoint URL is `https://analytex.userpilot.io` as the examples show. If you are on Enterprise or EU hosting, refer to the Environment Page in the application to retrieve your dedicated endpoint. *** ### 1. Delete Users **Endpoint:** ```http theme={null} DELETE https://analytex.userpilot.io/v1/users ``` > [!NOTE] > > The endpoint URL uses the `analytex` environment. For EU data residency, use > `analytex-eu` instead. See [Environment > Settings](https://run.userpilot.io/environment) for your specific endpoint. **Headers:** | Header | Value | Required | | - | - | - | | `Content-Type` | `application/json` | Yes | | `Authorization` | `Token {YOUR_API_KEY}` | Yes | | `X-API-Version` | `2020-09-22` | Yes | **Request Body:** | Field | Type | Required | Description | | - | - | - | - | | `users` | array | Yes | List of user IDs to be deleted | **Example Request:** ```bash theme={null} curl -X DELETE https://analytex.userpilot.io/v1/users \ -H 'Content-Type: application/json' \ -H 'Authorization: Token ' \ -H 'X-API-Version: 2020-09-22' \ -d '{"users": ["user_id", "user_id2"]}' ``` *** ### 2. Delete Companies **Endpoint:** ```http theme={null} DELETE https://analytex.userpilot.io/v1/companies ``` > [!NOTE] > > The endpoint URL uses the `analytex` environment. For EU data residency, use > `analytex-eu` instead. See [Environment > Settings](https://run.userpilot.io/environment) for your specific endpoint. **Headers:** | Header | Value | Required | | - | - | - | | `Content-Type` | `application/json` | Yes | | `Authorization` | `Token {YOUR_API_KEY}` | Yes | | `X-API-Version` | `2020-09-22` | Yes | **Request Body:** | Field | Type | Required | Description | | - | - | - | - | | `companies` | array | Yes | List of company IDs to be deleted | **Example Request:** ```bash theme={null} curl -X DELETE https://analytex.userpilot.io/v1/companies \ -H 'Content-Type: application/json' \ -H 'Authorization: Token ' \ -H 'X-API-Version: 2020-09-22' \ -d '{"companies": ["company_id", "company_id2"]}' ``` *** ## Response A successful request will schedule your delete job and return a `202 Accepted` status with a message indicating how many users or companies have been scheduled for deletion. **Example Response:** ```json theme={null} { "message": "2 users have been scheduled for deletion" } ``` > [!NOTE] > > Deletions are scheduled to be executed every 24 hours (daily at 2:00 AM UTC). *** ## Rate Limits The Delete API has the following rate limit: * **Delete Operations**: **1 request every 2 seconds** for delete operations If you exceed the rate limit, the API will return a `429 Too Many Requests` error. > [!TIP] > > **Best Practice:** Implement exponential backoff and retry logic to handle rate limit errors gracefully. *** ## FAQs > **Do I need separate calls to delete a user and their company?** > > Yes, `DELETE /v1/users` and `DELETE /v1/companies` are separate endpoints with separate ID arrays (`users` vs `companies`); deleting a user does not automatically delete the company they belonged to, or vice versa. # List Subscription Topics Source: https://docs.userpilot.com/api-references/email-subscriptions/list-topics Retrieve the ID and title of every live email subscription type, so your systems can reference them when setting unsubscribe preferences. ## **Overview** This endpoint returns your live email subscription types with their IDs. Use it to look up the `topic_id` values you pass to [Set Subscription Preferences](./set-preferences). ## When to Use Use this endpoint when: * Building an integration that unsubscribes people from a specific subscription type * Mapping your CRM's own list names onto Userpilot subscription types * Confirming an ID before a bulk write You can also read a single ID straight from the dashboard. On [**Settings > Email > Subscriptions**](https://run.userpilot.io/settings/email-settings/email-subscriptions), open the action menu on a row and select **Copy topic ID**. ## Prerequisites * At least one email subscription type, created in [Settings > Email > Subscriptions](https://run.userpilot.io/settings/email-settings/email-subscriptions) * Userpilot API Key from [Settings > Environment](https://run.userpilot.io/environment) ## Endpoint ```http theme={null} [GET] https://analytex.userpilot.io/v1/email_subscription_topics ``` > [!NOTE] > > The endpoint URL uses the `analytex` environment. For EU data residency, use > `analytex-eu` instead. See [Environment > Settings](https://run.userpilot.io/environment) for your specific endpoint. ## Headers | Header | Value | Required | | - | - | - | | `Authorization` | `Token {YOUR_API_KEY}` | Yes | | `X-API-Version` | `2020-09-22` | No | These endpoints have no versioned behavior, so `X-API-Version` is accepted and ignored. Send it if you already send it everywhere else. ## Example cURL Command ```bash theme={null} curl -X GET https://analytex.userpilot.io/v1/email_subscription_topics \ -H 'Authorization: Token {YOUR_API_KEY}' \ -H 'X-API-Version: 2020-09-22' ``` ## Response ```json theme={null} { "topics": [ { "id": 3, "title": "Promotions and Marketing" }, { "id": 7, "title": "Product Newsletter" } ] } ``` > [!NOTE] > > Only live subscription types appear here. A subscription type still in draft has an ID and accepts writes, but you need to copy that ID from the dashboard. Two things are deliberately absent from this list: * **Unsubscribed from all** is a scope rather than a subscription type. Set it with `"all": true` instead of a `topic_id`. * Titles are never accepted as input anywhere in the API. Renaming a subscription type would quietly break your integration, so IDs are the only reference. ## Common Issues | Error | Cause | Solution | | - | - | - | | 401 Unauthorized | Invalid or suspended API key | Verify your key in Environment settings | | Empty `topics` array | No live subscription types | Create one, or link an existing draft to an email so it goes live | | An ID you expected is missing | That subscription type is still in draft | Copy the ID from the dashboard row menu | ## Related * [Set Subscription Preferences](./set-preferences) - Unsubscribe or re-subscribe addresses * [Manage unsubscribes](/in-app-engagement/emails/manage-unsubscribes) - The same job from the dashboard * [Webhooks](/integrations/webhooks) - Receive an event when someone unsubscribes in Userpilot # Set Subscription Preferences Source: https://docs.userpilot.com/api-references/email-subscriptions/set-preferences Unsubscribe or re-subscribe email addresses in Userpilot from your own systems, in batches of up to 1,000 records per request. ## **Overview** Use this endpoint to keep email subscription preferences in sync between Userpilot and your own systems. When a user unsubscribes or re-subscribes through your CRM, marketing platform, or product, you can automatically update their status in Userpilot without manually uploading a file. ## **When to Use** Use this endpoint when: * Your CRM or marketing platform is the source of truth for email subscription preferences. * You want subscription changes to sync automatically instead of relying on manual file uploads. * You need to unsubscribe email addresses even if they are not yet associated with a Userpilot user. For one-time updates that don’t require an integration, use the [**dashboard import**](/in-app-engagement/emails/manage-unsubscribes) instead ## Prerequisites * Userpilot API Key from [Settings > Environment](https://run.userpilot.io/environment) * The `topic_id` values you want to write to, from [List Subscription Topics](./list-topics) ![Screenshot 2026 08 12 At 17 02 58](https://mintcdn.com/userpilot/I5i5V35YXpNyv_2p/images/Screenshot-2026-08-12-at-17.02.58.png?fit=max\&auto=format\&n=I5i5V35YXpNyv_2p\&q=85\&s=b2d2619e71f04afa13de18257c2020d8) ## Endpoint ```http theme={null} [POST] https://analytex.userpilot.io/v1/email_subscription_preferences ``` > [!NOTE] > > The endpoint URL uses the `analytex` environment. For EU data residency, use > `analytex-eu` instead. See [Environment > Settings](https://run.userpilot.io/environment) for your specific endpoint. ## Headers | Header | Value | Required | | - | - | - | | `Content-Type` | `application/json` | Yes | | `Authorization` | `Token {YOUR_API_KEY}` | Yes | | `X-API-Version` | `2020-09-22` | No | These endpoints have no versioned behavior, so `X-API-Version` is accepted and ignored. Send it if you already send it everywhere else. Every response carries `x-ratelimit-limit`, `x-ratelimit-remaining`, and `x-ratelimit-reset`, so you can pace a large sync off the headers rather than guessing. ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `records` | array | Yes | Up to 1,000 records. See the fields below. | Each record takes: | Field | Type | Required | Description | | - | - | - | - | | `email` | string | Yes | The address to change. Matched without regard to case or surrounding spaces. | | `topic_ids` | array | Yes | The subscription types this record applies to. | | `all` | boolean | Yes | `true` applies the change to every email, including emails with no subscription type. | | `status` | string | No | `unsubscribed` or `subscribed`. Defaults to `unsubscribed`. | > [!NOTE] > > Specify `topic_ids`, set `all: true`, or include both. Each record must contain at least one of these options; records with neither will be rejected. ## Example ```json theme={null} { "records": [ { "email": "kevin@acme.com", "all": true, "status": "unsubscribed" }, { "email": "sara@globex.com", "topic_ids": [3, 7], "status": "unsubscribed" }, { "email": "p.lang@initech.com", "topic_ids": [7], "status": "subscribed" } ] } ``` ## Example cURL Command ```bash theme={null} curl -X POST https://analytex.userpilot.io/v1/email_subscription_preferences \ -H 'Content-Type: application/json' \ -H 'Authorization: Token {YOUR_API_KEY}' \ -H 'X-API-Version: 2020-09-22' \ -d '{ "records": [ { "email": "kevin@acme.com", "all": true, "status": "unsubscribed" }, { "email": "sara@globex.com", "topic_ids": [3, 7], "status": "unsubscribed" } ] }' ``` ## Response ```json theme={null} { "received": 2, "unsubscribed": 2, "resubscribed": 0, "rejected": [] } ``` | Field | Description | | - | - | | `received` | How many records were accepted for processing. | | `unsubscribed` | How many opt-outs were genuinely new. | | `resubscribed` | How many opt-outs were lifted. | | `rejected` | Addresses left untouched, each with a reason. See [Rejected records](#rejected-records). | `unsubscribed` counts changes, not records. Sending five addresses where three were already unsubscribed comes back as `2`, so you can tell what actually changed and avoid double counting in your own system. ## Rules worth knowing **Unsubscribing only adds.** A record with `status: unsubscribed` is merged with whatever that address had already opted out of. It never clears an existing opt-out, so replaying a request or resending a full list is safe. **`all` is a master switch.** Re-subscribing someone to one subscription type doesn't lift a global opt-out. To undo it, send `all: true` with `status: subscribed`, which lifts the global block and leaves any subscription type opt-outs that address has in place. **Addresses don't have to be Userpilot users.** An address nobody has identified yet is accepted, and the opt-out applies automatically if that person ever signs up. **Duplicates collapse.** The same address more than once in a request is merged rather than treated as an error. **One address, one person.** If the same address exists on several Userpilot accounts, the change covers all of them. > [!WARNING] > > Put all of a person's subscription types in **one** record, like `{"email": "sara@globex.com", "topic_ids": [3, 7]}`, rather than a record per subscription type spread across requests you send at the same time. Everything Userpilot holds for an address lives in a single row, so two requests writing that same address concurrently can overwrite each other. Different addresses in parallel are fine. **Changes apply from the next send.** An email triggered by an event, or sent from a workflow, checks preferences as it goes out, so a change applies right away. A send to a segment works out its recipients when the send starts, so an opt-out that arrives mid-send applies to the next one. ### Rejected records Whoever unsubscribed decides who can undo it. If someone opted out through the unsubscribe link in an email, or through the one-click button in Gmail or Yahoo, this endpoint cannot re-subscribe them. Those addresses come back in `rejected` with the rest of the request applied as normal: ```json theme={null} { "received": 3, "unsubscribed": 1, "resubscribed": 1, "rejected": [ { "email": "kevin@acme.com", "reason": "this address opted out through the preference centre; only the recipient can re-subscribe it" } ] } ``` The way back for that person is the preference link in any email you send them. ## Validation Validation is all or nothing. If any record is malformed, the whole request is rejected with a `400` and nothing is written, so you never have to work out how much of a batch took effect: ```json theme={null} { "errors": [ { "error": "Bad Request", "error_code": "400", "message": "Invalid request payload provided.", "details": "records[1]: unknown topic_id 99" } ] } ``` `details` names the offending index. Rejected records are the one exception to all-or-nothing: refusing an entire import over one address that someone opted out of themselves would leave you with no way to proceed. ## Common Issues | Error | Cause | Solution | | - | - | - | | 401 Unauthorized | Invalid or suspended API key | Verify your key in Environment settings | | 400 `Request must include a records list` | `records` missing, not an array, or empty | Wrap your records in a non-empty `records` array | | 400 `A request may contain at most 1000 records` | Batch too large | Split the batch and send it in pages | | 400 `unknown topic_id 99` | The ID doesn't exist on this account | Look it up with [List Subscription Topics](./list-topics) | | 400 `email must be a valid email address` | A malformed address | Fix the record at the index named in `details` | | 429 Too Many Requests | Over 600 requests per minute per application token | Back off and retry with a growing delay | ## Related * [List Subscription Topics](./list-topics) - Find the IDs to write to * [Manage unsubscribes](/in-app-engagement/emails/manage-unsubscribes) - Import and manage the same lists from the dashboard * [Webhooks](/integrations/webhooks) - Receive an event when someone unsubscribes in Userpilot # Bulk Companies Update Source: https://docs.userpilot.com/api-references/endpoints/bulk/companies POST /v1/companies/bulk_identify Create or update multiple company profiles in a single API request using the bulk_identify endpoint. # Bulk Users Update Source: https://docs.userpilot.com/api-references/endpoints/bulk/users POST /v1/users/bulk_identify Create or update multiple user profiles in a single API request using the bulk_identify endpoint. # Export Job Source: https://docs.userpilot.com/api-references/endpoints/export/export-job POST /api/v1/analytics/exports Create a bulk export job to extract user, company, or event data with customizable date ranges and filters. # Import Job Source: https://docs.userpilot.com/api-references/endpoints/import/import-job POST /v1/imports Create a bulk import job to upload historical user, company, or event data from external systems. # View Job ID Source: https://docs.userpilot.com/api-references/endpoints/jobs/job-id GET /v1/background_jobs/{{job_id}} Get the status, progress, and results of a specific background job by its unique identifier. # List Jobs Source: https://docs.userpilot.com/api-references/endpoints/jobs/list GET /v1/background_jobs Retrieve a paginated list of background jobs including imports and exports with their status and metadata. # Banners Source: https://docs.userpilot.com/api-references/endpoints/lookups/banners GET /api/v1/analytics/exports/lookups/banners List all banners with their IDs and names for filtering analytics exports by specific announcement campaigns. # Checklists Source: https://docs.userpilot.com/api-references/endpoints/lookups/checklists GET /api/v1/analytics/exports/lookups/checklists List all checklists with their IDs and names for filtering analytics exports by onboarding progress data. # Company Properties Source: https://docs.userpilot.com/api-references/endpoints/lookups/company-properties GET /api/v1/analytics/exports/lookups/company_properties Retrieve all available company properties and their metadata for use in analytics exports and B2B segmentation. # Embeds Source: https://docs.userpilot.com/api-references/endpoints/lookups/embeds GET /api/v1/analytics/exports/lookups/embeds List all embeds (banners and cards) with their IDs and names for filtering analytics exports by specific embed campaigns. # Features & Events Source: https://docs.userpilot.com/api-references/endpoints/lookups/features-events GET /api/v1/analytics/exports/lookups/features_events List all tracked features and events with their IDs for filtering analytics exports by specific user actions. # Flows Source: https://docs.userpilot.com/api-references/endpoints/lookups/flows GET /api/v1/analytics/exports/lookups/flows List all flows with their IDs and names for filtering analytics exports by specific in-app experiences. # Resource Center Modules Source: https://docs.userpilot.com/api-references/endpoints/lookups/resource-center-modules GET /api/v1/analytics/exports/lookups/resource_center_modules List all Resource Center modules with their IDs for filtering analytics exports by help content engagement. # Segments Source: https://docs.userpilot.com/api-references/endpoints/lookups/segments GET /api/v1/analytics/exports/lookups/segments List all user segments with their IDs and names for filtering analytics exports by specific audience groups. # Spotlights Source: https://docs.userpilot.com/api-references/endpoints/lookups/spotlights GET /api/v1/analytics/exports/lookups/spotlights List all spotlights with their IDs and names for filtering analytics exports by tooltip and hotspot interactions. # Surveys Source: https://docs.userpilot.com/api-references/endpoints/lookups/surveys GET /api/v1/analytics/exports/lookups/surveys List all surveys with their IDs and names for filtering analytics exports by feedback collection campaigns. # User Properties Source: https://docs.userpilot.com/api-references/endpoints/lookups/user-properties GET /api/v1/analytics/exports/lookups/user_properties Retrieve all available user properties and their metadata for use in analytics exports and segmentation filters. # Identify Company Source: https://docs.userpilot.com/api-references/endpoints/realtime/identify-company POST /v1/companies/identify Create or update a company profile with properties like name, industry, and size via HTTP POST request. # Identify User Source: https://docs.userpilot.com/api-references/endpoints/realtime/identify-user POST /v1/identify Create or update a user profile with properties like name, email, and custom attributes via HTTP POST request. # Track Event Source: https://docs.userpilot.com/api-references/endpoints/realtime/track-event POST /v1/track Record a custom event for a user with optional metadata properties via HTTP POST request. # Environment Source: https://docs.userpilot.com/api-references/environment Userpilot APIs are available at different endpoint URLs depending on your hosting environment, with dedicated endpoints for Enterprise and EU customers. Userpilot APIs are available at different endpoint URLs depending on your hosting environment. Most users will use the default endpoint, but Enterprise and EU customers may have dedicated endpoints. ### Default Endpoint For most users, the API endpoint is: ```bash theme={null} https://analytex.userpilot.io ``` ### Enterprise & EU Hosting If you are on Enterprise or EU hosting, your API endpoint may be different. You can find your dedicated endpoint on the **Environment Page** in your Userpilot application. > [!TIP] > > Always check your Environment Page for the correct endpoint if you are unsure. ### How to Find Your Environment 1. Log in to your Userpilot dashboard. 2. Navigate to the **[Environment Page](https://run.userpilot.io/environment)**. 3. Copy the endpoint URL provided for your environment. > [!NOTE] > > Using the correct endpoint is essential for successful API requests, > especially if your data residency or compliance requirements dictate a > specific region. # Event Schema Source: https://docs.userpilot.com/api-references/export/event-schema A complete reference to the structure, attributes, and relationships of events exported from Userpilot. Welcome to the Userpilot Event Data Schema documentation! This guide provides a comprehensive overview of the structure, attributes, and relationships of events exported from Userpilot. It is designed to help data analysts, engineers, and business users understand and utilize Userpilot event data for integration, analytics, and reporting purposes. Below, you'll find detailed explanations of each event field, example payloads, and best practices for working with Userpilot data in your own systems. *** ## Event Structure | **Attribute** | **Type** | **Description** | | - | - | - | | `app_token` | String | Unique identifier for the app. | | `event_type` | String | Type of event (`identify_user`, `identify_company`, `interaction`, `track`, `track_labeled`, `track_feature`, `page_view`, `session_start`). | | `event_name` | String | Name of the event. For some types, this is empty or contains details (see below). | | `user_id` | String | Unique identifier for the user (for `identify_user` events). | | `company_id` | String | Unique identifier for the company (for `identify_company` events). | | `source` | String | Source of the event (`web-client`, `backend-http`, `backend-hubspot`, `backend-salesforce`, `backend-segment`). | | `inserted_at` | DateTime (UTC) | Timestamp of the event (microsecond precision). | | `internal_event_id` | String | Unique identifier calculated using xxHash64 of the composite key (`app_token`, `event_type`, `event_name`, `user_id`, `company_id`, `inserted_at`). | | `hostname` | String | Hostname where the event took place. | | `pathname` | String | Page path where the event occurred. | | `screen_width` | Integer | User's screen width (px). | | `screen_height` | Integer | User's screen height (px). | | `device_type` | String | Device type (`desktop`, `mobile`, etc.). | | `user_agent` | String | Browser's user agent string. | | `operating_system` | String | User's operating system. | | `browser` | String | Browser name (e.g., `Chrome`). | | `browser_language` | String | Browser language setting. | | `session` | String | Session number (for SDK events, auto-updated for `web-client`). | | `country_code` | String | 2-letter country code. | | `metadata` | Object | The attributes object varies depending on the `event_type` (see below). | ### Internal Event ID The `internal_event_id` is a unique identifier for each event, calculated using the xxHash64 algorithm on a composite key consisting of the following fields: * `app_token` * `event_type` * `event_name` * `user_id` * `company_id` * `inserted_at` This identifier provides a deterministic way to uniquely identify events based on their core attributes, which is useful for deduplication and event tracking across systems. ### Event Name & Type The `event_name` field tracks the name of the event and varies depending on the `event_type`: * **`For identify_user, identify_company, session_start, and page_view events:`** This field will be empty. * **`For track events:`** The value will represent the name of the event being passed programmatically or through the button-tracking feature. * **`For track_feature events:`** The value will contain the `id` of the tracked feature tag. * **`For interaction events:`** * The format is `{entity_type}:{entity_id}:{entity_interaction_status}` * Example: `checklist:2:SEEN`, `experience:448:DISMISSED`, `resource_center:1:ENGAGED`, `experiment:9:SEEN`. > [!NOTE] > > When a flow is part of an experiment, both `experiment:*` and `experience:*` events are generated to track experiment participation and flow interaction respectively. *** ### Source The `source` field indicates where the event was captured. The default value is `web-client`, but it can vary depending on the source of the event: * **web-client**: Captured through the web SDK. * **backend-http**: Captured via HTTP endpoints. * **backend-hubspot**: Captured through the HubSpot integration. * **backend-salesforce**: Captured through the Salesforce integration. * **backend-segment**: Captured through SegmentIO integration. *** ### Attributes Schema The `metadata` field varies depending on the event type: * **`For identify_user, identify_company, and track events:`** This field will contain custom attributes passed during the event tracking. * **`For page_view, session_start and track_feature events:`** This field will be an empty object. * **`For interaction events:`** Attributes vary based on the engagement entity type and provide specific interaction details (see below). *** ## Interaction Attributes Below are common interaction events and the relevant attributes associated with each. ### Surveys (`survey_module` events) | **Attribute** | **Type** | **Description** | | - | - | - | | `feedback` | String | Feedback provided by the user, often a unique identifier or actual feedback text. | | `interaction_id` | String | Unique ID of the survey module (e.g., `survey_module_49`). | | `is_dismissed` | String | Indicates whether the survey was dismissed by the user (`1` for yes, `0` for no). | | `parent_id` | String | The ID of the parent survey. | | `status` | String | Current status of the survey interaction (e.g., `COMPLETED`, `DISMISSED`). | | `survey_question` | String | The question presented to the user. | | `variant` | String | Survey type (e.g., `multiple_choice`, `open_text`). | **Example:** ```json theme={null} { "event_name": "survey_module:49:COMPLETED", "attributes": { "feedback": "318b72c9-b1ad-4bf6-854b-26d32ac9d281", "interaction_id": "survey_module_49", "is_dismissed": "0", "parent_id": "survey_13", "status": "COMPLETED", "survey_question": "", "variant": "multiple_choice" } } ``` ### Checklists (`checklist` and `checklist_task` events) | **Attribute** | **Type** | **Description** | | - | - | - | | `interaction_id` | String | Unique ID of the checklist or checklist task (e.g., `checklist_10`). | | `is_dismissed` | String | Indicates whether the checklist or task was dismissed by the user (`1` for yes, `0` for no). | | `parent_id` | String | The ID of the parent checklist, if applicable. | | `status` | String | Status of the checklist interaction (e.g., `SEEN`, `ENGAGED`). | **Example:** ```json theme={null} { "event_name": "checklist:10:SEEN", "attributes": { "interaction_id": "checklist_10", "is_dismissed": "", "parent_id": "", "status": "SEEN" } } ``` ### Resource Center and Articles (`resource_center`, `resource_center_article` events) | **Attribute** | **Type** | **Description** | | - | - | - | | `article_title` | String | Title of the article the user engaged with. | | `article_url` | String | URL of the article. | | `interaction_id` | String | Unique ID for the resource center interaction or article (e.g., `resource_center_article_6FAA69`). | | `is_dismissed` | String | Indicates if the resource center or article interaction was dismissed. | | `parent_id` | String | The ID of the parent resource center entity (if applicable). | | `status` | String | Status of the interaction (e.g., `ENGAGED`). | **Example:** ```json theme={null} { "event_name": "resource_center_article:6FAA69EBAFEEDF5E641ACF6D52847FC98AC44437591FC39D8BE67BDCF4570023:ENGAGED", "attributes": { "article_title": "How to Delete a Flow - Userpilot Knowledge Base", "article_url": "https://docs.userpilot.com/", "interaction_id": "resource_center_article_6FAA69EBAFEEDF5E641ACF6D52847FC98AC44437591FC39D8BE67BDCF4570023", "is_dismissed": "0", "parent_id": "resource_center_1", "status": "ENGAGED" } } ``` ### NPS Events | **Attribute** | **Type** | **Description** | | - | - | - | | `feedback` | String | Feedback provided by the user (can be text or a unique ID). | | `follow_up_question` | String | A follow-up question posed to the user after providing the NPS score. | | `interaction_id` | String | Unique ID for the NPS interaction (e.g., `nps_1`). | | `score` | String | NPS score given by the user (typically between 0 and 10). | | `status` | String | Status of the NPS interaction (e.g., `SUBMITTED_FEEDBACK`, `SUBMITTED_SCORE`). | | `survey_question` | String | The specific NPS question posed to the user. | | `submission_id` | String | Unique identifier for the NPS submission. | **Example:** ```json theme={null} { "event_name": "nps:1:SUBMITTED_FEEDBACK", "attributes": { "feedback": "Great tool, highly recommended!", "follow_up_question": "Is there anything else we can improve?", "interaction_id": "nps_1", "score": "9", "status": "COMPLETED", "submission_id": "2024-05-02", "survey_question": "How likely is it that you would recommend us to a friend or colleague?" } } ``` ### Flow and Flow Steps (`experience`, `experience_step` events) | **Attribute** | **Type** | **Description** | | - | - | - | | `interaction_id` | String | Unique ID of the experience or experience step (e.g., `experience_493`). | | `is_dismissed` | String | Indicates if the experience was dismissed (`1` for yes, `0` for no). | | `status` | String | The status of the experience (e.g., `COMPLETED`, `DISMISSED`). | | `total_dismissed` | String | The total number of times the experience was dismissed by the user. | **Example:** ```json theme={null} { "event_name": "experience:493:DISMISSED", "attributes": { "interaction_id": "experience_493", "is_dismissed": "1", "status": "DISMISSED", "total_dismissed": "1" } } ``` > [!NOTE] > > **Flows in Experiments:** When a flow is part of a test experiment, the system generates BOTH `experiment:*` and `experience:*` events: > > * `experiment:*` events track experiment participation and variant assignment > * `experience:*` events track flow interaction ### Experiments (`experiment` events) When flows are used testing experiments, Userpilot tracks experiment participation through dedicated experiment events. These events capture which variant (test group) a user was assigned to. | **Attribute** | **Type** | **Description** | | - | - | - | | `interaction_id` | String | Unique ID of the experiment (e.g., `experiment_9`). | | `is_dismissed` | String | Indicates whether the experiment interaction was dismissed by the user (`1` for yes, `0` for no). | | `never_show_again` | String | Indicates if the user chose to never see this again (`1` for yes, `0` for no). | | `status` | String | Current status of the experiment interaction (e.g., `SEEN`, `COMPLETED`, `DISMISSED`). | | `total_dismissed` | String | The total number of times the experiment was dismissed by the user. | | `variant` | String | The experiment group the user was assigned to. Contains the variant name (A, B, or C). | **Variant Values:** The `variant` attribute indicates which test group the user was assigned to. Possible values are "A", "B", or "C" depending on the experiment type (Controlled A/B Test, Head-to-Head A/B Test, or Controlled Multivariate Test). For more details on experiment types and how variants are assigned, see the [Experiments documentation](https://docs.userpilot.com/in-app-engagement/flows/use-cases/AB-testing). **Example:** ```json theme={null} { "event_name": "experiment:9:SEEN", "attributes": { "interaction_id": "experiment_9", "is_dismissed": "0", "never_show_again": "0", "status": "SEEN", "total_dismissed": "0", "variant": "B" } } ``` ### Workflows Workflow-related events are exported as `interaction` events, so they're included in your exported data automatically. Two types are captured: * **Workflow-level events:** track the overall workflow, e.g. `workflow:16:SEEN` and `workflow:16:COMPLETED`. * **Workflow node events:** track individual steps within a workflow, e.g. `experience_step:vagqc:SEEN` and `experience_step:vagqc:COMPLETED`. The workflow node events have the following meanings: * `SEEN` indicates that the user has completed the previous node and is ready to engage with the next node. * `COMPLETED` indicates that the user has completed the current node, either by interacting with it or because a configured delay for that node has elapsed. Additionally, content that is part of a workflow (for example, Flows or Surveys) follows the normal content interaction model. These events are exported the same way as if the content were used outside of a workflow. **Example:** ```json theme={null} { "event_name": "workflow:16:SEEN", "attributes": { "interaction_id": "workflow_16", "is_dismissed": "0", "never_show_again": "0", "status": "SEEN", "total_completed": "0", "total_dismissed": "0", "total_engaged": "0", "total_seen": "0" } } ``` ```json theme={null} { "event_name": "experience_step:vagqc:SEEN", "attributes": { "interaction_id": "experience_step_vagqc", "is_dismissed": "0", "never_show_again": "0", "parent_id": "experience_67", "status": "SEEN", "total_completed": "0", "total_dismissed": "0", "total_engaged": "0", "total_seen": "0" } } ``` *** ## Key Data Entities & Relationships * **Users:** Identified by `user_id`, with `metadata` (traits like `email`, `signup_at`, `plan_type`). * **Companies:** (B2B) Identified by `company_id`, with properties (e.g., `company_name`, `industry`). * **Events:** Actions or occurrences, each as a JSON line. * **Sessions:** Grouped events within a continuous user activity period, identified by `session`. ### Relationships * A **User** can perform many **Events**. * Multiple **Events** can occur within a single **Session**. * A **User** may belong to one **Company**. *** ## FAQs > **Does the metadata field always contain the same attributes?** > > No, it depends on `event_type`. For `identify_user`, `identify_company`, and `track` events it holds custom attributes you passed in. For `page_view`, `session_start`, and `track_feature` events it's empty. For `interaction` events it holds engagement-specific details (survey, checklist, NPS, flow, etc.) > **Why do flows in experiments generate two different event types?** > > When a flow is part of an A/B test, Userpilot emits both an `experiment:*` event (participation/variant assignment) and an `experience:*` event (the actual flow interaction) for the same user action, so you'll see two rows, not a duplicate. # Data Lookups Source: https://docs.userpilot.com/api-references/export/lookups The data lookup endpoints help you understand the structure and available fields in your exported data. The Lookup APIs allow you to retrieve metadata about various entities in your Userpilot account, such as user properties, company properties, features, events, and more. ## User & Company Endpoints for retrieving user and company metadata, as well as segments. | Endpoint | Description | | - | - | | `/api/v1/analytics/exports/lookups/user_properties` | Get user properties | | `/api/v1/analytics/exports/lookups/company_properties` | Get company properties | | `/api/v1/analytics/exports/lookups/segments` | Get segments (paginated) | *** ## Features & Events Endpoints for retrieving features, events, and event property metadata. | Endpoint | Description | | - | - | | `/api/v1/analytics/exports/lookups/features_events` | Get features & events | | `/api/v1/analytics/exports/lookups/events_properties` | Get trackable events' properties/attributes (paginated) | *** ## Experiences & UI Endpoints for retrieving experiences, UI elements, surveys, resource center modules, and checklists. | Endpoint | Description | | - | - | | `/api/v1/analytics/exports/lookups/flows` | Get flows (experiences) | | `/api/v1/analytics/exports/lookups/banners` | Get banners | | `/api/v1/analytics/exports/lookups/embeds` | Get embeds (banners and cards) | | `/api/v1/analytics/exports/lookups/spotlights` | Get spotlights | | `/api/v1/analytics/exports/lookups/surveys` | Get surveys (with modules) | | `/api/v1/analytics/exports/lookups/resource_center_modules` | Get resource center modules | | `/api/v1/analytics/exports/lookups/checklists` | Get checklists (with tasks) | *** ## How to Use All endpoints require authentication via your API key: ```bash theme={null} curl --location 'https://appex.userpilot.io/api/v1/analytics/exports/lookups/user_properties' \ --header 'Authorization: Token {{API_KEY}}' ``` ### Example Response ```json theme={null} [ { "display_name": "URL", "key": "url", "data_type": "string", ... } ] ``` ## Pagination Some endpoints (such as `events_properties` and `segments`) are paginated. The response includes a `cursor` object in the `metadata` section: * `after`: Cursor for fetching the next set of results. * `before`: Cursor for fetching previous results (if available). * `limit`: Number of results per request. * `total_count`: Total number of available records. ### Example Paginated Request ```bash theme={null} curl --location 'https://appex.userpilot.io/api/v1/analytics/exports/lookups/events_properties?after_cursor=...' \ --header 'Authorization: Token {{API_KEY}}' ``` ### Example Paginated Response ```json theme={null} { "data": [ ... ], "metadata": { "cursor": { "after": "...", "before": null }, "limit": 50, "total_count": 53 } } ``` > [!NOTE] > > * Always check if `cursor.after` exists in the response before making the next request. > * If `cursor.after` is `null`, you have reached the last page of data. > * To modify the number of results per request, use the `limit` parameter in your query string (default is 50). # Bulk Data Export API Source: https://docs.userpilot.com/api-references/export/overview The Bulk Data Export API lets you asynchronously export raw event, user, and company data from Userpilot — filtered by date range, user, company, event type, or segment — as JSON or CSV files. > [!NOTE] > > This feature is available on the Enterprise plan only or as an add-on to the > Growth plan. If interested in adding this feature please contact > [support@userpilot.com](mailto:support@userpilot.com). The **Userpilot Export Analytics Data API** allows you to export analytics data from Userpilot, enabling you to analyze user behavior, engagement, and more. You can filter the data by date range, user, company, event type, and additional parameters. The API supports exporting data in JSON or CSV formats. *** ## Authorization Userpilot API uses an API key to authenticate requests. You can find your API key on the [Environment Page](https://run.userpilot.io/environment). **Authentication Method:** Include your API key in the `Authorization` header: ```http theme={null} Authorization: Token {{API_KEY}} ``` > [!NOTE] > > All API requests must be made over HTTPS. > [!WARNING] > > Your API key carries many privileges, so be sure to keep it secure! Do not > share your secret API keys in publicly accessible areas. *** ## Rate Limits The Export API enforces rate limiting to ensure reliable data export operations and maintain system performance for all users. These limits help balance resource usage while enabling comprehensive analytics data retrieval. ### Rate Limit Details * **Job Limitation**: **One export job at a time** per application token ### Error Responses The API returns specific error codes when limits are exceeded: * **`409 Conflict`**: Returned when attempting to create a new export job while another is in progress ### Best Practices > [!TIP] > > **Verify Job Status**: Always check for existing export jobs before initiating a new one to avoid conflicts. > > **Handle Conflicts Gracefully**: When encountering a 409 Conflict error, wait for the current job to complete before retrying. > > **Track Export Progress**: Use job status endpoints to monitor export completion and download files promptly. > > **Optimize Export Requests**: Use specific date ranges and filters to reduce data volume and improve performance. *** ## Data Restrictions * **Job Status Availability:** Once an export job is created, the job status and related information will be available for **3 days**. After this period, the job status will no longer be accessible via the API. * **File Download Expiry:** The URLs generated to download the exported data files will expire after **2 days**. Ensure you download the files within this time frame, as the links will no longer be valid afterward. > [!TIP] > > Providing a valid email address in the request is recommended. You'll receive > a notification with download links when the export is ready, eliminating the > need to manually check job status. *** > [!NOTE] > > For the schema of exported events and data, see the [Event > Schema](./event-schema) page. > [!TIP] > > For details on available lookup endpoints and how to use them, see the [Data > Lookups](./lookups) page. ## Data Schema Reference The data exported via the Userpilot Export Analytics Data API follows the Userpilot [Event Data Schema](./event-schema). This schema defines the structure, attributes, and relationships of all events, users, and companies included in your export. **Key points:** * Each exported event will include fields such as `app_token`, `event_type`, `event_name`, `user_id`, `company_id`, `source`, `inserted_at`, and more. * Auto-captured attributes (e.g., `hostname`, `device_type`, `browser`, etc.) and custom attributes (in the metadata object) are included as described in the schema. * The schema covers all event types (`identify_user`, `identify_company`, `track`, `track_feature`, `page_view`, `interaction`, `session_start`, etc.) and their specific attribute structures. > [!WARNING] > > Your exported data may include new or additional fields as the schema evolves. > Please ensure your data processing pipelines can handle unexpected fields > gracefully. *** ## API Endpoints ### 1. Trigger an Export Job #### Endpoint ```http theme={null} POST https://appex.userpilot.io/api/v1/analytics/exports ``` #### Description Creates an export job to extract analytics data based on the specified parameters. #### Parameters | Parameter | Type | Required | Description | Example | | - | - | - | - | - | | `from` | `string` | Yes | Start date of the export in `YYYY-MM-DD` format. | "2024-05-02" | | `to` | `string` | Yes | End date of the export in `YYYY-MM-DD` format. | "2024-09-30" | | `emails` | `string[]` | No | List of email addresses to receive the exported data. If omitted, no email is sent. | \["[example@work.com](mailto:example@work.com)"] | | `event_type` | `string[]` | No | List of event types to filter. Defaults: see below. | \["track", "page\_view"] | | `user_id` | `string[]` | No | Specific user IDs to filter. If omitted, all users are included. | \["123456"] | | `company_id` | `string[]` | No | Specific company IDs to filter. If omitted, all companies are included. | \["13162551529"] | | `segment_id` | `string` | No | Filter data based on a specific segment. | "987654" | | `format` | `string` | No | Format of the exported data. Options: `json` (default) or `csv`. | "csv" | | `exclusions` | `boolean` | No | Exclude users/companies set in the web app - see [Exclude Users and Companies](../../configure/exclude-lists/exclude-user). Default: `false`. | true | **Default values:** * `event_type` defaults to `["identify_user", "identify_company", "page_view", "track", "track_feature", "interaction", "session_start"]` if not provided. * `user_id` and `company_id` default to empty arrays (`[]`), meaning all users or companies are included by default. * The default export `format` is `json`. #### Example Request ```bash theme={null} curl --location 'https://appex.userpilot.io/api/v1/analytics/exports' \ --header 'Authorization: Token {{API_KEY}}' \ --header 'Content-Type: application/json' \ --data-raw '{ "from": "2024-05-02", "to": "2024-09-30", "emails": ["example@work.com"], "event_type": ["identify_user", "identify_company", "page_view", "track"], "user_id": ["user_1"], "company_id": ["comp_2"], "segment_id": "9", "format": "csv", "exclusions": true }' ``` **Success** ```json theme={null} { "environment_app_token": "NX-51f4acf7", "environment_name": "production", "job_id": "86f4dd75-00c8-40cd-ac6a-3dce8440be0c", "links": "/api/v1/analytics/exports/jobs/86f4dd75-00c8-40cd-ac6a-3dce8440be0c", "start_time": "2024-10-06T10:44:50.999504" } ``` **409 Conflict** ```json theme={null} { "errors": [ { "details": "There is already an export job in progress, you can only have one export job in progress at a time.", "error": "Conflict", "error_code": "409", "message": "The request could not be completed due to a conflict with the current state of the target resource." } ] } ``` **401 Unauthorized** ```json theme={null} { "errors": [ { "details": "Your current plan for application NX-d7ce194a does not support the feature you are trying to access.", "error": "Unauthorized", "error_code": "401", "message": "You are not authorized to perform the requested action." } ] } ``` *** ### 2. Show Jobs #### Endpoint ```http theme={null} GET https://appex.userpilot.io/api/v1/analytics/exports/jobs ``` #### Description Retrieves a list of all export jobs triggered by your API token. #### Example Request ```bash theme={null} curl --location 'https://appex.userpilot.io/api/v1/analytics/exports/jobs' \ --header 'Authorization: Token {{API_KEY}}' ``` **Success** ```json theme={null} [ { "elapsed_time": "44 seconds", "elapsed_time_seconds": 44, "end_time": "2024-09-22T11:20:58.006463Z", "job_id": "af257e48-c145-40aa-996c-dad64839acb1", "progress": "5 out of 5 partitions", "start_time": "2024-09-22T11:20:14.731634", "status": "completed" } ] ``` *** ### 3. Show Job by Job ID #### Endpoint ```http theme={null} GET https://appex.userpilot.io/api/v1/analytics/exports/jobs/{job_id} ``` #### Description Retrieves detailed information about a specific export job identified by its `job_id`. #### Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | job\_id | String | Yes | The unique identifier of the export job | #### Example Request ```bash theme={null} curl --location 'https://appex.userpilot.io/api/v1/analytics/exports/jobs/{job_id}' \ --header 'Authorization: Token {{API_KEY}}' ``` **Success** ```json theme={null} { "app_token": "", "completed_chunks": ["2024-05-01", "2024-06-01"], "current_chunk": "2024-09-01", "end_time": "2024-09-22T13:18:11.558646Z", "job_id": "664314e1-ee60-48eb-a36a-5f38f4c524f1", "presigned_urls": [ {"filename": "2024-07-29.json.gz", "url": "https://..."} ], "progress": "2 out of 5 partitions", "start_time": "2024-09-22T13:17:26.864860Z", "status": "completed", "all_chunks": ["2024-05-01", "2024-06-01"], "type": "export" } ``` **404 Not Found** ```json theme={null} { "errors": [ { "details": null, "error": "Resource not found", "error_code": "404", "message": "Failed to retrieve job with id ...: job not found" } ] } ``` > [!NOTE] > > Ensure you retrieve job details within the **3-day** availability window to > effectively monitor ongoing jobs. - Download links for exported data will > expire after **2 days**, so make sure to download your data promptly. *** ## Understanding File Names and Date Ranges The Export API partitions data into files by weeks to avoid oversized files. It's important to understand how file names relate to the data they contain. ### File Naming Convention * **Weekly Partitioning:** Each file represents a weekly partition of data * **File Name Format:** Each weekly file's name is the date representing the **start of the week (Monday)** in `YYYY-MM-DD` format * **Data Filtering:** The file name does **not** indicate that the file contains all data from that entire week. The data within each file is still restricted to your requested date range filter ### Example Scenario If you request data for the date range **November 30, 2025 → December 2, 2025**, you may receive files with names that appear outside this range: * **File: `2025-11-24.json.gz`** * November 30, 2025 belongs to the week starting November 24, 2025 (Monday) * This file contains data for **November 30, 2025 only** (matching your filter) * The file name reflects the week start date, not the actual data range * **File: `2025-12-01.json.gz`** * December 2, 2025 belongs to the week starting December 1, 2025 (Monday) * This file contains data for **December 1, 2025 and December 2, 2025** (matching your filter) * Again, the file name reflects the week start date ### Key Points * File names represent the **week start date (Monday)** for organizational purposes * The actual data in each file is **filtered to your requested date range** * You may see file names with dates outside your requested range, but the data inside will only include events within your specified `from` and `to` dates * This partitioning approach helps manage file sizes while maintaining efficient data retrieval # User & Company Accumulative Properties Best Practices Source: https://docs.userpilot.com/api-references/import/best-practices When importing historical data, it is crucial to ensure the consistency and accuracy of user and company data in Userpilot. > [!WARNING] > > **PII Data Notice** > > Before passing any Personally Identifiable Information (PII) such as names, emails, or other sensitive user data to Userpilot: > > * **Security & Compliance:** Verify internally with your legal, security, and compliance teams that doing so aligns with your organization's data privacy policies and applicable regulations (e.g., GDPR, CCPA). > > * **Data Type Mapping:** Ensure correct data type mapping for all properties. Userpilot supports String, Numeric, and Date types. Make sure dates are in ISO8601 format and numeric values are properly formatted. Incorrect type mapping can cause issues with segmentation, analytics, and debugging. > > * **Debugging:** Proper data formatting is crucial for effective debugging and troubleshooting. Verify that your data maps correctly to Userpilot's expected formats before implementation. When importing historical data, it is crucial to ensure the consistency and accuracy of user and company data in Userpilot. Follow these guidelines to avoid potential issues in reporting or data analysis: 1. **Sequential Property Building:** Begin the import process with a subset of properties and gradually build up the profile until all properties are included. This ensures a smooth transition and avoids overwriting or omitting critical data. 2. **Consistent Property Lists:** Avoid submitting inconsistent sets of properties for the same user or company in sequential `identify_user` or `identify_company` events. Always include the full list of properties for each `identify` event. Populate known attributes and leave the remaining attributes as `null` or empty values. 3. **Avoid Overwriting with Partial Data:** Submitting partial data in subsequent `identify` calls can cause critical attributes to be overwritten with null values, leading to inaccurate reporting and inconsistent profiles. **Example Approach** | Attribute | First identify | Second Identify | Third Identify | | - | - | - | - | | name | "John Doe" | "John Doe" | "John Doe" | | email | "[john@example.com](mailto:john@example.com)" | "[john@example.com](mailto:john@example.com)" | "[john@example.com](mailto:john@example.com)" | | location | null | "Dublin" | "Dublin" | | job\_title | null | null | "Architect" | * **Best Practice:** Gradually build up the user profile by adding properties in each step until the full profile is complete. This ensures the merge process maintains data consistency. * **Incorrect Practice:** Reducing the set of properties in subsequent calls. This may result in missing or overwritten data. **Why This Matters** Userpilot's data pipeline relies on the accuracy and completeness of historical imports. The merge tree logic aggregates data based on the most recent updates. Inconsistent or partial imports can disrupt this process, leading to incorrect data in reporting tools. By adhering to these best practices, you ensure reliable and comprehensive user and company profiles in Userpilot, maintaining the integrity of your analytics and insights. # Event Schema Source: https://docs.userpilot.com/api-references/import/event-schema This page documents the event types and required fields for importing data into Userpilot using the Import API. This page documents the event types and required fields for importing data into Userpilot using the Import API. Each record in your import file must conform to one of the following event schemas. Supported event types are `identify_user`, `identify_company`, `page_view`, and `track`. > [!WARNING] > > **PII Data Notice** > > Before passing any Personally Identifiable Information (PII) such as names, emails, or other sensitive user data to Userpilot: > > * **Security & Compliance:** Verify internally with your legal, security, and compliance teams that doing so aligns with your organization's data privacy policies and applicable regulations (e.g., GDPR, CCPA). > > * **Data Type Mapping:** Ensure correct data type mapping for all properties. Userpilot supports String, Numeric, and Date types. Make sure dates are in ISO8601 format and numeric values are properly formatted. Incorrect type mapping can cause issues with segmentation, analytics, and debugging. > > * **Debugging:** Proper data formatting is crucial for effective debugging and troubleshooting. Verify that your data maps correctly to Userpilot's expected formats before implementation. *** ## 1. Identify User Identifies or updates the attributes of a user. | Field | Type | Required | Description | | - | - | - | - | | event\_type | String | Yes | Must be `identify_user` | | user\_id | String | Yes | Unique identifier for the user. | | metadata | Object | Yes | Key-value pairs of user attributes. | | source | String | Yes | Indicates the origin of the event data. | | inserted\_at | String (ISO) | Yes | Timestamp of when the data was recorded. | **Example Payload:** ```json theme={null} { "event_type": "identify_user", "event_name": "", "source": "web-client", "user_id": "user_id", "company_id": "company_id", "hostname": "example.com", "pathname": "/users/list", "country_code": "UK", "screen_width": 859, "screen_height": 746, "operating_system": "Mac", "browser": "Chrome", "browser_language": "en-US", "user_agent": "Chrome - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36", "device_type": "Desktop", "inserted_at": "2024-07-28 08:55:35.874555", "metadata": { "name": "John Doe", "email": "john.doe@example.com", "job_title": "Solution Architect", "department": "Engineering", "squad": "CORE Squad", "branch_location": "Dublin, Ireland", "sign_up_date": "2023-11-01 14:35:36.173103", "total_logins": 28, "days_since_last_login": 7 } } ``` *** ## 2. Identify Company Identifies or updates the attributes of a company. | Field | Type | Required | Description | | - | - | - | - | | event\_type | String | Yes | Must be `identify_company` | | company\_id | String | Yes | Unique identifier for the company. | | source | String | Yes | Indicates the origin of the event data. | | metadata | Object | Yes | Key-value pairs of company attributes. | | inserted\_at | String (ISO) | Yes | Timestamp of when the data was recorded. | **Example Payload:** ```json theme={null} { "event_type": "identify_company", "source": "web-client", "company_id": "company_id", "inserted_at": "2024-07-28 08:55:35.874555", "metadata": { "name": "Acme Labs Inc.", "industry": "Software Solutions", "branches_count": 3, "Headquarter": "Dublin, Ireland" } } ``` *** ## 3. Page View Logs a page view by a user. | Field | Type | Required | Description | | - | - | - | - | | event\_type | String | Yes | Must be `page_view` | | user\_id | String | Yes | Unique identifier for the user | | hostname | String | Yes | Hostname of the page (e.g., example.com) | | pathname | String | Yes | Pathname of the page (e.g., /dashboard) | | source | String | Yes | Indicates the origin of the event data | | inserted\_at | String (ISO) | Yes | Timestamp of when the page view occurred | **Example Payload:** ```json theme={null} { "event_type": "page_view", "event_name": "", "source": "web-client", "user_id": "user_id_10", "company_id": "company_id_60", "hostname": "example.com", "pathname": "/dashboard/", "country_code": "US", "screen_width": 859, "screen_height": 746, "operating_system": "", "browser": "", "browser_language": "en-US", "user_agent": "Chrome - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36", "device_type": "Desktop", "inserted_at": "2024-07-28 08:55:34.229738" } ``` *** ## 4. Track Event Tracks a custom user action or event. | Field | Type | Required | Description | | - | - | - | - | | event\_type | String | Yes | Must be `track` | | user\_id | String | Yes | Unique identifier for the user. | | event\_name | String | Yes | Name of the event (e.g., button\_click). | | source | String | Yes | Indicates the origin of the event data. | | metadata | Object | No | Key-value pairs of event-specific attributes | | inserted\_at | String (ISO) | Yes | Timestamp of when the event occurred. | **Example Payload:** ```json theme={null} { "event_type": "track", "event_name": "account_upgraded", "source": "web-client", "user_id": "user_id_123", "company_id": "", "hostname": "example.com", "pathname": "/subscription/plans", "country_code": "UK", "screen_width": 859, "screen_height": 746, "operating_system": "Mac", "browser": "Chrome", "browser_language": "en-US", "user_agent": "Chrome - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36", "device_type": "Desktop", "inserted_at": "2024-12-01T08:55:43.220396", "metadata": { "old_plan": "Basic", "new_plan": "Premium", "upgrade_date": "2024-12-01T08:55:43.220396" } } ``` # Events Bulk Import Source: https://docs.userpilot.com/api-references/import/overview The Import API allows you to import historical user data from other platforms into Userpilot. With this API, you can migrate data related to user and company. The Import API allows you to import historical user data from other platforms into Userpilot. With this API, you can migrate data related to user and company identification, page views, and custom events. This enables a seamless transition and continuity of insights, providing a comprehensive view of user interactions. > [!NOTE] > > This feature is available on the Enterprise plan only or as an add-on to the > Growth plan. If interested in adding this feature please contact > [sales@userpilot.com](mailto:sales@userpilot.com) or your designated Customer Success Manager (CSM). ### Use Cases 1. **Data Migration:** Migrate historical data from other analytics platforms to Userpilot, ensuring no loss of valuable insights. 2. **Retrospective Analysis:** Analyze historical user behavior and feature engagement by importing past data on user actions, page views, and events. > [!WARNING] > > **PII Data Notice** > > Before passing any Personally Identifiable Information (PII) such as names, emails, or other sensitive user data to Userpilot: > > * **Security & Compliance:** Verify internally with your legal, security, and compliance teams that doing so aligns with your organization's data privacy policies and applicable regulations (e.g., GDPR, CCPA). > > * **Data Type Mapping:** Ensure correct data type mapping for all properties. Userpilot supports String, Numeric, and Date types. Make sure dates are in ISO8601 format and numeric values are properly formatted. Incorrect type mapping can cause issues with segmentation, analytics, and debugging. > > * **Debugging:** Proper data formatting is crucial for effective debugging and troubleshooting. Verify that your data maps correctly to Userpilot's expected formats before implementation. > [!TIP] > > If you're migrating from another provider, complete the import process > **before** installing the SDK. This ensures historical data is ready for > analysis once Userpilot starts tracking live events. > [!WARNING] > > The Import API does not merge with a user's or company's existing (or live) > property state, unlike real-time Identify calls. Each imported event is > written as-is, so it's meant for one-time historical backfill, not ongoing > updates to current property values. If a user is later identified in real > time, that identify can overwrite properties you imported. To keep current > property values in sync, use the [Bulk Update API](../bulk-updates/users) > instead. *** ### Authorization See the [Authentication](../authentication) and [Environment](../environment) pages for details on API key usage and endpoint URLs. **Authentication Method:** Include your API key in the `Authorization` header: ```http theme={null} Authorization: Token {{API_KEY}} ``` > [!NOTE] > > All API requests must be made over HTTPS. > [!WARNING] > > Your API key carries many privileges, so be sure to keep it secure! Do not > share your secret API keys in publicly accessible areas. *** ## Rate Limits The Import API implements rate limiting to maintain system stability and ensure fair resource allocation across all users. These limits help prevent system overload while allowing efficient data migration workflows. ### Rate Limit Details * **Job Limitation**: **One import job at a time** per application token * **File Size**: **Up to 1 GB** file size for import operations ### Error Responses * **`401 Unauthorized`**: Returned when the API token is missing, invalid, or isn't a write/admin token * **`409 Conflict`**: Returned when attempting to create a new import job while another is in progress * **`413 Payload Too Large`**: Returned when file size exceeds 1 GB ### Best Practices > [!TIP] > > **Check Job Status First**: Always verify no existing import jobs are running before initiating a new one. > > **Implement Retry Logic**: When encountering a 409 Conflict error, wait for the current job to complete before retrying. > > **Monitor Job Progress**: Use the job status endpoints to track import progress and avoid conflicts. > > **Optimize File Sizes**: For very large datasets, consider splitting files to stay within the 1 GB limit. *** ## Endpoints For most users, the HTTP API endpoint URL is `https://analytex.userpilot.io`. If you are on Enterprise or EU hosting, refer to the [Environment](../environment) page in the application to retrieve your dedicated endpoint. ### 1. Request Data Import #### Endpoint ```http theme={null} POST {{ENDPOINT}}/v1/imports ``` > [!NOTE] > > The endpoint URL uses the `analytex` environment. For EU data residency, use > `analytex-eu` instead. See [Environment > Settings](https://run.userpilot.io/environment) for your specific endpoint. #### Description Initiates a data import job. Accepts a file in **CSV** or **NDJSON** format containing historical data. #### Headers | Header | Value | Required | | - | - | - | | `Content-Type` | `multipart/form-data` | Yes | | `Authorization` | `Token {{API_KEY}}` | Yes | #### Request Parameters | Parameter | Type | Required | Description | | - | - | - | - | | file | File | Yes | The CSV or NDJSON file containing the data to be imported. See the event schema for more details. | #### Data Format The imported data should conform to a list of events. See the [Import API Event Schema](./event-schema) for details on supported event types and required fields. > [!NOTE] > > Required fields vary by `source`. For example, `source: web-client` events require `hostname` and `pathname` (or a `url` field in their place), plus `user_agent`. #### Example Import File **NDJSON** ```json file.ndjson theme={null} {"event_type": "identify_user", "source": "backend-http-import", "user_id": "user_123", "inserted_at": "2024-07-28T08:55:35.874555", "metadata": { "name": "John Doe", "email": "john.doe@example.com", "location": "Dublin, Ireland" },"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"} {"event_type": "identify_company", "source": "backend-http-import","company_id": "company_123","inserted_at": "2024-07-28T08:55:35.874555","metadata": {"name": "Acme Labs Inc.","industry": "Software","size": "200-500","location": "San Francisco, CA"}} ``` **CSV** ```csv file.csv theme={null} event_type,event_name,source,user_id,company_id,hostname,pathname,country_code,screen_width,screen_height,operating_system,browser,browser_language,user_agent,device_type,inserted_at,metadata identify_user,,web-client,User-7320-001,Company-607-001,campfire-example.userpilot.io,/profile/settings/,DE,1086,696,Mac,Edge,de-DE,Edge - Mozilla/5.0 (Mac) AppleWebKit/537.36,Mobile,2023-12-26 14:36:15.453703,"{""created_at"": ""1694172975"", ""name"": ""User-Alice"", ""prop_0"": ""value_743"", ""prop_1"": ""value_927""}" page_view,,web-client,User-9491-002,Company-973-002,campfire-example.userpilot.io,/home/,DE,832,809,iOS,Chrome,fr-FR,Chrome - Mozilla/5.0 (iOS) AppleWebKit/537.36,Mobile,2024-04-13 14:36:15.453788,"{}" track,created_project,web-client,User-6267-003,Company-584-003,campfire-example.userpilot.io,/profile/settings/,PS,1114,704,iOS,Safari,en-US,Safari - Mozilla/5.0 (iOS) AppleWebKit/537.36,Mobile,2024-05-05 14:36:15.453813,"{""created_at"": ""1730810175"", ""name"": ""User-Bob"", ""prop_0"": ""value_17""}" ``` **Uploading the File** ```bash theme={null} curl -X POST "{{ENDPOINT}}/v1/imports" \ -H "Authorization: Token {{API_KEY}}" \ -F "file=@/path/to/import_data.ndjson" ``` #### Response **Success** ```json theme={null} { "end_time": null, "file_size": 2234, "filename": "2024-10-28.ndjson", "job_id": "imports:jobs:NX-51f4acf7:f681073b-21ac-4276-9af5-c662dc05fb25", "links": "/v1/background_jobs/imports:jobs:NX-51f4acf7:f681073b-21ac-4276-9af5-c662dc05fb25", "start_time": "2024-11-28T10:34:11.376662", "status": "queued", "total_rows": 0, "type": "import" } ``` **Error** ```json theme={null} { "errors": [ { "details": null, "error": "Conflict", "error_code": "409", "message": "The request could not be completed due to a conflict with the current state of the target resource." } ] } ``` *** ### 2. Get All Import Job Statuses #### Endpoint ```http theme={null} GET {{ENDPOINT}}/v1/background_jobs ``` #### Description Fetches the status of all import jobs, allowing users to monitor the progress of multiple imports at once. #### Headers | Header | Value | Required | | - | - | - | | `Authorization` | `Token {{API_KEY}}` | Yes | #### Example Request ```bash theme={null} curl -X GET "{{ENDPOINT}}/v1/background_jobs" \ -H "Authorization: Token {{API_KEY}}" ``` #### Response Returns a list of job objects, each with details such as job\_id, status, start\_time, end\_time, total\_rows, and reason\_for\_failure (if any). `status` can be `queued`, `pending_refresh` (data written, finalizing before completion), `completed`, or `failed`. ```json theme={null} [ { "elapsed_time": 6, "end_time": "2025-05-19T07:23:00.354452", "file_size": 757, "filename": "2024-10-28.ndjson", "job_id": "imports:jobs:NX-ac54070e:c6f91301-54c1-4df7-a077-b34715c38875", "links": "/v1/background_jobs/imports:jobs:NX-ac54070e:c6f91301-54c1-4df7-a077-b34715c38875", "start_time": "2025-05-19T07:22:54.409499", "status": "completed", "total_rows": 3, "type": "import" }, { "elapsed_time": 0, "end_time": "2025-05-19T07:09:49.883288", "file_size": 416, "filename": "2024-10-28.ndjson", "job_id": "imports:jobs:NX-ac54070e:1fd8f0d4-cd97-432f-86fe-3df9effc1912", "links": "/v1/background_jobs/imports:jobs:NX-ac54070e:1fd8f0d4-cd97-432f-86fe-3df9effc1912", "reason_for_failure": [ "Required field `user_id` not provided in the row: %{\"event_name\" => \"\", \"event_type\" => \"identify_user\", \"metadata\" => %{\"avg_resolution_time\" => \"2h\", \"customer_satisfaction\" => \"4.8\", \"escalation_count\" => \"1\", \"last_ticket_created\" => \"2025-02-01T10:20:30Z\", \"tickets_created\" => \"15\", \"tickets_pending\" => \"1\", \"tickets_resolved\" => \"14\"}}" ], "start_time": "2025-05-19T07:09:49.880967", "status": "failed", "total_rows": 0, "type": "import" } //... more jobs ] ``` *** ### 3. Get Job Status by ID #### Endpoint ```http theme={null} GET {{ENDPOINT}}/v1/background_jobs/:job_id ``` #### Description Fetches the status of a specific import job using its `job_id`, useful for tracking a single import job. #### Headers | Header | Value | Required | | - | - | - | | `Authorization` | `Token {{API_KEY}}` | Yes | #### Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | job\_id | String | Yes | The unique identifier of the import job | #### Example Request ```bash theme={null} curl -X GET "{{ENDPOINT}}/v1/background_jobs/:job_id" \ -H "Authorization: Token {{API_KEY}}" ``` #### Response Returns job details including job\_id, status, start\_time, end\_time, total\_rows, and reason\_for\_failure (if any). ```json theme={null} { "elapsed_time": 6, "end_time": "2025-05-19T07:23:00.354452", "file_size": 757, "filename": "2024-10-28.ndjson", "job_id": "imports:jobs:NX-ac54070e:c6f91301-54c1-4df7-a077-b34715c38875", "links": "/v1/background_jobs/imports:jobs:NX-ac54070e:c6f91301-54c1-4df7-a077-b34715c38875", "start_time": "2025-05-19T07:22:54.409499", "status": "completed", "total_rows": 3, "type": "import" } ``` *** > [!NOTE] > > For required validation rules for your import file, see the [Row Validation > Rules](./row-validation) page. > [!TIP] > > For best practices on importing user and company data, see [Best Practices for > Accumulative Properties](./best-practices). # Row Validation Rules Source: https://docs.userpilot.com/api-references/import/row-validation When preparing your HTTP import file, ensure that each row adheres to the following validation rules. 1. **Default Metadata:** If an event does not include the `metadata` key, it will be automatically assigned an empty object (`{}`). 2. **Identify Company Event Rules:** For events with `event_type` set to `identify_company`, only the following keys are processed: `company_id`, `source`, `inserted_at`, `metadata`. Any additional keys will be ignored. 3. **URL Parsing:** If the entry has a `url` key present, the `hostname` and `pathname` fields will be overridden based on the parsed values of the `url`. 4. **Auto Properties:** Fields like `browser` and `operating_system` are automatically derived from the `user_agent` property if included in your data. 5. **Allowed Values for `source`:** * `web-client`: Events come directly from a web client SDK, such as a browser or a front-end application. Must include `hostname`, `pathname`, and `user_agent`. You can provide a `url` field instead of `hostname` and `pathname`. * `backend-http`: Events captured from backend systems via direct HTTP integration. * `backend-hubspot`: Events imported from HubSpot integration. * `backend-salesforce`: Events imported from Salesforce integration. * `backend-segment`: Events forwarded through Segment integration. * `backend-http-import`: Events imported through the HTTP Bulk Import API. # Userpilot API Documentation Source: https://docs.userpilot.com/api-references/overview Welcome to Userpilot's comprehensive API suite! This guide helps you understand the different ways to integrate with Userpilot, choose the right API for your needs, and get the most out of our platform's capabilities. > **[Real-time APIs](/api-references/real-time/overview)** > > Live user tracking & eventsUse cases: > >
    >
  • Live user identification
  • >
  • Real-time event tracking
  • >
  • Instant personalization
  • >
  • Up-to-the-minute analytics
  • >
> **[Bulk Updates](/api-references/bulk-updates/users)** > > Update multiple profiles efficientlyUse cases: > >
    >
  • CRM data synchronization
  • >
  • Batch profile updates
  • >
  • Large-scale data sync
  • >
  • Company information updates
  • >
> **[Import Data](/api-references/import/overview)** > > Historical data migrationUse cases: > >
    >
  • Platform migration
  • >
  • Historical data backfill
  • >
  • Data warehouse replays
  • >
  • One-time data imports
  • >
> **[Export Data](/api-references/export/overview)** > > Analytics & reportingUse cases: > >
    >
  • Custom analytics
  • >
  • Data warehouse integration
  • >
  • Compliance reporting
  • >
  • External analysis tools
  • >
> **[Delete Data](/api-references/delete/overview)** > > Privacy complianceUse cases: > >
    >
  • GDPR compliance
  • >
  • User data removal
  • >
  • Privacy regulations
  • >
  • Data cleanup requests
  • >
# Identify Company Source: https://docs.userpilot.com/api-references/real-time/identify-company Identify and update company profiles in real time or in bulk using Userpilot's HTTP API. The Identify Company API allows you to create or update company profiles in real time, either individually or in bulk. Use this API to keep your company data in sync with Userpilot for analytics, segmentation, and B2B engagement. > [!WARNING] > > **PII Data Notice** > > Before passing any Personally Identifiable Information (PII) such as names, emails, or other sensitive user data to Userpilot: > > * **Security & Compliance:** Verify internally with your legal, security, and compliance teams that doing so aligns with your organization's data privacy policies and applicable regulations (e.g., GDPR, CCPA). > > * **Data Type Mapping:** Ensure correct data type mapping for all properties. Userpilot supports String, Numeric, and Date types. Make sure dates are in ISO8601 format and numeric values are properly formatted. Incorrect type mapping can cause issues with segmentation, analytics, and debugging. > > * **Debugging:** Proper data formatting is crucial for effective debugging and troubleshooting. Verify that your data maps correctly to Userpilot's expected formats before implementation. ## Individual Identify ### Endpoint ```http theme={null} [POST] https://analytex.userpilot.io/v1/companies/identify ``` > [!NOTE] > > The endpoint URL uses the `analytex` environment. For EU data residency, use > `analytex-eu` instead. See [Environment > Settings](https://run.userpilot.io/environment) for your specific endpoint. ### Headers | Header | Value | Required | | - | - | - | | `Content-Type` | `application/json` | Yes | | `Authorization` | `Token {YOUR_API_KEY}` | Yes | | `Accept` | `application/json, text/plain, */*` | Yes | ### Request Body | Field | Type | Required | Description | | - | - | - | - | | `company_id` | string | Yes | Unique identifier for the company | | `metadata` | object | No | Key-value pairs describing the company (e.g., name, industry, etc.) | ### Example ```json theme={null} { "company_id": "company_001", "metadata": { "name": "Acme Corporation", "industry": "SaaS", "plan": "Enterprise", "monthly_spend": 1000 } } ``` ### Response A successful identification returns HTTP status code 200 OK. > [!WARNING] > > The `company_id` field is required. Metadata values must be primitive types (string, number, boolean, null). *** ## Bulk Operations For bulk company identification and updates, see the dedicated documentation: [**Bulk Identify Companies**](https://docs.userpilot.com/api-references/bulk-updates/companies#bulk-identify-update-companies) - For bulk company operations These articles provide comprehensive details on endpoints, authentication, request/response examples, limitations, best practices, and troubleshooting for large-scale data synchronization. *** # Identify User Source: https://docs.userpilot.com/api-references/real-time/identify-user The Identify User API allows you to create or update user profiles in real time, either individually or in bulk. Use this API to keep your user data in sync with Userpilot for analytics, segmentation, and personalized experiences. ## When to Use Use the HTTP Identify API when: * Creating users from server-side code (e.g., after signup in your backend) * Updating user properties without a page load * Syncing user data from external systems * Your application doesn't run in a browser (backend services, mobile apps without SDK) > [!WARNING] > > **PII Data Notice** > > Before passing any Personally Identifiable Information (PII) such as names, emails, or other sensitive user data to Userpilot: > > * **Security & Compliance:** Verify internally with your legal, security, and compliance teams that doing so aligns with your organization's data privacy policies and applicable regulations (e.g., GDPR, CCPA). > > * **Data Type Mapping:** Ensure correct data type mapping for all properties. Userpilot supports String, Numeric, and Date types. Make sure dates are in ISO8601 format and numeric values are properly formatted. Incorrect type mapping can cause issues with segmentation, analytics, and debugging. > > * **Debugging:** Proper data formatting is crucial for effective debugging and troubleshooting. Verify that your data maps correctly to Userpilot's expected formats before implementation. **Use the JavaScript SDK instead** when: * Users are interacting with your web application in a browser * You need to display Userpilot content immediately after identification ## Prerequisites * Userpilot API Key (not App Token) from [Settings > Environment](https://run.userpilot.io/environment) * Knowledge of your API environment (US or EU) ## Individual Identify ### Endpoint ```http theme={null} [POST] https://analytex.userpilot.io/v1/identify ``` > [!NOTE] > > The endpoint URL uses the `analytex` environment. For EU data residency, use > `analytex-eu` instead. See [Environment > Settings](https://run.userpilot.io/environment) for your specific endpoint. ### Headers | Header | Value | Required | | - | - | - | | `Content-Type` | `application/json` | Yes | | `Authorization` | `Token {YOUR_API_KEY}` | Yes | | `X-API-Version` | `2020-09-22` | Yes | ### Request Body | Field | Type | Required | Description | | - | - | - | - | | `user_id` | string | Yes | Unique identifier for the user | | `metadata` | object | No | Key-value pairs describing the user | | `company` | object | No | Object with at least an `id` field for company association | ### Example ```json theme={null} { "user_id": "user_001", "metadata": { "name": "Jane Doe", "email": "jane@example.com" }, "company": { "id": "company_001" } } ``` ### Response A successful identification returns HTTP status code 202 Accepted. > [!WARNING] > > The `user_id` field is required. If you include a `company` object, it must > have at least an `id` field. *** ## Bulk Operations For bulk user identification and updates, see the dedicated documentation: * **[Bulk Identify Users](../bulk-updates/users)** - For bulk user operations These articles provide comprehensive details on endpoints, authentication, request/response examples, limitations, best practices, and troubleshooting for large-scale data synchronization. *** ## Common Issues | Error | Cause | Solution | | - | - | - | | 401 Unauthorized | Invalid API key | Verify API key (not App Token) from Environment settings | | 400 Bad Request | Missing `user_id` | Include `user_id` field in request body | | 400 Bad Request | Company without `id` | If `company` object included, it must have `id` field | | User not appearing | Wrong environment | Verify endpoint matches your environment (US vs EU) | ## Related * [Track Event API](/api-references/real-time/track-event) - Track user actions * [Identify Company API](/api-references/real-time/identify-company) - Create/update companies * [Bulk Identify Users](/api-references/bulk-updates/users) - High-volume user operations * [Data Model Reference](/reference/data-model) - User and company schema details * [JavaScript SDK identify()](/developer/installation/web) - Browser-based identification # Overview Source: https://docs.userpilot.com/api-references/real-time/overview Real-time HTTP APIs for user and company identification and event tracking in Userpilot. These APIs allow you to identify users and companies, update their profiles in bulk, and track custom events in real time. Integrate these endpoints with your backend or service to keep your Userpilot data up-to-date and actionable. ## Endpoints * [**Identify User**](./identify-user): Create or update individual user profiles, or update multiple users in bulk. Useful for onboarding, profile updates, and large-scale data syncs. * [**Identify Company**](./identify-company): Create or update individual company profiles, or update multiple companies in bulk. Ideal for B2B platforms and company-level analytics. * [**Track Event**](./track-event): Record custom events for users to monitor engagement, feature usage, and more. ## Rate Limits The real-time HTTP APIs enforce rate limiting to ensure optimal performance and fair usage across all applications. Understanding these limits helps you design robust integrations that work efficiently within the platform's constraints. ### Rate Limit Details * **HTTP Identify API**: **600 requests per minute** per application token * **HTTP Track Events API**: **600 requests per minute** per application token ### Error Responses When you exceed these limits, the API returns: * **`429 Too Many Requests`**: Returned when you exceed the 600 requests per minute limit ### Best Practices > [!TIP] > > **Implement Exponential Backoff**: When you receive a 429 error, wait progressively longer before retrying (e.g., 1s, 2s, 4s, 8s). > > **Monitor Your Usage**: Track your request patterns to stay within limits and optimize your integration performance. > > **Handle Errors Gracefully**: Always implement proper error handling for rate limit responses in your applications. # Track Event Source: https://docs.userpilot.com/api-references/real-time/track-event Record custom user events in real time with Userpilot's Track Event HTTP API to power analytics, segmentation, and personalized experiences. The Track Event API allows you to record custom events for users in real time. Use this API to monitor user actions, feature usage, and engagement for analytics and personalized experiences. ## When to Use Use the HTTP Track API when: * Recording events from server-side code (e.g., payment completed, subscription changed) * Tracking actions that don't happen in a browser * Syncing events from external systems (CRM, billing, support) **Use the JavaScript SDK `userpilot.track()` instead** when: * User performs action in your web application * You need the event immediately available for content triggering ## Prerequisites * User must already exist in Userpilot (identified via SDK or API) * Userpilot API Key from [Settings > Environment](https://run.userpilot.io/environment) ## Endpoint ```http theme={null} [POST] https://analytex.userpilot.io/v1/track ``` ## Headers | Header | Value | Required | | - | - | - | | `Content-Type` | `application/json` | Yes | | `Authorization` | `Token {YOUR_API_KEY}` | Yes | | `X-API-Version` | `2020-09-22` | Yes | ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `user_id` | string | Yes | Identifier of the user associated with the event | | `event_name` | string | Yes | Name of the event to track | | `metadata` | object | No | Key-value pairs providing additional event details | ## Example ```json theme={null} { "user_id": "unique_user_id", "event_name": "user_subscribed", "metadata": { "plan": "free", "created_at": "1519205055" } } ``` ## Example cURL Command ```bash theme={null} curl -X POST https://analytex.userpilot.io/v1/track \ -H 'Content-Type: application/json' \ -H 'Authorization: Token {YOUR_API_KEY}' \ -H 'X-API-Version: 2020-09-22' \ -d '{ "user_id": "unique_user_id", "event_name": "user_subscribed", "metadata": { "plan": "free", "created_at": "1519205055" } }' ``` ## Response A successful event tracking returns HTTP status code 202 Accepted. > [!WARNING] > > Only primitive types (string, number, boolean, null) are supported in metadata. > [!TIP] > > Use this endpoint to track any custom event relevant to your analytics or engagement workflows. *** ## Common Issues | Error | Cause | Solution | | - | - | - | | 401 Unauthorized | Invalid API key | Verify API key from Environment settings | | 400 Bad Request | Missing `user_id` or `event_name` | Both fields are required | | Event not appearing | Processing delay | Events may take up to 15 minutes to appear in dashboard | | Event not triggering content | User not identified | Ensure user exists before tracking events | ## Related * [Identify User API](/api-references/real-time/identify-user) - Create users before tracking events * [JavaScript SDK track()](/developer/installation/web) - Browser-based event tracking * [Events Dashboard](/data-events/events-dashboard) - View tracked events * [Funnel Reports](/product-analytics/reports/funnels) - Analyze event sequences ## FAQs > **What's the difference between the Track Event API and SDK's userpilot.track()?** > > Use the Track Event API for server-side events, actions that don't happen in a browser and use the JavaScript SDK's userpilot.track() when the action happens in your web app and you need the event available immediately for content triggering. > **How long does it take for a tracked event to appear in the dashboard?** > > Events may take up to 15 minutes to appear due to processing delay. If an event still hasn't shown up after that, check that the request returned 202 Accepted and that user\_id matches an already-identified user. > **What data types can I send in metadata?** > > Only primitive types such as string, number, boolean, or null are supported currently and \ > nested objects/ arrays aren't supported. # Building your Workflow Source: https://docs.userpilot.com/building-your-workflow Workflows in Userpilot allow you to design and automate multi-step user journeys across your product. ## Overview Workflows in Userpilot allow you to design and automate multi-step user journeys across your product. Using a drag-and-drop builder, you can connect content, data updates, and logic into a single, easy-to-understand sequence — complete with branching conditions, time delays, and automatic property updates. Whether you're guiding new users through onboarding or re-engaging customers at key moments, Workflows lets you orchestrate the right experience at the right time. > [!NOTE] > > Workflows are only available for Growth and Enterprise plans. ### Use cases * **Build guided onboarding journeys:** Create step-by-step onboarding experiences that adapt based on user actions, progress, or attributes. * **Drive feature adoption:** Trigger targeted flows, tooltips, or checklists after users complete key milestones or engage with specific features. * **Automate lifecycle communication:** Send surveys, emails, or in-app messages at strategic moments, including delayed follow-ups days or weeks after an event. * **Personalize user experiences:** Branch journeys dynamically based on user behavior, company data, or engagement with previous content. * **Track and update user data automatically:** Trigger events or update user properties as users progress through a workflow to keep data accurate and actionable. * **Improve retention and re-engagement:** Reconnect with inactive users through time-based nudges, reminders, or contextual experiences. * **Coordinate cross-channel journeys:** Combine in-app content with email and mobile experiences to create cohesive, multi-touch user journeys. * **Experiment and optimize journeys:** Visualize dependencies between steps to identify bottlenecks, test variations, and continuously improve outcomes. ![Image](https://mintcdn.com/userpilot/FqbDo5rXYzcojyC-/images/image-11.png?fit=max\&auto=format\&n=FqbDo5rXYzcojyC-\&q=85\&s=e770a3234085995255257093ccbf752d) ## Get started To get started, simply navigate to the **Workflows** page from the navigation bar — here, you’ll see a table of all your created Workflows or have the option to create a new one. ## Define your Start and Exit Settings Before building your workflow steps, you’ll need to configure the start and end settings. These settings determine **who enters the workflow, when they enter, and when they exit**. ![Image](https://mintcdn.com/userpilot/FqbDo5rXYzcojyC-/images/image-12.png?fit=max\&auto=format\&n=FqbDo5rXYzcojyC-\&q=85\&s=408098e1b9f19957b9cd44d77cb4063d) ### **1. Define start settings** Start settings control how users are enrolled into the workflow. **Enrollment options** Choose how users should enter the workflow: * **Match condition** — Users are enrolled automatically once they meet the defined audience conditions. * **Event occurrence** — Users enter the workflow immediately after performing a specified event. * **Only-manually** — Users are added after triggering manually through other Userpilot content, a permalink or programatically. **Audience settings** Define which users are eligible to enter the workflow by configuring audience conditions such as user properties, company data, segments, events, or behaviors. Only users who meet these criteria will be enrolled. * **All Users:** Any identified user will be enrolled. * **Only Me:** Only enrolls users who have access to your Userpilot account (useful for testing). * **Saved Segment:** Use a predefined user segment to enroll a specific group. * **Custom Conditions:** Create a targeted subset of users by setting specific rules. **Environment selection** Select the environment where the workflow should run (i.e. production or staging). This is helpful for testing the workflow in your staging environment before making it live for all users. ![Image](https://mintcdn.com/userpilot/FqbDo5rXYzcojyC-/images/image-13.png?fit=max\&auto=format\&n=FqbDo5rXYzcojyC-\&q=85\&s=f1448db47860307873cb66bf37ee7de7) ### **2. Define exit settings** Exit settings determine when a user exits the workflow and whether they can enter again in the future. **Goal:** Define a goal to measure how your flow impacts user behavior and track key interactions. **Workflow exit rules:** Choose when users should exit the workflow: * **Reach the end** — Users remain in the workflow until they move through the final step. * **Complete a goal** — Users exit as soon as they achieve the defined goal, even if they haven’t reached the final step. **Re-enroll:** Control whether users can enter the workflow again after completing or exiting. This is useful for recurring processes such as periodic surveys, or lifecycle campaigns. ## Build your workflow Begin designing the workflow itself by adding steps to the canvas. Workflow steps define what happens after a user enters the workflow and allow you to create structured, behavior-driven journeys. ![IMAGENEW 1](https://mintcdn.com/userpilot/kVQ_Tcc4vRqMOR7Y/images/IMAGENEW-1.png?fit=max\&auto=format\&n=kVQ_Tcc4vRqMOR7Y\&q=85\&s=b1cb9b87bffdabc8e6a4e6e688a8d8e4) There are three types of steps you can add: ### 1. Content steps Content steps allow you to trigger user-facing experiences at specific points in the workflow. These steps help you guide, educate, or engage users throughout their journey. You can trigger Flows, Spotlights, Banners, Checklists, Emails or Surveys. * **Create New or Clone:** Decide whether to build your content from scratch or duplicate an existing experience. When cloning, all content and configuration settings are copied from the original, (except for environment and audience, which are controlled by the workflow’s start settings). * **Skip logic:** From the step node’s ‘More actions’ menu, you can configure skip logic to move users to the next step if the content isn’t triggered within a defined time period. ![Image](https://mintcdn.com/userpilot/FqbDo5rXYzcojyC-/images/image-14.png?fit=max\&auto=format\&n=FqbDo5rXYzcojyC-\&q=85\&s=a2cc617a910ebd4653d92b44e598027c) ### 2. Logic steps Logic steps control the flow and timing of the workflow. They help you create personalized paths and ensure users move through the journey in the intended order. * **True / False branches** — Split the workflow into different paths based on conditions such as user data or behaviour, company data, segments, or content engagement. * **Time delays** — Pause the workflow before the next step occurs for the user. Delays can be set for a specific duration (minutes, hours, days, weeks, or sessions) or until a specific date and time. ### 3. Data steps Data steps allow you to trigger backend actions without showing user-facing content. These steps help you keep your data accurate and track workflow progress. * **Track event** — Send an event when a user reaches a specific point in the workflow. * **Update property** — Update a user property with a predefined value when the step is reached. ![Image](https://mintcdn.com/userpilot/FqbDo5rXYzcojyC-/images/image-15.png?fit=max\&auto=format\&n=FqbDo5rXYzcojyC-\&q=85\&s=ca9d11176fb166726feb4263fcfc6f69) # AI Assistant Source: https://docs.userpilot.com/ce-basics/ai-assistant Use Userpilot's AI Assistant to generate, refine, and optimize in-app copy instantly, improving clarity, engagement, and consistency across flows. ## Overview Userpilot's AI writing assistant is a powerful copywriting tool that helps you enhance your content effortlessly. Whether you're refining existing text or creating something entirely new, the AI assistant leverages natural language processing to generate, improve, and optimize your writing. It offers a fast, reliable way to perfect your copy without needing expert-level writing skills or a dedicated content team. ## Use Cases * Instantly generate or refine UI text (tooltips, modals, banners) without relying on your content team. * Enhance existing onboarding flows by using AI to generate relevant copy, summarize, or extend content for clarity and engagement. * Automatically fix spelling, grammar, and punctuation errors in your copy, ensuring professionalism and accuracy. * Simplify and optimize onboarding flows by eliminating redundant or unnecessary steps in your copy. ## Guide Using the AI writing assistant is quick and simple. Here's how to get started: #### Starting from Scratch 1. In any text box within modals, tooltips, banners, or similar UI elements, press **'space'** to open the prompt field. 2. Type your desired prompt (e.g., "). 3. The AI will generate the relevant text for you, which you can tweak as needed. ![Animation](https://mintcdn.com/userpilot/-1T26zc10zXx-NJl/images/Animation.gif?s=4290b91cb612c19c7e7a924de5ee2ad5) #### Enhancing Your Existing Text If you already have some content, you can ask the AI to refine it or make adjustments: * **Continue writing:** If you're unsure how to finish your copy, ask AI to continue the text. It will seamlessly build upon your existing message. * **Fix spelling and grammar:** With a single click, the AI will automatically correct spelling and grammar mistakes, ensuring a polished output. * **Summarize:** If your content is lengthy, AI can summarize the key points, keeping it concise and to the point. * **Improve writing:** Let AI enhance your writing by rephrasing your sentences to be clearer, more engaging, or more professional. * **Make longer:** Need to elaborate on an idea or explain something in greater detail? The AI will add extra content to expand on your existing text. * **Make shorter:** Ideal for space-limited UI, such as tooltips, AI will condense your message while maintaining its core meaning. ![Animation2](https://mintcdn.com/userpilot/-1T26zc10zXx-NJl/images/Animation2.gif?s=685dbe03842858349670d12b18b66da3) Once the AI generates the new text, you can either accept the suggestion, tweak it as needed, or discard it and revert to your original draft. If discarded, your text will return to its initial state. ### Best Practices * **Be Specific with Prompts:** The more specific your prompt, the more tailored the AI output will be. For example, instead of "write a message," try "write a friendly welcome message for first-time users." * **Use AI for Quick Iterations:** Don't be afraid to experiment with different prompts to generate multiple variations of content, allowing you to choose the best fit for your app. * **Adjust AI Suggestions as Needed:** While the AI is a powerful tool, it may still need a human touch. Feel free to make small edits to ensure the tone and message align with your brand. *** ### FAQs > **How accurate is the AI assistant?** > > The AI assistant is powered by OpenAI's advanced language models, making it highly accurate in grammar, structure, and style. However, for optimal results, it's best to review the content to ensure it matches your brand's tone and specific context. > **Is there a limit to how much text the AI can generate?** > > There is no strict limit to the amount of text the AI can generate. However, for readability and UI constraints, you may want to ensure that the generated text fits within your design requirements. > **Can the AI generate content in multiple languages?** > > The AI writing assistant can generate content in multiple languages, with the output depending on the language used in the input text. # Button Actions Source: https://docs.userpilot.com/ce-basics/button-actions Create seamless user journeys by assigning actions to buttons across your in-app experiences. ## Overview Button actions in Userpilot allow you to trigger specific actions when a user interacts with a button. These actions can help guide the effort experience, navigate through steps, or trigger external events, such as navigating to URLs or executing JavaScript functions. Button actions provide flexibility in customizing user interactions and can be used across different flows and steps to create a seamless experience. ## Use Cases * Guide users through multiple steps in a flow or jump to a specific step. * Redirect users to a specific URL or dynamic link based on user attributes. * Activate external flows, surveys, or JavaScript functions for more complex interactions. * Customize user experience by skipping groups or dismissing flows. ## Button Action Types Userpilot provides a variety of button actions to help you control the flow of the user experience. Each action can be used to perform specific tasks, such as navigating, triggering flows, or managing user settings. ![Screenshot 2026 06 02 At 11 40 43 PM](https://mintcdn.com/userpilot/OsTbaFrNYV-atQEF/images/Screenshot-2026-06-02-at-11.40.43-PM.png?fit=max\&auto=format\&n=OsTbaFrNYV-atQEF\&q=85\&s=18903be7b8cb68851e9e08133a6373f8) > **Go to Next Step** > > This action will automatically navigate the user to the next step in the current flow. > **Go to URL** > > Redirect users to an external URL when they click the button. This is perfect for sending users to a webpage, a documentation page, or any external resource. > > > [!WARNING] > > > > If the URL is dynamic, make sure to pass the dynamic part as a user property. Then, replace the dynamic portion of the URL with the property placeholder to ensure each user is directed to the correct personalized URL. > > > > ![images/Screenshot2025-03-21at1.13.00PM.png](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/ce-basics/assets/images/Screenshot2025-03-21at1.13.00PM.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=7a653188de3e886b099c77e714bd84b3) > **Go to Previous Step** > > If users need to go back to the previous step, this action allows them to reverse direction and revisit the prior screen in the flow. > **Go to a Specific Step** > > Instead of progressing step-by-step, you can jump to any specific step in the flow. This action allows more flexibility when creating non-linear user journeys. > **Trigger Flow** > > You can trigger a different flow when the user clicks the button. This is useful for connecting flows that may run independently but need to be linked based on user actions. > > > [!WARNING] > > > > Triggering Userpilot content from a button will not work in preview mode. To test it, you need to publish both the content and the flow, then test it live. > **Trigger Survey** > > Use this action to trigger a survey, allowing you to gather user feedback or conduct surveys at specific points in your flow. > **Trigger JavaScript Function** > > This action enables you to run a custom JavaScript function when the button is clicked. It’s ideal for integrating custom behaviors or triggering external scripts as part of the flow. > **Skip to Next Group** > > If your flow is organized into groups, this action will let the user skip over the current group of steps and move directly to the next group. Great for skipping optional steps or providing an expedited flow. > **Dismiss Flow** > > 1. **No Action** This button action simply closes the current flow without taking any other action. It is useful for offering users a way to dismiss or cancel the flow. > > 2. **Never Show This Flow Again** By selecting this action, users will be opted out of seeing the same flow in the future. It’s a good option for scenarios where users are familiar with the flow and don’t need to see it again. > > > [!WARNING] > > > > The settings in the button action will override the flow’s frequency settings. For example, if the flow is set to trigger every time but the button action is set to "Never show this flow again," the flow will not trigger again after the button action is clicked. > > 3. **Show This Flow in the Next Session** If you want the user to see the flow again at a later time, this action will ensure that the flow is shown to them in the next session. > > > [!NOTE] > > > > A new session is defined when a user returns to interact with the app after being inactive for more than 30 minutes. This means that actions like logging in or out, or closing and reopening the tab, do not automatically start or end a session. > > 4. **Mark Flow as Completed** This action marks the flow as complete for the user, meaning they will no longer see the flow again unless manually triggered. > 5. **Unsubscribe User from Folder** In certain cases, you may want to unsubscribe a user from receiving further messages or updates from a folder. This action allows you to manage user subscriptions directly from the flow. ## Additional Functionality ### Update Properties and Track Events In addition to triggering the actions listed above, you can also update user properties and track events directly from the button actions. ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/ce-basics/assets/images/Screenshot2025-03-05at3.04.04PM.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=084bfb99c54f296a1b16b9ba87aa7084) * **Update Properties:** You can update an already passed user property (such as a user’s status, role, or preferences) when a button is clicked. For example, you might update a user’s progress or completion status after they finish a flow. * **Track Events:** You can trigger specific events that will be logged for future analysis, helping you track how users interact with the flow and their behaviour during the process. This is especially useful for gathering data on button clicks or flow interactions to improve the overall user experience. ### FAQs > **What happens if a user dismisses a flow? Can they re-access it later?** > > If a user dismisses a flow, you can set the flow to show again in their next session or re-trigger it using specific conditions. If you've selected "Never Show This Flow Again," the user will not see it again unless manually re-triggered. > **If I'm using a Javascript in the button action, how can I also take the users to the next step?** > > You can surely achieve that by adding "userpilot.next();" to your Javascript code. > **How can I horizontally align two buttons on a section of the UI pattern?** > > You can align two buttons by first adding a button next to an existing button.  > > 1. Open your Flow. > 2. Hover the mouse pointer over the UI pattern, next to the existing button. A purple (+) button displays. > > ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/ce-basics/assets/images/file-dLUz78mnDx.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=26ba3f361d7b54b8d841cc26855cc7ab) > 3. Click the purple (+) button and choose "button" # Embeds Source: https://docs.userpilot.com/ce-basics/embeds Embed third-party tools like Calendly, Loom, Typeform, and Synthesia directly into Userpilot flows to deliver rich, interactive in-app experiences. ### Overview Userpilot's embed feature allows you to enhance user experiences by seamlessly integrating content from popular platforms directly into your flows. With just a click, you can embed tools like **Calendly, Loom, Typeform, and Synthesia**, making it easier to engage users, collect feedback, and provide dynamic content, all without leaving your site. ### Use Cases * Embed **Calendly** to let users book meetings instantly within Userpilot content. * Use **Loom** to embed explainer videos, tutorials, or support responses. * Integrate **Typeform** to collect feedback and insights effortlessly. * Add **Synthesia** videos to deliver personalized and scalable video content. * Adjust width, height, and padding for a fully tailored experience. ![Card settings](https://mintcdn.com/userpilot/O9Qz3xXrHx29CSpn/ce-basics/assets/images/card-settings.png?fit=max\&auto=format\&n=O9Qz3xXrHx29CSpn\&q=85\&s=4c5803aabc84813e8abf65d1a2126e23) ![Card design panel](https://mintcdn.com/userpilot/O9Qz3xXrHx29CSpn/ce-basics/assets/images/card-design.png?fit=max\&auto=format\&n=O9Qz3xXrHx29CSpn\&q=85\&s=ec1760bfdc60b99d1d193e60de68a178) ## Embedding Options ![](https://mintcdn.com/userpilot/O9Qz3xXrHx29CSpn/ce-basics/assets/images/block-type.png?fit=max\&auto=format\&n=O9Qz3xXrHx29CSpn\&q=85\&s=f8b032441642b79105b2ea4d3767518b) ### Calendly Easily integrate your **Calendly booking flow** into your Userpilot content, allowing users to schedule meetings without navigating away from your platform. This is ideal for product demos, customer support calls, or onboarding sessions, making scheduling frictionless and efficient. ### Loom Embed **Loom videos** into Userpilot modals to create engaging training materials, provide personalized support, showcase product demos, or deliver internal updates. By embedding Loom, you can add a human touch to your interactions and improve user engagement. ### Typeform Gather valuable insights with **Typeform's interactive forms and surveys**. Whether you're collecting user feedback, running customer satisfaction surveys, or building engaging quizzes, Typeform’s drag-and-drop editor makes it easy to design visually appealing and effective forms. Its modern design and flexibility make it one of the most user-friendly survey tools available. ### Synthesia Elevate your content with **AI-generated videos from Synthesia**. This tool allows you to create high-quality videos featuring AI-powered presenters, without the need for actors, filming, or editing. Use it for product walkthroughs, training sessions, or marketing videos. You can also scale personalized videos using Synthesia’s API integration. ![](https://mintlify.s3.us-west-1.amazonaws.com/userpilot/assets/images/select-embed.png) ### Customization To ensure a seamless fit with your design, Userpilot allows you to adjust the **width, height, and padding** of your embedded content, ensuring an optimal user experience. ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/ce-basics/assets/images/Screenshot2025-03-05at3.20.09PM.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=5fbaffaaf865a581d96fe4744e83bb9d) *** ### FAQs > **Will embedded content be mobile-friendly?** > > Yes! Embedded content is designed to be mobile-friendly and will adjust to different screen sizes for a smooth user experience. Additionally, you can control its visibility by selecting the Hide section for mobile option if needed. > **Can I track engagement with embedded content?** > > While embedded content is not tracked natively within Userpilot, most platforms (like Loom and Typeform) provide their own analytics to measure engagement. > **Why isn't my embed showing after being published?** > > You would need to check the z-index in the card settings and ensure it's set correctly on the page to avoid being overlapped by another DOM element. If the issue persists, please reach out to [support@userpilot.com](mailto:support@userpilot.com). # Personalize Content Source: https://docs.userpilot.com/ce-basics/personalize-content Personalize Userpilot flows with dynamic variables to show tailored content for each user, increasing relevance, engagement, and overall product adoption. ## **Overview** Personalization allows you to dynamically display content within your flow. This feature helps make your interactions feel more personalized and tailored to each user, boosting engagement and enhancing their experience with your product. ## **Use Cases** * Use dynamic content like the user's name to make the experience feel more personalized and inviting. * Add personalized messaging throughout your flows to increase relevancy. * Personalizing content based on user data can create a stronger connection and encourage continued use. ![](https://mintcdn.com/userpilot/i_hL61WZW7vm9Xm1/Screenshot2025-07-10at12.57.20.png?fit=max\&auto=format\&n=i_hL61WZW7vm9Xm1\&q=85\&s=7b83a3028c2cbaa87eb8a4951a6dc654) ### **Sending and Using Variables in Personalization** To use personalization in your flows, you must first send custom user property data to Userpilot during the installation process. This allows you to pass variables (such as the user’s first name) through the snippet, which will then be dynamically inserted into your UI patterns. ### **Inserting Dynamic Variables into Your Flows** 1. **Create your UI pattern**: Design the flow and add the necessary text sections as you usually would. 2. **Use the personalization icon**: In the text toolbar, click on the personalization icon to insert a dynamic variable. 3. **Select the dynamic user property**: Choose the variable (like the user's first name) you want to substitute in that part of the text. ![Screenshot2025 07 10at12 58 12 Pn](https://mintcdn.com/userpilot/eKuoNPh2qVInx2ej/images/Screenshot2025-07-10at12.58.12.png?fit=max\&auto=format\&n=eKuoNPh2qVInx2ej\&q=85\&s=a5be4e050b424badbe5e163a9e0f2676) ### **Setting Up Fallback Text** Sometimes, a user property may be missing (e.g., the user's first name hasn't been set). In this case, you can specify a fallback value. Simply type the fallback text in the "Fallback value" box. This ensures that if the dynamic data isn’t available, a default message (like “Hello, valued user!”) is shown instead. ![](https://mintcdn.com/userpilot/i_hL61WZW7vm9Xm1/Screenshot2025-07-10at12.59.00.png?fit=max\&auto=format\&n=i_hL61WZW7vm9Xm1\&q=85\&s=19aa5ba95f782c275a307ce52bf4ba55) *** ### **FAQs** > **Can I test personalization before launching my flow?** > > Absolutely! You can preview your flows with dynamic variables to see how the personalization will look in action, ensuring that everything displays correctly before going live. You can also set the flow to "Only Me" for testing. > **Can I personalize content for anonymous users?** > > Anonymous users don’t have passed properties, so personalization cannot be applied to them since they don't have a name or a userid. # Create & Preview Content Source: https://docs.userpilot.com/ce-basics/preview-content Creating, managing, and previewing content can be done directly with the Userpilot Chrome Extension on your web app for Flows, Spotlights, Embeds, and Events. ### Overview Creating, managing, and previewing content can be done directly with the [Userpilot Chrome Extension](https://chromewebstore.google.com/detail/userpilot/ecmjjdafjebhegfhjincbdhgfonpibfm) on your web app for Flows, Spotlights, Embeds, and Events Labeling. After installing the extension on your browser, simply open your web app and run the extension to start your content building. ### Use Cases * Create content based on your web application and preview it directly there * Check how the user journey looks like in your end user's shoes * Make adjustments and preferences that suit your use case based on how your web app is built ### Desktop & Mobile Preview After building the content there is an option to preview/test it. For flows, we provide a full-flow preview and certain steps preview as well. Each step can be clicked on to adjust the related settings, add steps into groups, page changes within the same flow, localization, and logic. ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/ce-basics/assets/images/Screenshot2025-02-25205106.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=40df7ba10cdb40e71cabf4b0391ed67c) Our builder also supports responsive screens, previewing content on Mobile view can be done by switching the screen to a mobile browser in addition to hiding certain sections to keep things neat, make reading simpler, and remove any extra clutter for smaller screens. > [!NOTE] > > Only the related content can be viewed, triggering other content cannot be tested in preview under the selected one (f.e. setting a certain button to trigger another flow) ### FAQs > **How can I set content to show for a certain device type (Desktop or Mobile)?** > > This can be done by setting the Device Type under the Audience settings. > > ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/ce-basics/assets/images/Screenshot2025-02-25193129.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=dc40a90784b000db0e2ec74ed476c5f7) > **Is Mobile preview supported for all content types?** > > Currently it's only supported for Flows, with the potential of adding it for other content types if needed. # Select The Right Element Source: https://docs.userpilot.com/ce-basics/select-element Learn how to accurately select and detect UI elements in Userpilot to ensure flows, tooltips, and product tours attach to the right components. ## Overview Detecting the right element in Userpilot is crucial for providing a personalized user experience through product tours, tooltips, and flow steps. Proper element selection ensures that the right features are highlighted and tracked for user engagement. The platform allows automatic detection of elements, with an option for manual adjustments when needed, ensuring accurate implementation of onboarding flows. ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/ce-basics/assets/images/file-mKA8VMI1UD.gif?s=48adb0472820b18932daee92fece7c7a) ## Use Cases * Highlighting specific elements on a web application interface. * Continuing a flow even when the element is not visible on the page. * Modifying element detection to ensure proper onboarding functionality. ## Main Section ### What is a CSS Selector? A **CSS selector** is a pattern used to select and target HTML elements on a web page. These selectors allow Userpilot to accurately identify and interact with page elements. CSS selectors can target elements based on their ID, class, type, or other attributes. Inspecting the element with your browser’s "**Inspect**" tool can help you find the CSS selectors used by a particular element. ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/ce-basics/assets/images/file-eTgnuJBF8a.gif?s=f3bbe75eb9653c1d86e51be31376a364) ### How Userpilot Detects Elements Automatically When adding a new step in Userpilot, the builder automatically detects elements and selects the best CSS selector for them. It visually highlights the element on the page with a rectangle around the target. Based on this, Userpilot determines the optimal placement for UI patterns, such as tooltips or driven actions, on your app. ### When to Manually Adjust CSS Selectors Manual adjustments should only be made when automatic detection doesn’t work as expected, which occurs in rare cases. These include situations where: * The element is not detected properly, possibly due to dynamic content or complex page structures. * There are too many similar elements, causing the wrong one to be selected. ### How to Adjust CSS Selectors Manually 1. Choose the **'Manual'** option under the **Element** menu. 2. Once enabled, you can edit the CSS selectors for better precision. 3. To ensure that multiple similar elements are handled correctly, consider adjusting the **element order** or adding a text string filter to further specify the element. ![Screenshot 2026 07 10 122335](https://mintcdn.com/userpilot/GFskr1VmhOf4-e8X/images/Screenshot-2026-07-10-122335.png?fit=max\&auto=format\&n=GFskr1VmhOf4-e8X\&q=85\&s=764bcbf004b8365961dcb83291b53491) If the selected element contains dynamic selectors that may change with each page load or interaction, Userpilot provides an **Exclude List** feature to help mitigate this issue. By working with your development team, you can exclude dynamic selectors from being added when Userpilot detects the element. This ensures that once the dynamic selectors are added to the exclude list, any future element selections containing those selectors will be automatically ignored by Userpilot. > [!NOTE] > > Elements previously selected with dynamic selectors won't be automatically excluded. You'll need to manually reselect them after adding the CSS selectors to the Exclude List to ensure proper tracking. ### FAQs > **How do I deal with multiple similar elements that could be mistakenly selected?** > > If you encounter this issue, manually adjust the CSS selectors by specifying the correct element using its unique attributes or text content. # Switching Themes Source: https://docs.userpilot.com/ce-basics/switching-themes Customize your flows with themes for consistent branding and flexible styling. ## Overview Switching themes in Userpilot allows you to customize the look and feel of your flow, either globally or on a per-step basis. You can maadoption se themes to provide a cohesive experience for your users or tailor the design for each individual step. The flexibility to adjust themes within the builder provides you with full control over how your content is presented. ## Use Cases * **Global Theme:** Set a consistent theme across the entire flow by adding a global theme, ensuring a unified visual style. * **Step-Specific Theme:** Modify individual steps with custom themes for more dynamic and tailored user experiences at specific points. ![](https://mintcdn.com/userpilot/OsTbaFrNYV-atQEF/images/Screenshot-2026-06-08-at-11.42.51-PM.png?fit=max\&auto=format\&n=OsTbaFrNYV-atQEF\&q=85\&s=fa9158aef15b432b42effcf434816ded) ## Global Theme In Userpilot, you can apply a global theme to the entire flow. This theme is visible when you click the pen icon in the builder, giving you access to the global settings for the flow's design. Any changes made here will apply to all steps in the flow, ensuring consistency across the user journey. ## **How to Set a Global Theme** * Open the flow in the Userpilot builder. * Click on the pen icon at the top of the flow to access the global theme settings. * Choose your desired theme from the available options, and it will automatically apply to every step in the flow. ![Screenshot 2026 06 08 At 11 45 22 PM](https://mintcdn.com/userpilot/OsTbaFrNYV-atQEF/images/Screenshot-2026-06-08-at-11.45.22-PM.png?fit=max\&auto=format\&n=OsTbaFrNYV-atQEF\&q=85\&s=5df6e649cdaba73cc27395deaf82330c) ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/addtogroup.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=35c6e869488bbfc619e7af7238a317aa) ## Switching Theme for Individual Steps You also have the ability to customize the theme for each individual step in the flow. By selecting a specific step, you can change its theme independently of the global theme to create variation when needed. This is ideal for highlighting specific steps or offering unique experiences for different parts of the flow. ### **How to Change Theme for a Selected Step** * In the flow builder, click on the step you wish to modify. * In the step settings panel, look for the theme option and select the desired theme for that step. * This theme will override the global theme for that particular step, giving it a distinct style. ![](https://mintcdn.com/userpilot/OsTbaFrNYV-atQEF/images/Screenshot-2026-06-08-at-11.49.29-PM.png?fit=max\&auto=format\&n=OsTbaFrNYV-atQEF\&q=85\&s=bd31bd3ae13c5734bb69f08b86e18ffe) ### Understanding "Custom" Theme When a step has the theme labeled as "Custom," it means that manual adjustments have been made to the theme for that step. **Why the "Custom" Label Appears** * When you manually adjust the styling for a step, the theme will be marked as "Custom" to differentiate it from global theme. * This helps you keep track of which steps have unique design customizations. ### FAQs > **Can I revert a step to the global theme after making custom changes?** > > Manual changes override theme settings. For instance, if you change the font color manually, applying a new theme won’t affect it. To revert, you’ll need to manually reset those changes. > **Will changing a step's theme affect the rest of the flow?** > > No, changing the theme for an individual step will only affect that step and will not change the global theme or any other steps. # UI Templates Source: https://docs.userpilot.com/ce-basics/ui-templates Save and reuse UI pattern templates in Userpilot to apply consistent styles, layouts, and settings across multiple flow steps and experiences. ## Overview Saving a UI pattern template allows you to reuse custom styles and settings across multiple steps. This feature helps streamline the design process and ensures consistency in user interfaces, saving time when creating similar patterns. By creating templates, you can focus more on design and less on repetitive tasks. ### Use Cases * **Rapid Deployment:** Quickly apply consistent UI patterns across multiple steps or projects. * **Branding Consistency:** Ensure uniformity in design by saving your preferred theme. * **Efficiency in Development:** Reduce time spent manually adjusting settings for every new step or page by reusing templates. * **Customization Across Projects:** Ideal for teams working on similar experiences but with slight variations in branding or layout. ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/ce-basics/assets/images/Screenshot2025-03-08at4.28.55PM.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=3a7a736a7db4318871d0b996eea06623) ## How to Save a UI Template Follow these steps to save a UI pattern template for future use: ### Step-by-Step Process **1. Edit Your UI Pattern** Start by designing and adjusting your UI pattern according to your needs. **2. Save Your Template** Once you're satisfied with your design, go to the tool settings section and click on the "Save Template" button. This action stores the UI pattern for reuse. ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/ce-basics/assets/images/Screenshot2025-03-08at4.29.36PM.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=fbae30448693b91986f3adc4b8b10e2e) **3. Access Saved Templates** After saving, you can easily access your templates anytime by navigating to the "SAVED" tab. When creating a new step of the same type, simply select your saved template from the list. ### What Settings Are Saved The following settings are saved when you save a template: * **Style Settings:** Customizations like background color, font size, and button actions are stored. > [!NOTE] > > Note: Some of these style settings may not be saved depending on the specific UI pattern and its implementation (e.g., Cards). * **Layout & Element Preferences:** Settings related to elements such as alignment, padding, and spacing. ### What Settings Are Not Saved The following settings are not saved when you save a template: * **Side-Panel Settings:** Elements like beacons, box configurations, element placement, and backdrop settings won’t be stored in the template. ### Managing Your Saved Templates You have full control over your saved templates. If you want to delete or rename a template: * Navigate to the "SAVED" tab. * Hover over the template to reveal the three horizontal dots icon. * Click on the icon to access options to either delete or rename your template. ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/ce-basics/assets/images/Screenshot2025-03-08at4.31.20PM.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=8ab320bf9b26287570d372734d7df6f2) ![]() ### FAQs > **Can I save multiple templates?** > > Yes, you can save as many templates as needed and organize them for different steps or project types. They are all stored in the "SAVED" tab for easy access. > **How can I edit a saved template?** > > To make changes to a saved template, select it from the "SAVED" tab, modify the design, and save it again under the same name. You can also rename or delete templates as needed. #### # Data Capture & Privacy Source: https://docs.userpilot.com/configure/data-capture-and-privacy/data-capture-and-privacy The Data Capture & Privacy settings allow you to configure how user interaction data is collected, recorded, masked, and restricted across features like Session Replay and Auto-capture (raw events). ### Use Cases * **Control what user data is collected** – Decide which interactions, inputs, and sessions are captured to balance insights with privacy requirements. * **Protect sensitive information** – Mask or exclude specific elements, text inputs, URLs, or pages to prevent PII from being recorded. * **Enable secure session replays** – Observe real user behavior while ensuring sensitive fields and actions are hidden or excluded. * **Optimize session replay usage** – Make the most of your sessions allowance by recording only users or segments that are relevant to your analysis. * **Support compliance and governance** – Configure capture rules that align with GDPR, CCPA, or internal data-handling policies. ## Key Controls & Options Before getting started, navigate to the ‘Data Capture & Privacy’ tab within your settings. ![Screenshot2025 12 18at15 16 25](https://mintcdn.com/userpilot/Ym5ZDQVtqh8A0PbT/images/Screenshot-2026-07-21-at-10.40.58-PM.png?fit=max\&auto=format\&n=Ym5ZDQVtqh8A0PbT\&q=85\&s=303352d21a5266dbcd33a8870b490508) ### 1. Core data capture settings * **Automatically capture frontend events** - Enable Userpilot’s **autocapture** feature (raw events) to record frontend interactions automatically: clicks, text input changes, and form submissions. When enabled, Userpilot will start collecting this data once the SDK is active in your app. * **Session playback toggle** - Enable or disable **Session Replay** entirely. If turned off, Userpilot won’t store or display replayable session data * **Capture text input** - Enable text capture if you want typed interactions to show up in replays; if disabled, all text inputs are masked. * **Detection of Frustrations** - Automatically flags signals that indicate a user struggled. It detects dead clicks, rage clicks, u-turns, form abandonment, and error clicks during a session. * **Network & Console Capturing** - Records network errors, console errors, and uncaught exceptions and syncs them to the session replay in the built-in Dev Tools panel, so you can debug without pulling in engineering. ![Screenshot2025 12 18at15 24 07](https://mintcdn.com/userpilot/Ym5ZDQVtqh8A0PbT/images/Screenshot-2026-07-21-at-10.44.42-PM.png?fit=max\&auto=format\&n=Ym5ZDQVtqh8A0PbT\&q=85\&s=78e302e286414a0a125e02044d40d642) ### 2. Element capture restrictions Use element-level rules to control how specific UI elements are captured in auto-captured events and session replays. * **Mask elements** — Hide the text content of selected elements in session replays. Masked elements remain visible in replays, but their content is replaced so sensitive information isn’t exposed. * **Exclude elements** — Fully exclude selected elements from data capture. Interactions, text, and metadata related to these elements won’t be recorded in raw events or session replays. * **Exclude attributes** — You can also exclude specific **attributes** (for example, `value`, `placeholder`, or custom data attributes) to prevent sensitive or unnecessary information from being captured. ![Screenshot2025 12 18at15 32 29](https://mintcdn.com/userpilot/3R7kPXhVasT4PvBg/images/Screenshot2025-12-18at15.32.29.png?fit=max\&auto=format\&n=3R7kPXhVasT4PvBg\&q=85\&s=b5a6457731eda73c049b7903aab77d53) ### 3. Data capture restrictions Data capture restrictions allow you to limit where auto-captured events and session data are collected. * **Restrict by domain** - Exclude specific domains or subdomains from being recorded. This is useful for omitting sensitive areas such as authentication flows, billing pages, or internal tools. * **Restrict by user location** - Control data capture based on a user’s physical location (for example, country or region). This helps you align data collection with regional privacy requirements or internal policies. * **Ignore Frustrations and errors** - Set rules to exclude specific interactions from frustration and error detection. Pick a frustration or error type (e.g. rage click), scope it to where it occurred (e.g. Path equals /checkout/payment), and Userpilot will stop flagging that interaction in sessions. ![Screenshot2025 12 18at15 45 01](https://mintcdn.com/userpilot/NnB2h7c1hQahcBny/images/Screenshot-2026-07-22-at-12.21.47-AM.png?fit=max\&auto=format\&n=NnB2h7c1hQahcBny\&q=85\&s=b146f1fc91e1531122435c4f5e2c8b12) ![Screenshot 2026 07 22 At 12 19 06 AM](https://mintcdn.com/userpilot/NnB2h7c1hQahcBny/images/Screenshot-2026-07-22-at-12.19.06-AM.png?fit=max\&auto=format\&n=NnB2h7c1hQahcBny\&q=85\&s=5ade9294943134e97fac025c77cb45ee) ### 4. Session replay restrictions Session replay restrictions let you control *whose* sessions are recorded and stored. * **Segment-based recording** — Limit session replays to specific user or company segments (for example, paid users, users on a certain plan, or users in a specific region). ![Screenshot2025 12 18at15 46 01](https://mintcdn.com/userpilot/NnB2h7c1hQahcBny/images/Screenshot-2026-07-22-at-12.21.47-AM-2.png?fit=max\&auto=format\&n=NnB2h7c1hQahcBny\&q=85\&s=fc06829857e75a7e8976852224f57ca4) ## See also * [Data Retention](/configure/data-retention/data-retention) — control how long Userpilot stores your analytics events and session replays. ### FAQs > **If I mask the text of an element, would Userpilot still capture the text and display it masked in the replays?** > > No, Userpilot would not capture the text of masked elements. So the text associated with the masked elements would not be saved in our database. # Data Retention Source: https://docs.userpilot.com/configure/data-retention/data-retention Control how long Userpilot stores your analytics events and session replays. Configurable on Growth and Enterprise plans, fixed on all other plans. > [!NOTE] > > Custom retention windows are available on **Growth** and **Enterprise** plans. All other plans use fixed retention values described below. ## Overview Data retention controls how long Userpilot stores the analytics events and session replays captured for your application. Each Userpilot organization has two independent retention windows: * **Tracked data and interactions** — how long Userpilot keeps analytics events (page views, tracked events, autocaptured raw events, content interactions) available for reporting. * **Recorded sessions** — how long Userpilot keeps session replay recordings available for playback. Aligning these windows with your needs helps you balance long-term trend analysis against compliance, privacy, and storage cost requirements. ## Use Cases * **Compliance and governance** – Configure shorter retention to align with GDPR, CCPA, or internal data-handling policies. * **Long-term trend analysis** – On Growth and Enterprise plans, extend retention to compare year-over-year product usage. * **Cost-aware operations** – Keep only the data you actively use for reporting, reducing the surface area of stored personal data. * **Audit and incident review** – Define a retention window long enough to reconstruct user journeys when investigating bugs or incidents. ## Retention by plan | Plan | Tracked data and interactions | Recorded sessions | Configurable? | | - | - | - | - | | Starter | **1 year** reportable (3 years stored) | **30 days** | No (fixed) | | Growth | **3 years** storage and reporting (default) | **30 days** (default) | Yes — contact your CSM representative | | Enterprise | **3 years** storage and reporting (default) | **30 days** (default) | Yes — contact your CSM representative | On Growth and Enterprise, both windows can be extended on request. Your current values are always visible in **Settings → Subscription → Plan details**. ## What's covered Retention windows apply to: * Tracked events (custom and built-in) * Autocaptured raw events * Content interactions (flow steps, checklist items, NPS / surveys responses, etc.) * Session replay recordings ## Where retention shows up in the product Retention windows are enforced consistently across the product. You'll see their effects in the following places. ### Settings → Subscription → Plan details The **Plan details** card now shows a **Data retention** section with two rows: **Tracked data and interactions** and **Recorded sessions**. The values reflect either your fixed plan limits or your custom configuration. ![Data retention section on the Plan details card](https://mintcdn.com/userpilot/3W5speZ1ZkK68cCr/configure/data-retention/assets/data-retention-plan-details.png?fit=max\&auto=format\&n=3W5speZ1ZkK68cCr\&q=85\&s=7f3b54edab5b29d947ee34c11755308d) Hover the info icon next to **Data retention** to see a tooltip with a **Change** button. ![Data retention tooltip with Change button](https://mintcdn.com/userpilot/3W5speZ1ZkK68cCr/configure/data-retention/assets/data-retention-tooltip-change.png?fit=max\&auto=format\&n=3W5speZ1ZkK68cCr\&q=85\&s=9da7dcac29da93b914f0d4e20e1d534c) ### Reports and Dashboards date pickers Date pickers in Reports and Dashboards are bounded by your analytics retention window. The earliest selectable date is the start of your retention window, and period presets that fall outside the window are hidden. When a previously selected start date falls outside your current retention window, Userpilot automatically adjusts it to the boundary and surfaces a tooltip that reads: **The date has been adjusted to stay within your data retention window.** ### Sessions date picker and filters The Sessions date picker, playlists, and saved filters are bounded by your **session replay** retention window — independent from analytics retention. ## Changing your retention period Changing retention is available on **Growth** and **Enterprise** plans only. 1. Open **Settings → Subscription → Plan details**. 2. In the **Data retention** section, hover the info icon and click **Change**. 3. A scheduling page opens so you can book a call with your Userpilot support representative to discuss the new retention values. If you're on Starter, or Trial, upgrade to Growth or Enterprise to unlock configurable retention. ## What happens when data expires * The cleanup job runs once per month, on the **first Saturday of the month at 03:30 UTC**. It is irreversible — expired data cannot be recovered. * Analytics events older than your configured window are deleted from storage in the partitions that match the expiry boundary. * Session replay recordings are tagged at capture time with their organization's retention window, and expire automatically through storage lifecycle policies. ## FAQs > **Can I recover data after it has expired?** > > No. Once the monthly cleanup runs, expired analytics events and session replays are permanently removed and cannot be restored. If you need a longer retention window, change it before your data expires (Growth and Enterprise only). > **Does retention affect data exported via the Export API?** > > Yes. Only data still within your retention window is available to export. Data that has expired through the monthly cleanup is no longer available through the Export API. > **Does session replay retention affect MAU counts or billing?** > > No. Monthly Active Users (MAU) counts and billing are independent from retention. Changing your retention window does not change how MAU is calculated. > **I can't select a date earlier than a certain point — why?** > > Date pickers across Reports, Dashboards, and Sessions are bounded by your retention window. The earliest selectable date is the start of that window. To go further back, you'd need a longer retention window (Growth and Enterprise only). > **I'm on Growth or Enterprise — what's my actual retention?** > > Unless your organization has a custom agreement, the defaults are **3 years** for **tracked data and interactions** (how long events are stored and available for reporting) and **30 days** for **recorded sessions** (session replay storage). Your exact values are always shown at **Settings → Subscription → Plan details** in the **Data retention** section. > **When does a change to my retention period take effect?** > > Changes apply immediately for new data. Existing data that falls outside the new (shorter) retention window is removed at the next monthly cleanup. Existing data that fits within the new (longer) retention window stays available for reporting and replay. # Exclude CSS Selectors Source: https://docs.userpilot.com/configure/exclude-lists/dynamic-attributes Easily excluding dynamically generated class names, IDs, and attributes in JavaScript frameworks like React.js and Ember.js that could affect Userpilot's ability to target elements properly. ## Overview Dynamically generated class names, IDs, and attributes in JavaScript frameworks like React.js and Ember.js can hinder Userpilot's ability to target elements accurately. This article guides users on configuring Userpilot to exclude such dynamic attributes using regular expressions, ensuring more reliable element targeting. These patterns are variable and might change on every page load or deployment. Some examples of such patterns: ```css theme={null} .sc-1fzu5ml-0 // styled-component .jss514 // React.js #ember45 // ember.js ``` To improve targeting, Userpilot lets you exclude attributes using Regex in the [Exclude Lists](https://run.userpilot.io/settings/exclude-lists) under Settings. ## **Example frameworks and how to exclude them** **Material-UI** Example: Excluding the auto-generated portion in the class with the format .jss514 ```text theme={null} jss(.+?)$ ``` ```text theme={null} css(.+?)$ ``` **Styled-components** Example: Using a regular expression to only match the stable portion of the class .sc-1fzu5ml-0 ```text theme={null} sc-(.+?)-(.+?)$ ``` **Ember** Example: Excluding the number in the ID #ember45 ```text theme={null} ember(.+?)$ ``` ![Screencapture Run Userpilot Io Settings Exclude Lists 2025 12 15 21 42 56 Pn](https://mintcdn.com/userpilot/0XEiWZ1rhiOKgu7L/images/screencapture-run-userpilot-io-settings-exclude-lists-2025-12-15-21_42_56.png?fit=max\&auto=format\&n=0XEiWZ1rhiOKgu7L\&q=85\&s=eb04d14e42bb3bbbafee3557323bb5bf) # Exclude Users and Companies Source: https://docs.userpilot.com/configure/exclude-lists/exclude-user Exclude specific users or companies from Userpilot analytics or from seeing in-app messages to keep your reports accurate and ensure insights reflect real end-user behavior. ## Overview You can exclude users and companies in two different ways, depending on your goal.\ The first option is to exclude them from analytics only, this keeps your reports focused on real end-user behavior, but those users can still see Userpilot in-app messages. Excluded traffic is still recorded in the background and can be re-included in analytics at any time.\ The second option is to exclude users or companies from seeing Userpilot in-app messages, which prevents them from being targeted by in-app experiences such as flows, checklists, or surveys. ## Use cases * **Excluding internal team members** - If your internal teams (Product, QA, Support, Sales) use your product regularly, excluding them helps prevent their activity from skewing analytics and feedback. * **Filtering out rest or QA accounts** - During testing, staging, or content validation, test users often generate high volumes of artificial engagement. * **Cleaning up historical analytics** - If certain users or companies were mistakenly included in the past, excluding them helps restore confidence in reports and long-term trends. * **Hide experiences from internal/test accounts** - Run targeted experiments, or avoid showing beta content to certain customers. ## Exclude users and companies from Userpilot analytics * Go to [****Settings > Exclude Lists → Internal Users****](https://run.userpilot.io/settings/exclude-lists) in your Userpilot dashboard * Click **+ Add User ID** under List of excluded User IDs * Enter the **User ID** and select the user ID ![Screenshot2025 12 15at21 55 40 Pn](https://mintcdn.com/userpilot/rS0mvMJvrQoSHz-a/images/Screenshot2025-12-15at21.55.40.png?fit=max\&auto=format\&n=rS0mvMJvrQoSHz-a\&q=85\&s=6f74ce731cce3985aaa3a85dbac01052) * To exclude a company, click **+ Add Company ID**, enter the **Company ID**, and select the company ID ![Screenshot2025 12 15at22 02 47 Pn](https://mintcdn.com/userpilot/rS0mvMJvrQoSHz-a/images/Screenshot2025-12-15at22.02.47.png?fit=max\&auto=format\&n=rS0mvMJvrQoSHz-a\&q=85\&s=079a567fba7b50600ce41af435cd7931) > [!WARNING] > > Excluding users or companies **does not affect live experiences**. Excluded users will still: > > * See in-app messages in **live mode** > * Trigger engagement events > * Generate notifications related to in-app message interactions > > This behavior is intentional today, and we’re considering enhancements in the future to provide more control over live delivery and notifications for excluded users. ## Exclude users and companies from seeing Userpilot in-app messages * Go to [**Settings > Exclude Lists → Internal Users**](https://run.userpilot.io/settings/exclude-lists) in your Userpilot dashboard * Under Excluded user IDs from Userpilot content, click **+ Add User ID** * Enter the **User ID** and select the user ID ![Screenshot2026 03 17at14 37 41](https://mintcdn.com/userpilot/7T6RxAPrbYWmASoJ/images/Screenshot2026-03-17at14.37.41.png?fit=max\&auto=format\&n=7T6RxAPrbYWmASoJ\&q=85\&s=44ddc9481776d858a890ca437dba7454) * To exclude a company, go to Excluded company IDs from Userpilot content, click **+ Add Company ID**, enter the Company ID, then select it from the list ![Screenshot2026 03 17at14 37 18](https://mintcdn.com/userpilot/7T6RxAPrbYWmASoJ/images/Screenshot2026-03-17at14.37.18.png?fit=max\&auto=format\&n=7T6RxAPrbYWmASoJ\&q=85\&s=d1159aba624d0d9ab1a8913d783d4bff) * Changes are applied **automatically**; no additional save action is required. > [!NOTE] > > You can also exclude **multiple User or company IDs in bulk** by selecting the **Wildcard** option. Enter the User or company ID, replace the dynamic part with an **asterisk (\*)**, and then click **+ Add User/company ID**. > > ![Screenshot2025 12 15at21 59 52 Pn](https://mintcdn.com/userpilot/rS0mvMJvrQoSHz-a/images/Screenshot2025-12-15at21.59.52.png?fit=max\&auto=format\&n=rS0mvMJvrQoSHz-a\&q=85\&s=ab8a3e0797e0d426f7a05198f275dbfd) # Digest notification Source: https://docs.userpilot.com/configure/notifications/digest Use Digest notifications in Userpilot to receive scheduled summaries of key product insights, engagement metrics, and feedback via email or Slack. ## Overview The **Digest** in Userpilot helps you stay on top of your key product insights automatically.\ It sends a **customized summary email or Slack notification** containing updates on the most important user activities and engagement metrics.\ Instead of checking multiple reports, you can get everything that matters in one simple, scheduled summary. With the Digest, every team member can **customize their own summary**, decide what to include, and choose how and when to receive it. ## Use cases 1. Get weekly insights into how users engage with flows, pages, or checklists. 2. Receive a summary of survey or NPS responses to track satisfaction trends. 3. Review engagement data on spotlights, embeds, and campaigns. 4. Get a quick overview of user activity and performance without logging in. ## What Userpilot offers in the Digest Userpilot’s **Digest** gives you full control over your notification preferences and structure: 1. **Custom delivery** - Choose how often you want to receive the summary: **daily, weekly, or monthly**, and send it via **email** or **Slack**. 2. **Category control** - Enable or disable categories like **Data**, **Feedback**, and **Engagement** using toggles. 3. **Granular filters** - Select whether to include **all content**, **only content you created**, or set **custom filters**. 4. **Personalized structure** - Rearrange the order of sections in your Digest using **drag-and-drop**, so your most relevant insights always appear first. 5. **User-Level configuration** - The Digest is fully personal. Each team member can set up their own Digest preferences, and notifications are sent **only to the logged-in user**. ![digest-overview](https://mintcdn.com/userpilot/15tMays9iGDTQjwe/ScreenRecording2026-08-13at4.58.40PM.gif?s=a75e2ca9c12337c9a96acce8857a62d5) ## How percentages and trends are calculated in the digest email The Digest email calculates metrics by comparing performance in the current period with the previous equivalent period. #### Weekly digest \ If the digest is scheduled to be sent on a Wednesday, the current period covers the time from that Wednesday to the following Wednesday. The previous period covers the Wednesday of the prior week up to the current Wednesday. The system then compares the data between these two time frames to show trends. #### Monthly digest \ If the digest is scheduled to be sent on the 21st of April, the current period runs from the 21st of March to the 21st of April. The previous period runs from the 21st of February to the 21st of March. The system compares these periods to highlight changes in performance. * In the Survey report, the Completed column indicates the number of users who have answered all survey questions. * For NPS, the percentage represents the number of users who selected a specific score divided by the total number of users who submitted an NPS response. > [!NOTE] > > Please note that all percentage values reflect trends in the data. > [!NOTE] > > The trend reflects the number of survey **completed** event. > > ![Screenshot2026 03 18at17 40 45](https://mintcdn.com/userpilot/O2AF4wnp9wmco4oH/images/Screenshot2026-03-18at17.40.45.png?fit=max\&auto=format\&n=O2AF4wnp9wmco4oH\&q=85\&s=7fbcb10a20736ff21d39033b1a679d81) > > ![Screenshot2026 03 18at17 41 03(1)](https://mintcdn.com/userpilot/O2AF4wnp9wmco4oH/images/Screenshot2026-03-18at17.41.03\(1\).png?fit=max\&auto=format\&n=O2AF4wnp9wmco4oH\&q=85\&s=3d568e746f4edf2a50c0b3f3ad1a3921) ## Notification examples 1. Slack notification ![Screenshot2025 10 07at16 22 49 Pn](https://mintcdn.com/userpilot/NlIR2NyubaxdaeSU/images/Screenshot2025-10-07at16.22.49.png?fit=max\&auto=format\&n=NlIR2NyubaxdaeSU\&q=85\&s=c58236b01b71a17d84c43dac32ed950f) 2. Email notification ![Screenshot2025 10 07at16 25 19 Pn](https://mintcdn.com/userpilot/NlIR2NyubaxdaeSU/images/Screenshot2025-10-07at16.25.19.png?fit=max\&auto=format\&n=NlIR2NyubaxdaeSU\&q=85\&s=c38fe3447228fa3590d4e9e32e06f7d0) # Get started with notifications Source: https://docs.userpilot.com/configure/notifications/get-started-with-notifications Get started with Userpilot notifications to stay aligned on product updates, performance insights, and system alerts via in-app, email, or Slack. ## **Overview** Userpilot Notifications help your team stay aligned and take action faster by centralizing updates from your workspace. You can receive notifications through **three types** of notifications, **System Notifications, Digests, and Signals**, and choose how they’re delivered: 1. **In-app:** Receive alerts directly inside Userpilot while working 2. **Email:** Get summarized updates or alerts sent to your inbox 3. **Slack:** Instantly share key updates with your team through connected Slack channels ## **Use cases** 1. Get notified when new flows, checklists, or experiments are published. 2. Receive a weekly or monthly summary of product usage, flow performance, and engagement. 3. Notifications can be automatically routed to Slack or email, helping Growth and CS teams act quickly. 4. Share insights directly in Slack to spark discussions or follow-ups. 5. Choose what you want to be notified about and how (e.g., only critical system alerts via Slack, performance digests via email). 6. Each teammate can customize their notification settings from Settings → Notifications. ## Next steps To start using notifications: 1. Go to **Settings → Notifications.** 2. Select the notification type you would like to receive: **System Notifications**, **Signals**, or **Digest**. 3. Select the **channels** (In-App, Email, Slack) you want to use. 4. Adjust your **preferences** based on the types of alerts or digests you want to receive. ![Screenshot2025 10 07at14 28 07 Pn](https://mintcdn.com/userpilot/NlIR2NyubaxdaeSU/images/Screenshot2025-10-07at14.28.07.png?fit=max\&auto=format\&n=NlIR2NyubaxdaeSU\&q=85\&s=61d83fc3bb84f2e5d54bc63cc28f9e11) > [!NOTE] > > **Slack Notifications**\ > To receive notifications in Slack, first **enable the Slack integration** from the **Integrations page** in Userpilot and follow the setup steps.\ > Once connected, you will start getting **System Notifications**, **Signals**, and **Digests** directly in Slack. > > ![Screenshot2025 10 07at14 40 48 Pn](https://mintcdn.com/userpilot/NlIR2NyubaxdaeSU/images/Screenshot2025-10-07at14.40.48.png?fit=max\&auto=format\&n=NlIR2NyubaxdaeSU\&q=85\&s=d01af4fa2e98be205e20881b7f011452) # Signal notifications Source: https://docs.userpilot.com/configure/notifications/signals Use Signal notifications in Userpilot to alert your team about key in-app events in real time or on a schedule via in-app, email, or Slack. ## Overview Signals in Userpilot allow you and your teammates to stay informed about key in-app activities through automated notifications.\ You can configure personalized signals to alert your team when important events occur, such as when a flow is completed, a survey is submitted, or an NPS response is received, without needing to constantly monitor dashboards. Signals can trigger when an event occurs, or when a value crosses a threshold you define — for example, when a flow's completion rate falls by a set amount compared to a previous period. Signals can be delivered **in real-time** or on a **scheduled basis (hourly, daily, weekly)** through multiple channels: * **In-app (Inbox)** - visible directly within Userpilot. * **Email** - sent to selected recipients or teams. * **Slack** - posted to designated Slack channels for instant visibility. This flexibility ensures your team never misses critical product or user insights. ## Use cases 1. Get notified when a flow or a checklist is completed to measure adoption. 2. Stay informed the moment a user submits an NPS score and feedback. 3. Track page views or campaign engagement in real-time. 4. Monitor survey submissions or forms to identify customers who need follow-up. 5. Get alerted when a metric crosses a threshold — such as a flow's completion rate falling compared to the previous period, or a segment's average engagement increasing. 6. Create a signal directly from the flow, survey, or profile you're viewing. ## What Userpilot offers in signals Userpilot’s **Signals** feature is built for control, flexibility, and collaboration: 1. **Multi-channel delivery**: send alerts via **in-app**, **email**, or **Slack channel(s)**. 2. **Event-based triggers**: choose from **flows**, **checklists**, **surveys**, **forms**, **NPS**, **pages**, or **events** to trigger notifications. 3. **Custom frequency**: send alerts in **real-time** or as summaries **hourly**, **daily**, or **weekly**. 4. **Team visibility**: share notifications across team members to align actions across functions. 5. **Threshold conditions**: alert only when a value increases or decreases by an amount you define, compared to a previous period. 6. **Property customization**: choose which properties (like plan or MRR) appear directly inside the notification. 7. **Duplicate signals**: clone an existing signal to create a new one based on it. ![Signals Pn](https://mintcdn.com/userpilot/NlIR2NyubaxdaeSU/images/Signals.png?fit=max\&auto=format\&n=NlIR2NyubaxdaeSU\&q=85\&s=747290ceb7152710ec0ccd200b563641) ## How to create a signal 1. Go to **Settings → Notifications → [Signals](https://run.userpilot.io/settings/notifications/signals)** 2. Click **Create Signal** 3. Select the type of event that should trigger your signal.\ You can choose from: 1. **Flows** - when a user starts, completes, or skips a flow 2. **Checklists** - when a checklist is completed 3. **Surveys or NPS** - when a user submits a response 4. **Pages or Events** - when a user performs a specific action 4. For supported types, choose a condition: **event occurred**, or **threshold** — a value that increases or decreases by an amount (percentage or absolute) you set, compared to a previous period. 1. **Optional**: under **Advanced options**, set a minimum volume floor — the change must involve at least this many occurrences for the signal to fire. Off by default; use it to prevent a signal firing on noise from very low-traffic items. 5. Select the **Channel** where you want your signal to be sent: 1. **Slack** - sends an alert to your selected Slack channel 2. **Email** - sends an email notification to specific recipients 3. **In-App (Inbox)** - delivers the signal directly inside Userpilot 6. Choose **the properties** you'd like to be displayed inside the notification. 7. Select the users or team members who should receive the notification. You can choose individuals, teams, or connected Slack channels. 8. Set the **Frequency** by choosing how often you’d like the signal to be sent: 1. **Real-time** (as soon as the event happens) 2. **Hourly**, **Daily**, or **Weekly** summaries 9. Click **Save** to finalize your setup. Your new signal will appear in the list; you can toggle it **on/off** anytime. ![Hjj Pn](https://mintcdn.com/userpilot/HxRmTZxi195Tnb3F/images/hjj.png?fit=max\&auto=format\&n=HxRmTZxi195Tnb3F\&q=85\&s=07889ed301703bd5de45ffcbe2890560) > [!NOTE] > > You can create one signal per event type. For example, you can’t use a single signal to receive notifications from both **Flows** and **Events**. If you’d like to track multiple event types, simply create a separate signal for each one. ## Permissions and access control Workspace account owners can manage who can **create** or **delete** signals through the **Permissions** settings. This ensures that only authorized users can modify or manage signals, keeping your workspace notifications organized and consistent. ![Screenshot2025 10 07at16 02 54 Pn](https://mintcdn.com/userpilot/NlIR2NyubaxdaeSU/images/Screenshot2025-10-07at16.02.54.png?fit=max\&auto=format\&n=NlIR2NyubaxdaeSU\&q=85\&s=aefd39a5e53210925d6040680f0b692a) ## Notification examples 1. Slack notification ![Frame20 Pn](https://mintcdn.com/userpilot/LUOb0SzvOkMZ8XK2/images/Frame20.png?fit=max\&auto=format\&n=LUOb0SzvOkMZ8XK2\&q=85\&s=19c566b94cb9d52caaf475f9a5df6816) 2. Email notification ![Email Notifications6 Pn](https://mintcdn.com/userpilot/LUOb0SzvOkMZ8XK2/images/EmailNotifications6.png?fit=max\&auto=format\&n=LUOb0SzvOkMZ8XK2\&q=85\&s=c2055fa8562f929a246fa9f45fc03a47) 3. In-app (inbox) ![41 Pn](https://mintcdn.com/userpilot/LUOb0SzvOkMZ8XK2/images/41.png?fit=max\&auto=format\&n=LUOb0SzvOkMZ8XK2\&q=85\&s=e2959b067cfa9183119abd97a130e5b4) > [!NOTE] > > If you prefer to receive signal notifications in **real-time,** **hourly, daily, or weekly**, Userpilot will automatically **aggregate your notifications** and deliver them in a single bulk update based on your selected frequency while creating the Signal. # System notifications Source: https://docs.userpilot.com/configure/notifications/system-notifications Use System Notifications in Userpilot to stay informed about data health, content updates, and engagement events via in-app, email, or Slack alerts. ## Overview System Notifications in Userpilot keep you informed about key activities, data issues, and content updates within your workspace.\ They’re designed to help you **stay in control** of your app’s health, from monitoring data flow to tracking content status, so you can act before small issues become big ones. System Notifications are **user-level**, which means only the logged-in user will receive them through their preferred channel: * **In-app (Inbox)** - notifications appear directly inside your Userpilot workspace. * **Email** - updates are sent to your registered email address. * **Slack** - alerts can be sent to your connected Slack account (once integration is enabled). ## Use cases 1. **Monitor data flow health:** Get notified when a page, mobile screen, or event hasn’t received data for over 21 days. 2. **Get alerted on flow issues:** Get notified when a tooltip or driven action doesn’t appear as expected. 3. **Stay updated on engagement:** Receive alerts when an experiment ends or content you created is published/unpublished. 4. **Track feedback responses:** Be informed when a new survey, NPS, or form response is submitted. 5. **Reduce noise:** Choose exactly what you want to be notified about, all content, only your content, or custom selections. ### How to set up system notifications 1. Go to **Settings → Notifications → System Notifications.** 2. Toggle on the sections you want to receive alerts for (**Data**, **Engagement**, **Feedback**). 3. Choose the content scope: * **All content** * **Only content I created** * **Custom** 4. Select how you want to receive alerts: **In-app**, **Email**, or **Slack**. 5. For Slack, ensure you’ve completed the **Slack integration** under **Settings → Integrations** (this is a one-time setup). ![Screenshot2025 10 07at15 06 30 Pn](https://mintcdn.com/userpilot/NlIR2NyubaxdaeSU/images/Screenshot2025-10-07at15.06.30.png?fit=max\&auto=format\&n=NlIR2NyubaxdaeSU\&q=85\&s=753653f67d0ab8e4368c4207a76af809) ## Notification types Within the **System Notifications** tab, notifications are grouped into three main categories: #### **1. Data** Stay informed when data stops flowing to your workspace. * Page hasn’t received data for 21 days * The mobile screen hasn’t received data for 21 days * The event hasn’t received data for 21 days\ You can set these alerts for *all content*, *only the ones you created*, or *custom selections*. #### **2. Engagement** Get notified when content is published, unpublished, or encounters an error. * When your content (flows, checklists, Resource center, mobile content, banners, spotlights ) is published/unpublished. * When an experiment ends. * When content encounters errors or disruptions, Userpilot will notify you when content breaks, for example, if a specific step stops displaying in a flow. #### **3. Feedback** Keep track of all incoming user feedback. * When a survey or NPS response is received. * When a form submission is made. * When feedback-related content you created is published or unpublished. ![Screenshot2025 10 07at14 28 07 Pn](https://mintcdn.com/userpilot/NlIR2NyubaxdaeSU/images/Screenshot2025-10-07at14.28.07.png?fit=max\&auto=format\&n=NlIR2NyubaxdaeSU\&q=85\&s=61d83fc3bb84f2e5d54bc63cc28f9e11) > [!NOTE] > > You can stop receiving all notifications by turning off a category.\ > Or, if you prefer to receive specific types of notifications on certain channels, customize your preferences by selecting the checkboxes for the channels you want: **In-app**, **Email**, or **Slack**. ## Notification schedule | **Channel** | **Time** | | :- | :- | | Slack | Real-time notifications for all notification types | | In-app | Real-time notifications for all notification types | | Email | 1. Aggregate responses from surveys, NPS, and form submissions, and send them in a batch every 3 hours 2. Aggregate all errors and send a summary at the end of each day | ## System notification permission If a team member doesn’t have permission to receive Survey or NPS notifications, Userpilot will prevent them from enabling these notification types and display a message prompting them to contact their account owner to request access. ![Screenshot 2025-10-07 at 15.39.46.png](https://mintcdn.com/userpilot/NlIR2NyubaxdaeSU/images/Screenshot2025-10-07at15.39.46.png?fit=max\&auto=format\&n=NlIR2NyubaxdaeSU\&q=85\&s=4c694a72f2c4e04d229934820d12276e) ## Notification examples 1. Slack notification ![67 Pn](https://mintcdn.com/userpilot/f1dhkEAPQC1nzBGa/Screenshot2025-11-03at16.39.13.png?fit=max\&auto=format\&n=f1dhkEAPQC1nzBGa\&q=85\&s=6cf26c3ad67d009d77c0cc5e2eb9e396) 2. Email notification ![Email Notifications6 Pn](https://mintcdn.com/userpilot/LUOb0SzvOkMZ8XK2/images/EmailNotifications6.png?fit=max\&auto=format\&n=LUOb0SzvOkMZ8XK2\&q=85\&s=c2055fa8562f929a246fa9f45fc03a47) 3. In-app (inbox) ![41 Pn](https://mintcdn.com/userpilot/LUOb0SzvOkMZ8XK2/images/41.png?fit=max\&auto=format\&n=LUOb0SzvOkMZ8XK2\&q=85\&s=e2959b067cfa9183119abd97a130e5b4) ## FAQs > **Can i turn off system notifications for my teammates?** > > No, each teammate would be able to manage their own notification preferences from the account settings and it wouldn't be possible to turn off notifications for other teammates. # Two-Factor Authentication (2FA) Source: https://docs.userpilot.com/configure/team-and-roles/2fa Enable Two-Factor Authentication (2FA) in Userpilot to add an extra layer of security and protect your workspace from unauthorized access. ## **Overview** Two-Factor Authentication (2FA) adds an extra layer of security to your Userpilot account by requiring users to verify their identity using a passcode generated from an authentication app (such as Google Authenticator, 1Password, or Authy).\ This ensures that even if a password is compromised, your workspace remains protected. ## Use cases 1. You want to enhance workspace security across all teammates 2. You are working with sensitive product data or customer information 3. You are required to meet internal compliance or security guidelines 4. You use SSO and want an additional verification layer 5. You want to ensure only verified users access your Userpilot setup ## Enable 2FA 1. You can enable 2FA settings from [******Settings → Team → Authentication → Mandatory Two-Factor Authentication******](https://run.userpilot.io/settings/team/authentication) 2. Toggle **“Require all teammates to authenticate via 2FA”** on > [!NOTE] > > By default, 2FA is **not required**, and teammates can log in normally. Admins can choose to enforce it for the entire team ![Screenshot2025 12 14at10 45 54 Pn](https://mintcdn.com/userpilot/okx6EgHCnXtrBzqv/images/Screenshot2025-12-14at10.45.54.png?fit=max\&auto=format\&n=okx6EgHCnXtrBzqv\&q=85\&s=8767a576fc5ff8953884c31a1e9a7a5c) 3. Once enabled: * All existing teammates will receive an **invitation to enroll in 2FA** * New teammates will be required to set up 2FA immediately after creating their password ![Screenshot2025 12 14at10 51 04 Pn](https://mintcdn.com/userpilot/okx6EgHCnXtrBzqv/images/Screenshot2025-12-14at10.51.04.png?fit=max\&auto=format\&n=okx6EgHCnXtrBzqv\&q=85\&s=d7118684ecd325224e5b12b09dc42c12) ![Screenshot2025 12 14at10 53 31 Pn](https://mintcdn.com/userpilot/okx6EgHCnXtrBzqv/images/Screenshot2025-12-14at10.53.31.png?fit=max\&auto=format\&n=okx6EgHCnXtrBzqv\&q=85\&s=6e8946dfe072b67638158962ee5fdb22) * Users can't access the workspace until they complete their 2FA setup * A 2FA icon appears next to their name in the Teammates table ![Screenshot2025 12 14at11 02 46 Pn](https://mintcdn.com/userpilot/okx6EgHCnXtrBzqv/images/Screenshot2025-12-14at11.02.46.png?fit=max\&auto=format\&n=okx6EgHCnXtrBzqv\&q=85\&s=d4f691528188fac5401b085be555dbf0) > [!NOTE] > > Admins can disable 2FA for a teammate if the user is locked out of their account. For example, after losing access to their authentication device. > > ![Screenshot2025 12 14at11 04 10 Pn](https://mintcdn.com/userpilot/okx6EgHCnXtrBzqv/images/Screenshot2025-12-14at11.04.10.png?fit=max\&auto=format\&n=okx6EgHCnXtrBzqv\&q=85\&s=98d5bdd412e304785b19d1f6616ef82e) > [!NOTE] > > **Note:**\ > Team members can enable Two-Factor Authentication from **Settings → User** by scanning a QR code and activating 2FA on their account. > > However, if an admin has enforced **mandatory 2FA** for the entire workspace, users will **not** be able to disable it themselves. Only admins have permission to disable 2FA for a teammate. > > ![Screenshot2025sss12 14at11 22 08 Pn](https://mintcdn.com/userpilot/okx6EgHCnXtrBzqv/images/Screenshot2025sss12-14at11.22.08.png?fit=max\&auto=format\&n=okx6EgHCnXtrBzqv\&q=85\&s=d491db7890cc41180bf94b8461548993) # Access and activity logs Source: https://docs.userpilot.com/configure/team-and-roles/access-and-activity-logs Use Access and Activity Logs in Userpilot to monitor workspace access, track admin actions, and maintain security with detailed, auditable records. > [!NOTE] > > Activity logs are available with Enterprise plan. Kindly reach out to [support@userpilot.com](mailto:support@userpilot.com) if you need any assistance. ## Overview Managing a workspace often requires more than assigning roles and permissions; you also need visibility into who is accessing your account and what actions are taking place inside it.\ Userpilot provides two dedicated logs to help you monitor workspace activity and maintain security: **Access Logs** and **Activity Logs**. Both logs are available under **Settings → Team**, each serving a different purpose depending on the level of insight you need. ## Access Log The **Access Log** focuses on workspace entry and permission changes. It answers the question: * Who accessed my workspace * What admin-level changes did they make? **You can see these details in the access log page:** * **Login activity** - Shows which teammate logged into your Userpilot workspace. * **Role changes** - Tracks who changed another teammate’s role and when. * **Provisioning activity** - Records teammates being added, removed, or having their role changed, whether through [SCIM directory sync](/configure/team-and-roles/scim), [JIT provisioning](/configure/team-and-roles/jit), [CSV import](/configure/team-and-roles/csv-import), or a manual invite. Each entry shows the role before and after, who or what triggered it, the application, and the reason. * **IP addresses** - Helps you verify where the login originated from. * **Timestamps** - Displays the exact date and time of each action. **When this is useful** * Monitoring unauthorized or unusual logins * Keeping track of administrative changes * Reviewing account access for audits or security checks * Troubleshooting permission-related issues The Access Log is especially helpful for admins and account owners who need transparency into how workspace access is being managed. ### Filtering the Access Log To find a specific change quickly, filter the Access Log by: * **Source** - where the change came from: **Web app**, **SCIM provisioning**, **SSO sign-in**, or **CSV import**. (SCIM scheduled syncs and directory webhooks share the single **SCIM provisioning** source.) * **Activity** - the type of change: **Member provisioned**, **Member deprovisioned**, **Changed a member's role**, or **Role sync skipped (set manually)** - plus sign-in, 2FA, and SSO events (Signed in, Invited a member, Removed a member, Enabled/Disabled SSO, and so on). You can also search by teammate name or email. This makes it easy to audit, for example, every teammate your identity provider added last week, or why a particular role change was skipped during a sync. ![Screencapture Run Userpilot Io Settings Team Access Log 2025 12 11 22 11 26 Pn](https://mintcdn.com/userpilot/7ZrZAZ39L5rLesXD/images/screencapture-run-userpilot-io-settings-team-access-log-2025-12-11-22_11_26.png?fit=max\&auto=format\&n=7ZrZAZ39L5rLesXD\&q=85\&s=9af14d1667b575b7e7bddb9e93bda0f2) ## **Activity Log** The **Activity Log** tracks in-product activity, specifically, actions performed inside the Userpilot workspace. It answers the question: * What changes were made to the content * Who made them? ### **What you can see in the Activity Log** * **Publish actions** - Shows when a flow, checklist, or other content has been published. * **Unpublish actions** - Shows when content has been taken down or disabled. * **Content titles and types** - Easily identify which flow, checklist, survey, or element was updated. * **IP addresses and timestamps** - For added traceability. ### **When this is useful** * Tracking content changes across your workspace * Understanding who published or unpublished flows * Reviewing collaboration activity within your team * Maintaining compliance or internal documentation * Troubleshooting unexpected content updates The Activity Log is particularly valuable for teams working on multiple flows or onboarding experiences at the same time, helping ensure nothing goes live or offline without visibility. ![Screencapture Run Userpilot Io Settings Team Activity Log 2025 12 11 22 15 56 Pn](https://mintcdn.com/userpilot/7ZrZAZ39L5rLesXD/images/screencapture-run-userpilot-io-settings-team-activity-log-2025-12-11-22_15_56.png?fit=max\&auto=format\&n=7ZrZAZ39L5rLesXD\&q=85\&s=ec89010beb3e84a9418daed8932b5997) # Bulk Teammate Import (CSV) Source: https://docs.userpilot.com/configure/team-and-roles/csv-import Invite many teammates to Userpilot at once by uploading a CSV with a role per application. ### Overview The CSV import lets you invite many teammates to your Userpilot applications from a single file, instead of inviting people one at a time. You upload a CSV, review a validation preview, and confirm. Userpilot then invites each teammate with the role you specified. This is one of three ways to add teammates, alongside manual invites and automated provisioning through [SCIM](/configure/team-and-roles/scim) and [JIT](/configure/team-and-roles/jit). Unlike SCIM and JIT, importing is an invite action and is available on every plan. You can run the import from two places: * **Settings > Team** imports teammates into the application you are currently viewing. * **Admin Console > Team** imports across your whole organization, with each row naming its target application. ### Requirements * Permission to invite teammates, the same permission used for single invites. * To assign a given role, you must hold a role with at least that level of access in the target application. > [!NOTE] > > CSV import is disabled whenever SCIM directory sync is active - in every role-management mode, not only when the IdP owns roles (see [SCIM role management](/configure/team-and-roles/scim#choosing-who-owns-roles)) - and while a sync is in progress. Once SCIM is on, teammate membership comes from your IdP. ## Importing teammates **1. Open the importer** On **Settings > Team** (or the Admin Console's **Team** page), click **Import** to open the importer drawer. ![Import teammates from CSV drawer](https://mintcdn.com/userpilot/HBAH9vHQbZ2nERNC/images/csv-import.png?fit=max\&auto=format\&n=HBAH9vHQbZ2nERNC\&q=85\&s=bd6931327124bba5bc341071f03750bf) **2. Prepare your CSV** In the **Start with the template** card, click **Download .CSV** to get a correctly formatted file (`userpilot-teammates-template.csv`). The template matches the importer you opened it from, so you can fill it in and upload it as is. In the Admin Console importer, the drawer also lists **Your applications**, with each application's name and token for reference. **3. Upload the file** Drag and drop your `.csv` onto the dropzone, or click to browse. Files must be `.csv` and up to 2 MB. **4. Review the preview** Userpilot validates every row and groups them into up to three tables, each showing the teammate, role, and application: * **Rows with errors.** Invalid rows (bad email, missing or unassignable role, and so on), each with the reason. Fix the file and re-upload - these are not imported. * **Rows won't be imported.** Rows skipped because the person already exists as a teammate, or the row is a duplicate within the file. * **Rows ready to import.** Valid rows that will be invited when you confirm. **5. Confirm** Click **Invite Teammates** to invite everyone in the **Rows ready to import** list. Importing runs in the background, and rows that fail do not stop the others. ## CSV format ### Admin Console importer The org-wide import needs these columns, with exactly these headers: | Column | Required | Notes | | - | - | - | | `email` | Yes | The teammate's email address. | | `first_name` | Yes | May be left blank. | | `last_name` | Yes | May be left blank. | | `application` | Yes | The application token, shown in the importer under "Your applications". Application names are also accepted, but tokens are safer because they are unique. | | `role` | Yes | A role that exists in that application. | Roles are set per application. To add someone to multiple applications, add one row per application, with the same email but a different application token and role on each row. ```csv theme={null} email,first_name,last_name,application,role jane.doe@example.com,Jane,Doe,NX-abc123,Admin john.smith@example.com,John,Smith,NX-abc123,Publisher john.smith@example.com,John,Smith,NX-def456,Analyst ``` Rows can also target the Admin Console itself. For those rows, use one of the Admin Console roles: `Admin`, `Billing`, `View Only`, or `Account Owner`. ### Team page importer The importer on **Settings > Team** always targets the application you are viewing, so the `application` column is dropped: ```csv theme={null} email,first_name,last_name,role jane.doe@example.com,Jane,Doe,Admin john.smith@example.com,John,Smith,Publisher ``` > [!NOTE] > > Account Owner can be assigned via CSV, but only if you are an Account Owner yourself. In general, you can only assign roles whose access is within your own role's permissions. The same applies to the [JIT](/configure/team-and-roles/jit) and [SCIM](/configure/team-and-roles/scim) default roles, which can also be set to Account Owner. ## Validation and limits Each row is checked before anything is invited: * **Email** must be a valid address. If it already belongs to a different Userpilot organization, that row is rejected with a clear message and no invite is sent for it. * **Application** must exist in your organization (Admin Console importer). * **Role** must exist in that application, and you must be allowed to assign it. * **Seat limits** are enforced per application. A row that would exceed your plan's seats is blocked, while the rest of the import continues. * **File size** is capped at 2 MB and 50,000 rows. Re-importing a teammate who already exists is treated as already invited and is skipped, so running the same file twice will not create duplicates. ## After import Each successfully imported teammate: * Receives an invitation to their application or applications, with the role from the CSV. * Is created without a password if your organization enforces [SSO](/configure/team-and-roles/sso). In that case they sign in through your identity provider. * Is recorded in the Access Logs with the source CSV, so you can audit exactly who was added and by whom. See [Access and activity logs](/configure/team-and-roles/access-and-activity-logs). > [!NOTE] > > CSV import is available only while SCIM is off (or in orgs without SCIM). A CSV teammate's role is flagged as manually set, so if you later enable SCIM, a hybrid-mode sync keeps that role. But CSV teammates are not in your IdP directory, so mapping or re-mapping your groups removes them like any other non-IdP member. Role protection covers the role, not membership. ## FAQs > **Why is the Import action disabled?** > > It is hidden or disabled whenever SCIM directory sync is active (in any role-management mode - manage teammates in your IdP), while a SCIM sync is running, or if you do not have permission to invite teammates. > **A row failed. Does the whole import fail?** > > No. Invalid rows are listed under "Needs attention" and skipped, and valid rows still import. Rows that fail during processing, for example by hitting a seat limit, do not stop the rest. > **How do I add one person to several applications?** > > Use the Admin Console importer and add one row per application, with the same email but a different application token and role on each row. # Just-in-Time (JIT) Provisioning Source: https://docs.userpilot.com/configure/team-and-roles/jit Automatically create Userpilot teammates the first time they sign in with SSO, using your verified identity-provider domains. > [!NOTE] > > This feature is available on the Enterprise plan and requires [SSO](/configure/team-and-roles/sso) to be enabled. ### Overview Just-in-Time (JIT) provisioning creates a Userpilot account for a teammate the first time they sign in through SSO. No manual invite or prior sync is required. Without JIT, a person who signs in with SSO but has no account is turned away. With JIT, their account is created during sign-in and given a default role. JIT is the simplest way to onboard a team. It works alongside the other provisioning methods: * **JIT** handles individual first-time sign-ins. * **[SCIM](/configure/team-and-roles/scim)** keeps whole groups of users and their roles in sync with your identity provider. * **[CSV import](/configure/team-and-roles/csv-import)** invites a known list of teammates in bulk. JIT and SCIM work well together. JIT gives a teammate immediate access on first login, and a later SCIM sync can move them into a mapped group and update their role. ### Requirements * [SSO](/configure/team-and-roles/sso) enabled for the organization. * An Account Owner role, or a role with the Manage SSO permission. ## Enabling JIT provisioning JIT is configured on the **Just-in-time provisioning** card under **Settings > Team > [Authentication](https://run.userpilot.io/settings/team/authentication)**. The card appears once SSO is enabled. **1. Turn JIT on** Switch on **Create teammates automatically on first SSO login**. **2. Choose a default role** Set **Default role on first SSO login**. This role is granted when a teammate signs in via SSO for the first time, until SCIM assigns them a mapped role. The available roles are Admin, Publisher, Analyst, and Account Owner. Because this role applies to everyone JIT creates, pick Account Owner only if every teammate who signs in through your verified SSO domains should have full ownership. > [!NOTE] > > JIT stays off until you choose a default role. **3. Choose a default application** Set **Default application**. This is where auto-created teammates land on their first SSO login, and on logins started from your IdP tile. ![Just-in-time provisioning settings](https://mintcdn.com/userpilot/o9MX9rURnW9bs0V7/images/jit-provisioning.png?fit=max\&auto=format\&n=o9MX9rURnW9bs0V7\&q=85\&s=5aad2a3d20f4b560718e675c24626a53) ## How it works The first time a teammate signs in through SSO without an existing account, Userpilot does the following: 1. Confirms the sign-in came through your organization's verified SSO connection. 2. Checks that the teammate's email domain is one of your verified SSO domains. 3. Creates the account, assigns the default role, and places the teammate in the default application. > [!NOTE] > > If SCIM later places a JIT-created teammate into a mapped group, that group's role replaces the JIT default role on the next sync. The JIT default is a starting point, not a permanent assignment. The one exception is the Userpilot [role-management mode](/configure/team-and-roles/scim#choosing-who-owns-roles), where SCIM never changes roles. ## IdP-initiated SSO JIT provisioning supports IdP-initiated SSO, so teammates can reach Userpilot straight from your identity provider without being invited beforehand. There are two ways to start an SSO sign-in, and JIT works with both: * **Service-provider-initiated.** The teammate starts from Userpilot, enters their work email, and is redirected to your identity provider to authenticate before being sent back to Userpilot: `Userpilot → Identity provider → Userpilot` * **IdP-initiated.** The teammate opens the Userpilot tile from their identity provider's app dashboard and is sent straight to Userpilot, where JIT provisioning creates their account and completes the sign-in: `Identity provider → Userpilot` ![Identity provider app dashboard showing application tiles](https://mintcdn.com/userpilot/idWCuF5jtX2Yhtj7/images/idp-initiated-app-tile.png?fit=max\&auto=format\&n=idWCuF5jtX2Yhtj7\&q=85\&s=39476db46466fe34ec37eb3639583875) With JIT enabled, an eligible teammate has their Userpilot account created during their first SSO login from either flow, as long as their email domain is one of your verified SSO domains. A manual invitation or prior SCIM sync is not required. New teammates land in the [default application](#enabling-jit-provisioning) you set for JIT. ## JIT and SSO are linked JIT only runs during an SSO sign-in, so its availability follows SSO: * Disabling SSO automatically turns JIT off. * Deleting the SSO configuration turns JIT off and clears the JIT default role. If you re-enable SSO later, re-enable JIT and select its settings again. ## FAQs > **Why isn't a teammate being auto-created?** > > Check that JIT is on, that a default role and default application are set, and that the teammate's email domain is one of your verified SSO domains. > **Can JIT assign someone as Account Owner?** > > Yes. Account Owner is selectable as the JIT default role, the same as in [CSV import](/configure/team-and-roles/csv-import). Because the default role applies to every teammate JIT creates, choose it only when that is what you intend. > **Do I still need SCIM if I use JIT?** > > Not necessarily. JIT alone is enough to onboard teammates on first login. Add SCIM when you also want roles and de-provisioning to stay in sync with your IdP groups. > **Does JIT work with IdP-initiated SSO?** > > Yes. When a teammate opens the Userpilot tile in your identity provider (an IdP-initiated login), JIT creates their account and signs them in, the same as a sign-in started from Userpilot. No prior invite or SCIM sync is needed. # Managing your team in Userpilot Source: https://docs.userpilot.com/configure/team-and-roles/managing-your-team-in-userpilot Manage teammates in Userpilot by inviting users, assigning roles and permissions, tracking activity, and securing access with authentication controls. ### Overview You can manage who has access to your Userpilot workspace by inviting your colleagues and assigning them roles with the right permissions. This gives you flexibility in defining each person’s responsibilities. Userpilot provides predefined roles such as Admin, Account Owner, Publisher, and Analyst, and you can also create custom roles to tailor permissions exactly to your team’s needs. You can also review [access and activity logs](/configure/team-and-roles/access-and-activity-logs) to monitor who has logged in and what actions they have taken, ensuring everything remains secure and well-tracked. From the **Authentication** tab, you can also enable [2FA](/configure/team-and-roles/2fa), [SSO login](/configure/team-and-roles/sso), [Just-in-Time provisioning](/configure/team-and-roles/jit), and [SCIM directory sync](/configure/team-and-roles/scim) for enhanced security and automated user management. > [!NOTE] > > Custom roles, access logs, activity logs, and advanced authentication options are only available on the Enterprise plan. If you’re on a lower plan, you’ll need to [upgrade](https://userpilot.com/pricing/) to access these features. ### Teammates The **Team** tab in Userpilot allows you to control who has access to your workspace, assign the right permissions, and keep track of activity. Whether you're collaborating with product managers, designers, analysts, or engineers, this section helps you manage roles and maintain workspace security with ease. * **Filters and role tags** - At the top, you will find role-based filters that help you quickly sort teammates by their responsibilities or permission levels. * **Search** - Use the search bar to find teammates instantly by typing their name or email. * **User List** - This section shows all workspace members, and you can easily update their roles or manage their permissions directly from here. ![Team tab showing the list of teammates with roles and filters](https://mintcdn.com/userpilot/1uV1KqEEgwbcI6uC/configure/team-and-roles/assets/images/2025-12-14-team-tab-teammates-list.png?fit=max\&auto=format\&n=1uV1KqEEgwbcI6uC\&q=85\&s=13ff5f3baac9249ebb81556084b5707b) > [!NOTE] > > If a teammate hasn’t accepted their invitation yet, Userpilot will show a red clock icon beside their name. You can resend the invitation to keep your workspace setup on track. > > ![Resend Invite option for a teammate with a pending invitation](https://mintcdn.com/userpilot/1uV1KqEEgwbcI6uC/configure/team-and-roles/assets/images/2025-12-14-resend-invite-pending-teammate.png?fit=max\&auto=format\&n=1uV1KqEEgwbcI6uC\&q=85\&s=75226de729874c14944d359a661827ef) ### Adding teammates There are three ways to add people to your workspace: * **Invite individually** - Click **Invite Teammates**, enter the email, and choose a role and application. * **[Bulk import via CSV](/configure/team-and-roles/csv-import)** - Upload a CSV to invite many teammates at once, with a role per application. * **Automated provisioning** - On the Enterprise plan, let your identity provider manage teammates with [SCIM directory sync](/configure/team-and-roles/scim), or create them automatically on first SSO login with [JIT provisioning](/configure/team-and-roles/jit). > [!NOTE] > > Whenever SCIM directory sync is active - in every role-management mode, not only when the IdP owns roles - inviting and importing teammates directly in Userpilot is disabled, and membership comes from your IdP. It is also paused while a directory sync is in progress. ### Roles By default, Userpilot offers four predefined roles: | **Role** | **Description** | **Access level** | | :- | :- | :- | | **Account Owner** | Automatically assigned to the first user who creates the workspace | Full access to everything, including billing and authentication settings | | **Admin** | Day-to-day administration without account-level controls | Everything except billing, authentication settings (MFA, SSO, and SCIM), and assigning the Account Owner role | | **Publisher** | Focused on content creation and publishing | Creates and publishes content, and manages users, companies, segments, events, and themes | | **Analyst** | Works with analytics and reporting | Access to reports, session replay and playlists, and feature, page, and mobile screen tagging | > [!NOTE] > > If SCIM is connected, roles are assigned automatically based on your identity provider’s group mappings and can’t be changed from the Userpilot UI. To update a teammate’s role, make the change in your IdP instead. #### Custom Roles You can also create **Custom Roles** (Enterprise plan only) if you need more control.\ Just click **+ Add Role**, then choose exactly which permissions to enable or restrict. ![Create Role panel showing permission options](https://mintcdn.com/userpilot/1uV1KqEEgwbcI6uC/configure/team-and-roles/assets/images/2025-12-14-create-role-permissions-panel.png?fit=max\&auto=format\&n=1uV1KqEEgwbcI6uC\&q=85\&s=36afc1ebfaeedd554e72b6f6f4cf0d32) If you want to view or edit the permissions for a specific role, simply click on the role and then select View. ![View button for an existing role's permissions](https://mintcdn.com/userpilot/1uV1KqEEgwbcI6uC/configure/team-and-roles/assets/images/2025-12-14-role-permissions-view-button.png?fit=max\&auto=format\&n=1uV1KqEEgwbcI6uC\&q=85\&s=d1075d0fe78fd5911fac8af06d1675d3) > [!NOTE] > > Userpilot’s premade roles (Account Owner, Admin, Publisher, and Analyst) can’t be edited. If you need different permissions, create a Custom Role instead, since custom roles can be edited at any time. ### How roles work with automated provisioning When teammates are created automatically, their role comes from one of three settings, depending on how they arrive: * **JIT default role.** A teammate created on their first SSO login gets the [JIT default role](/configure/team-and-roles/jit) you chose on the Authentication page. This is a starting point: a later SCIM sync can replace it with a mapped role. * **SCIM group mapping.** With [SCIM](/configure/team-and-roles/scim) enabled, teammates get the role mapped to their IdP group. A teammate in several mapped groups gets the highest-privilege role among them. * **SCIM default role.** Teammates who belong to no mapped group follow the **Individuals** row in your group mapping. Pick a role there to provision them, or choose Skip to leave them out of Userpilot. Roles assigned by a group mapping take precedence over both defaults. Whether a role you change by hand survives the next sync depends on your [role management mode](/configure/team-and-roles/scim#choosing-who-owns-roles): in the recommended hybrid setup, manual changes are kept; with strict IdP enforcement, every sync resets roles to match your IdP. # SCIM Integration Source: https://docs.userpilot.com/configure/team-and-roles/scim Automate user provisioning and deprovisioning in Userpilot using SCIM with identity providers like Okta and Azure AD. > [!NOTE] > > This feature is available on the Enterprise plan and requires [SSO](/configure/team-and-roles/sso) to be enabled. ## Overview As organizations grow, managing user accounts across applications becomes complex. Adding and removing teammates, changing roles, and revoking access all take time and are easy to get wrong. SCIM (System for Cross-domain Identity Management) solves this by making your identity provider (IdP) the single source of truth. You map your IdP groups to Userpilot roles once, and Userpilot keeps teammates and their access in sync automatically. It provisions new users, updates roles, and removes people who leave. ### Use cases 1. Automatically create, update, and remove Userpilot teammates as your IdP changes. 2. Keep roles consistent by mapping IdP groups (from Okta, Microsoft Entra ID, and others) to Userpilot roles. 3. Revoke access promptly when employees leave or change teams. 4. Eliminate manual teammate management across multiple applications. ### Prerequisites 1. An Account Owner role, or a role with the Manage SCIM permission. 2. [SSO](/configure/team-and-roles/sso) configured and enabled. 3. An active subscription with a SCIM-capable IdP. 4. A Userpilot organization on the Enterprise plan. *** ## Roles in Userpilot Userpilot ships four built-in system roles that you can map IdP groups to: | Role | Access | | - | - | | **Account Owner** | Full access, including billing, authentication, and SCIM configuration. | | **Admin** | Everything except billing, authentication settings (MFA, SSO, and SCIM), and assigning the Account Owner role. | | **Publisher** | Creates and publishes content, and manages users, companies, segments, events, and themes. | | **Analyst** | Reports, session replay and playlists, and feature, page, and mobile screen tagging. | You can also map groups to any custom roles you have created in an application. The Admin Console has its own role set (Admin, Billing, View Only, and Account Owner). For more on roles and permissions, see [Managing your team in Userpilot](/configure/team-and-roles/managing-your-team-in-userpilot). *** ## Enable SCIM for the organization **1. Step 1: Set up SSO** SCIM builds on SSO. If you have not already, go to **Settings > Team > [Authentication](https://run.userpilot.io/settings/team/authentication)**, click **Enable** on the SSO card, and connect your identity provider. ![Set up SSO](https://mintcdn.com/userpilot/HBAH9vHQbZ2nERNC/images/authentication-overview.png?fit=max\&auto=format\&n=HBAH9vHQbZ2nERNC\&q=85\&s=674791a115c1b3df84e85567ccf4d309) ![Select your identity provider](https://mintcdn.com/userpilot/o9MX9rURnW9bs0V7/images/file-hxTc5PWM1W.png?fit=max\&auto=format\&n=o9MX9rURnW9bs0V7\&q=85\&s=4d1df8ec7041af684dc277d04f2da50e) ![SSO enabled](https://mintcdn.com/userpilot/HBAH9vHQbZ2nERNC/images/file-SdAJWXVJVt.png?fit=max\&auto=format\&n=HBAH9vHQbZ2nERNC\&q=85\&s=ba943f703c7a5a8d8b252e37953aa9a7) **2. Step 2: Set up SCIM** In the **Directory sync (SCIM)** card, click **Setup SCIM** and follow your IdP's instructions for adding SCIM provisioning. **3. Step 3: Map IdP groups to roles** Click **Map Roles** to open the mapping wizard: 1. **Add Groups.** Select the IdP groups you want to give access to the organization. ![Select IdP groups](https://mintcdn.com/userpilot/HBAH9vHQbZ2nERNC/images/file-6JvN3LuknX.png?fit=max\&auto=format\&n=HBAH9vHQbZ2nERNC\&q=85\&s=f6698c3a8cddd3ef9e2993a8d586d98e) 2. **Map groups with application roles.** Align each IdP group with a role in each application. When you have multiple applications, you can map the same group to different roles in each one. ![Map groups to application roles](https://mintcdn.com/userpilot/HBAH9vHQbZ2nERNC/images/file-M08ERooPlI.png?fit=max\&auto=format\&n=HBAH9vHQbZ2nERNC\&q=85\&s=afa2f7d04dfd0aaf52a19f4d92ae5f79) 3. **Review changes.** Userpilot shows a preview of exactly what will happen. Nothing is applied until you confirm, and going back changes nothing. **4. Step 4: Sync your directory** Confirm the preview to apply the mapping. Provisioning runs in the background and is durable: it survives restarts and retries automatically. When it finishes, your teammates appear under the **[Team](https://run.userpilot.io/settings/team)** page. ![Teammates list](https://mintcdn.com/userpilot/HBAH9vHQbZ2nERNC/images/file-AcRwhEXwqQ.png?fit=max\&auto=format\&n=HBAH9vHQbZ2nERNC\&q=85\&s=73a983708f2c87056ce69946fa6ca112) > [!NOTE] > > After SCIM is successfully configured, SSO Mandatory Login is enforced for everyone in the organization. While SCIM is enabled, SSO cannot be disabled. Disable SCIM first if you need to. ### Creating a custom role While mapping groups, you can create a new role. Click **Create Role**, enable the permissions you want, and click **Create**. The new role becomes available for mapping. ![Create a custom role](https://mintcdn.com/userpilot/o9MX9rURnW9bs0V7/images/file-TkiG5HSy21.png?fit=max\&auto=format\&n=o9MX9rURnW9bs0V7\&q=85\&s=fc97dd8a0f2f116ec6712244a5c6085e) ### Default role for unmapped groups Each application's mapping table ends with an **Individuals** row. This row covers IdP users who belong to no mapped group (and users whose groups are all unmapped). Pick a role to provision them automatically, or choose **Skip - don't add to Userpilot** to leave them out. > [!WARNING] > > With Skip, teammates who later leave all of their mapped groups are removed from Userpilot. Choose a role in the **Individuals** row instead if you want to keep them. You can select any application role here, including Account Owner. Since it applies to every groupless and unmapped user, pick Account Owner only when that is intended. ![Group mapping with the Individuals default-role row](https://mintcdn.com/userpilot/o9MX9rURnW9bs0V7/images/scim-group-mapping.png?fit=max\&auto=format\&n=o9MX9rURnW9bs0V7\&q=85\&s=26ea857a4c8ab63c9cd87259673d6223) ### What if a teammate is in multiple groups? If a teammate belongs to several mapped groups, Userpilot resolves their role automatically by granting the highest-privilege role among those groups. You do not need to restrict people to a single group in your IdP. *** ## Choosing who owns roles The **Role management** cards let you decide whether your IdP or Userpilot controls teammate roles: * **Identity provider.** Your directory controls who has access and their roles. Map each group to a role, and unmapped users follow the "All other groups" default. * Leave **Strictly enforce IdP roles** off (recommended) for hybrid behavior. Roles come from your IdP groups, but any role you change in Userpilot is kept across syncs. * Turn **Strictly enforce IdP roles** on for fully provider-owned behavior. Every sync replaces all roles to match your IdP, overwriting changes made in Userpilot. * **Userpilot.** SCIM provisions and removes users but never changes roles. You manage every teammate's role in Userpilot. ![Role management mode cards: Identity provider and Userpilot, with the Strictly enforce IdP roles option](https://mintcdn.com/userpilot/o9MX9rURnW9bs0V7/images/scim-role-management.png?fit=max\&auto=format\&n=o9MX9rURnW9bs0V7\&q=85\&s=35e3bd29721f3dd1ce4ac8db97e9056f) > [!NOTE] > > Whenever SCIM is active, inviting or importing teammates directly in Userpilot is disabled in every role-management mode, not only when the identity provider owns roles. Teammate membership comes from your IdP. Role management is separate: manual role edits are still allowed in hybrid and Userpilot modes, as described above. *** ## Syncing users Use **Sync Users** on the Authentication page to re-sync all mapped groups on demand. Before anything is applied, a preview shows exactly what will change, grouped by application: * **Will be added.** New teammates and their roles. * **Role changes.** The current role and the new role. * **Not applied (kept as-is).** Memberships that are skipped, with the reason. * **Not found in your IdP.** Listed for information only. A manual sync never removes anyone. Removals happen only when you re-map. Confirm the preview to apply the changes. ![SCIM change preview showing additions, role changes, and removals before they are applied](https://mintcdn.com/userpilot/o9MX9rURnW9bs0V7/images/scim-preview-changes.png?fit=max\&auto=format\&n=o9MX9rURnW9bs0V7\&q=85\&s=0008f7d95c64a8e268d2ef73cb41d2d9) *** ## Real-time directory events Once SCIM is set up, Userpilot stays in sync with your IdP in real time by listening to directory events: | Event | What Userpilot does | | - | - | | **User added to a group** | Provisions the teammate, or updates their role, for the mapped application. | | **User removed from a group** | Removes the teammate from that application if they are no longer in any mapped group. | | **User updated** | Updates the teammate's profile. | | **User deleted** | Removes the teammate from the organization. | | **Group created** | Surfaces the new group so you can map it. A banner on the Team page notifies admins of IdP changes. | | **Group deleted** | Removes the group and its mapping. | ![Group-change banner](https://mintcdn.com/userpilot/o9MX9rURnW9bs0V7/images/file-uaT63BGGjn.png?fit=max\&auto=format\&n=o9MX9rURnW9bs0V7\&q=85\&s=72764c7e8e70f84bf70ef3938f31ed44) These events are processed durably and idempotently. Each event is queued and retried until it succeeds, so nothing is lost on a restart, and Userpilot reconciles against the current directory state. This means that out-of-order or duplicate deliveries always converge to the correct result. For example, a "removed" and an "added" event that arrive in the wrong order during a role change still resolve correctly. > [!NOTE] > > Directory events take effect only after you complete the initial group mapping. While SCIM is connected but not yet mapped, changes in your IdP do not add, remove, or re-role anyone in Userpilot. The last remaining Account Owner is always protected and is never removed by a sync. *** ## Audit log Every provisioning action is recorded in the organization's access logs, alongside sign-ins, invites, and role changes, with one entry per change. You can filter by Source (Web app, SCIM provisioning, SSO sign-in, CSV import) and Activity (Member provisioned, Member deprovisioned, Changed a member's role, Role sync skipped (set manually), plus sign-in, 2FA, and SSO events), and search by teammate. Each entry shows the teammate, the role before and after, who triggered it, the application, and the reason. See [Access and activity logs](/configure/team-and-roles/access-and-activity-logs) for more. ### When a sync fails If a provisioning run fails, the Authentication page surfaces the error and the SCIM card shows a failure banner. Provisioning is durable and retries automatically, so transient failures usually clear on their own; you can also re-run **Sync Users** or re-map your groups to recover. Retrying removes no teammates. *** ## Disable SCIM Disabling SCIM has the following effects: 1. SCIM configuration and all group mappings are removed from Userpilot. 2. The connection to your IdP directory is disconnected. 3. Manual teammate management is unlocked again. 4. Teammates already provisioned through SCIM keep their access. 5. SSO Mandatory Login remains enforced. > [!WARNING] > > **Important.** While SCIM is enabled, teammates created through your IdP have no password. If you later disable SSO, those teammates lose access until they set a password, and they receive an email guiding them to do so. SCIM must be disabled before SSO can be disabled. ## FAQs > **Will a sync ever remove the last Account Owner?** > > No. The last remaining Account Owner is always protected and is never removed by a sync, even if their IdP group changes. > **Will SCIM overwrite roles I change manually in Userpilot?** > > It depends on your role-management mode. In hybrid mode (Identity provider selected, strict enforcement off), manual role edits survive syncs. With Strictly enforce IdP roles on, every sync overwrites roles to match your IdP. In Userpilot mode, SCIM never touches roles. > **What happens to a teammate in several mapped groups?** > > Userpilot grants the highest-privilege role among their mapped groups. You do not need to limit teammates to a single IdP group. # SSO Source: https://docs.userpilot.com/configure/team-and-roles/sso Enable Single Sign-On (SSO) in Userpilot to let users authenticate securely with a single set of credentials via Okta, Azure, Google, or other identity providers. > [!NOTE] > > This feature is available on the Enterprise plan. ### Overview Single Sign-On (SSO) lets a user authenticate once, with a single set of credentials, across several related but independent systems. When SSO is enabled, your team signs in to Userpilot through your identity provider (IdP) instead of a separate Userpilot password. Userpilot's SSO supports more than 12 identity providers across both SAML and OIDC, including Okta, Microsoft Entra ID (Azure AD), Google, OneLogin, and Ping. You connect your IdP once, and your team signs in securely from then on. ### Requirements * An active Userpilot subscription on the Enterprise plan. * An Account Owner role, or a role with the Manage SSO permission. * A third-party identity provider, such as [Okta](https://www.okta.com/), [Microsoft Entra ID](https://azure.microsoft.com/en-us/products/active-directory), or [Google](https://cloud.google.com/architecture/identity/single-sign-on). ### Where to configure SSO All authentication settings are under **Settings > Team > [Authentication](https://run.userpilot.io/settings/team/authentication)**. From this page you can enable SSO, require SSO for everyone, and, once SSO is on, set up [Just-in-Time provisioning](/configure/team-and-roles/jit) and [SCIM directory sync](/configure/team-and-roles/scim). ![Organization authentication page showing SSO, JIT, and SCIM](https://mintcdn.com/userpilot/HBAH9vHQbZ2nERNC/images/authentication-overview.png?fit=max\&auto=format\&n=HBAH9vHQbZ2nERNC\&q=85\&s=674791a115c1b3df84e85567ccf4d309) If your Userpilot instance has access to the Admin Console, you can also manage these settings on the [Admin Console's Authentication page](https://run.userpilot.io/admin-console/team/authentication), under Team. ## Enabling Single Sign-On **1. Open the Authentication page** Confirm you have an Account Owner role (or the Manage SSO permission), then go to **Settings > Team > Authentication**. **2. Start the SSO setup** In the **Single sign on (SSO)** card, click **Enable**. Userpilot opens the SSO setup portal in a new window. **3. Connect your identity provider** In the setup portal you will: 1. Add and verify the email domains your team signs in with, such as `acme.com`. Domain verification is what lets Userpilot route each teammate to the right IdP. 2. Choose your protocol (SAML or OIDC) and identity provider. 3. Follow the provider-specific steps to finish the connection. **4. Confirm SSO is enabled** Once the connection is active, Userpilot marks SSO as enabled and stores your provider and connection type. The Authentication page shows **Current SSO Provider** with your connection. ![SSO enabled](https://mintcdn.com/userpilot/dMw6IOT9pshG8YID/images/Screenshot-2026-04-21-at-10.36.05-AM.png?fit=max\&auto=format\&n=dMw6IOT9pshG8YID\&q=85\&s=f2d9d70616dee8f27e91f0ea080d3dd8) > [!NOTE] > > You configure the actual IdP (Okta, Entra, and so on) in the setup portal, not inside Userpilot. Userpilot stores only the connection reference and never sees your users' IdP passwords. ## Signing in with SSO Userpilot supports both sign-in started from Userpilot and sign-in started from your identity provider. **1. From the Userpilot sign-in page** On Userpilot's sign-on form, choose **Continue with Enterprise SSO** and enter your work email. Userpilot detects your domain, finds the matching connection, and redirects you to your IdP to authenticate. **2. From your identity provider** If your IdP shows a Userpilot tile (an IdP-initiated login), opening it signs you in directly. Newly created teammates land in your organization's default application. > [!NOTE] > > **New teammates without an account yet.** If a teammate does not have a Userpilot account, sign-in normally fails. They must first be invited, [imported via CSV](/configure/team-and-roles/csv-import), or synced through [SCIM](/configure/team-and-roles/scim). To create accounts automatically on first SSO login instead, enable [Just-in-Time (JIT) provisioning](/configure/team-and-roles/jit). ## Require SSO (SSO Mandatory Login) You can make SSO the only way your team signs in. On the **Single sign on (SSO)** card, turn on **Require all teammates to sign in with SSO**. When enforcement is on: * Password login is disabled for everyone in the organization. Teammates who used a password must sign in with SSO from then on. * Teammates created by SCIM or JIT are created without a password. > [!WARNING] > > When SCIM is enabled, SSO enforcement is applied automatically and cannot be turned off on its own, and SSO cannot be disabled while SCIM is enabled. Disable SCIM first if you need to change these settings. To turn enforcement off, switch **Require all teammates to sign in with SSO** off. Teammates who were passwordless receive an email with instructions to set a password so they can sign in without SSO again. ## Disabling or deleting SSO The **Single sign on (SSO)** card offers two actions: * **Disable** turns SSO off but keeps your SSO connection so you can re-enable it later. Disabling SSO also turns off JIT provisioning, because JIT only runs during SSO sign-in. * **Delete SSO Configuration** removes the SSO connection entirely. JIT settings, including the default role, are cleared as well. > [!WARNING] > > Neither action is available while SCIM is enabled, so disable SCIM first. Teammates who were created without a password (through SCIM or JIT) lose access if SSO is removed. They receive an email with instructions to set a password and regain access. ## FAQs > **Which identity providers are supported?** > > Userpilot supports more than 12 providers across SAML and OIDC, including Okta, Microsoft Entra ID (Azure AD), Google, OneLogin, and Ping. You choose and configure your provider in the SSO setup portal. > **Do I need SSO before setting up SCIM or JIT?** > > Yes. Both [SCIM directory sync](/configure/team-and-roles/scim) and [JIT provisioning](/configure/team-and-roles/jit) build on SSO and require it to be enabled first. > **What happens to passwords when SSO is enforced?** > > Password login is disabled for the organization. If you later turn enforcement off, passwordless teammates receive an email with a link to set a password. # Get started Source: https://docs.userpilot.com/configure/themes-and-design-library/get-started Get started with Userpilot Themes and Design Library to create brand-aligned, consistent in-app experiences across modals, tooltips, banners, surveys, and more. ## Overview Userpilot enables you to deliver in-app experiences that not only work for your users but also look and feel like an extension of your brand. Through its **Themes** and **Design Library** features, Userpilot gives you advanced and highly flexible options to ensure every modal, tooltip, banner, survey, or email you create is fully aligned with your design system, brand personality, and UI guidelines. Below is a detailed overview of how these features work and how you can use them to maintain brand consistency across all your in-app experiences. ## Themes: Systematic, brand-aligned styling Userpilot's Themes system acts as the master style guide for your in-app content. A theme is a set of shared styling rules that automatically apply to all your Userpilot content (flows, spotlights, banners, emails, checklists, etc.) for a consistent user experience. ![Modals (2).png](https://mintcdn.com/userpilot/0hCXw5eVRU6iqpYB/images/Modals\(2\).png?fit=max\&auto=format\&n=0hCXw5eVRU6iqpYB\&q=85\&s=00669125aef52d272df22698ed01ac99) ## Design Library: Your single source of styling truth The Design Library is built to give you and your team maximum flexibility and control over your in-app content style. It lets you organize design tokens into groups for easy management and supports a wide range of options you can define and reuse later from your themes, Chrome Extension, or when building custom themes for in-app experiences like surveys, checklists, or the Resource Center. ![colors (5).png](https://mintcdn.com/userpilot/6mq0DeMvNIpVRWN5/images/colors\(5\).png?fit=max\&auto=format\&n=6mq0DeMvNIpVRWN5\&q=85\&s=77e1f8014358b499cb533cd5a6718afa) ## Best practices for Userpilot themes and design library Here’s how to get the most out of these features: * **Map your brand system first**: Collect your color palette, typography rules, spacing standards, and assets. * **Create multiple themes if needed**: For example, one theme for light mode, another for dark mode, or create your own custom one. * **Use the design library to enforce consistency**: Don’t rely on ad hoc color picking; use saved tokens. > [!NOTE] > > Users on any paid plan can access Themes and create multiple themes to match their brand. However, the Design Library is available exclusively to Enterprise plan users. Users on the Growth or Starter plan will need to upgrade to Enterprise to unlock this advanced flexibility and fully transfer their design system into Userpilot. # Set up a design library Source: https://docs.userpilot.com/configure/themes-and-design-library/set-up-a-design-library Set up a Design Library in Userpilot to centralize brand styles, reuse approved design tokens, and maintain consistent, on-brand in-app experiences at scale. ## Overview The Design Library works as a powerful extension to the theme system, giving users more dynamic and flexible options to bring their company’s design system, crafted by the design team, into Userpilot content seamlessly. The idea is similar to how design teams organize and manage styles in Figma. Just like in Figma, where teams define and reuse components such as typography, colors, spacing and sizes, shadows, and assets, the Design Library provides a centralized place in Userpilot to set up these elements. You can create groups of styles that your team can consistently use across all Userpilot content, including the Chrome Extension, Checklists, Resource Center, NPS, and Surveys. This ensures your in-app experiences always feel cohesive and on-brand without needing to manually adjust styles every time. By setting up your Design Library, you give your entire team easy access to approved, reusable design tokens, making it faster and easier to maintain visual consistency, brand integrity, and a polished user experience at scale. With this approach, your Userpilot content isn’t just functional, it becomes a true extension of your product’s design system, mirroring the same careful planning and detail your team puts into your primary design tools. > [!NOTE] > > If you're on the **Enterprise plan**, you'll have access to the **Design Library** by default. Users on the **Starter** plan will need to **upgrade to Enterprise** or **Growth** to unlock this feature and start building their design library, giving them greater flexibility and control over their content styles. ## Use cases * **Brand consistency:** ensure all in-app messages follow approved colors, fonts, and spacing for a unified look. * **Centralized management:** update design tokens in one place and apply changes across all Userpilot content. * **Team collaboration:** give teams access to shared, brand-approved styles for easy, consistent creation. * **Faster creation:** save time by reusing pre-defined style groups instead of manual styling every time. * **Multi-brand support:** manage separate style groups for different brands, products, or regions. * **Cross-channel consistency:** apply the same design tokens to Chrome Extension, Checklists, Resource Center, and NPS. ## **How to create a design library** The Design Library lets you organize your design tokens into Groups for easy reuse. You can create Groups for Colors, Typography, Spacing & Sizes, Shadows, and Assets. You can start creating your theme by navigating to [Configure > Themes](https://run.userpilot.io/themes) from settings icon. ![themes-location](https://mintcdn.com/userpilot/vqOGYtQAPmJlvSl9/Screenshot2026-07-03at4.22.45PM.png?fit=max\&auto=format\&n=vqOGYtQAPmJlvSl9\&q=85\&s=9dd77b86e0cf32c410599910768ae464) Click on **Design Library** to begin adding your design style groups to your design library. ![design-library-location](https://mintcdn.com/userpilot/vqOGYtQAPmJlvSl9/Screenshot2026-07-03at4.33.11PM.png?fit=max\&auto=format\&n=vqOGYtQAPmJlvSl9\&q=85\&s=b7f2a9f6558c56c827474c25a9a16575) When you click on the Design Library button, you'll see several tabs at the top of the screen, including: * **Colors** * **Typography** * **Spacing & Sizes** * **Shadows** * **Assets** Choose the tab you want (e.g., Colors, Typography) ![design-library](https://mintcdn.com/userpilot/6mq0DeMvNIpVRWN5/images/colors\(6\).png?fit=max\&auto=format\&n=6mq0DeMvNIpVRWN5\&q=85\&s=88a591e8cbd37d4651f119019b639dd2) To begin adding your design system styles, simply click on **Create Group**. ![create-group](https://mintcdn.com/userpilot/6mq0DeMvNIpVRWN5/images/colors\(7\).png?fit=max\&auto=format\&n=6mq0DeMvNIpVRWN5\&q=85\&s=831ba810eeeaf76c9f08a016f69c7895) Then a **side panel** will open where you can start adding styles that match your **design system,** and then enter a **Group Name** and add your items. ![create-group-1](https://mintcdn.com/userpilot/6mq0DeMvNIpVRWN5/images/creategroup\(1\).png?fit=max\&auto=format\&n=6mq0DeMvNIpVRWN5\&q=85\&s=32977d4a9e37d2d4e8470e2f78548d7a) > [!TIP] > > You can add an unlimited number of items by clicking the “Add” button. Once you've finished adding your types, simply click **Create** to save your changes. ![example](https://mintcdn.com/userpilot/6mq0DeMvNIpVRWN5/images/colors\(8\).png?fit=max\&auto=format\&n=6mq0DeMvNIpVRWN5\&q=85\&s=aaecc038d95cda86669d4887572b6275) ## Design library tabs explained ### **Colors** * Use Groups to organize color tokens by theme (e.g. Primary, Grayscale, Status). * Define color names and HEX values. ![colors](https://mintcdn.com/userpilot/6mq0DeMvNIpVRWN5/images/colors\(9\).png?fit=max\&auto=format\&n=6mq0DeMvNIpVRWN5\&q=85\&s=08e7d9ed254ed23da164ee394af20b2d) ### **Typography** * Use Groups to save font styles (e.g. Headings, Body). * Define font family, weight, size, and line height. ![typography](https://mintcdn.com/userpilot/wmxQrQyJqRV1jwFr/images/typography\(1\).png?fit=max\&auto=format\&n=wmxQrQyJqRV1jwFr\&q=85\&s=e2757e9d2dbdf80760f9b13994a59db6) ### **Spacing & Sizes** * Use Groups to save spacing scales or size presets. * Define pixel or rem values. ![spacing-and-sizes](https://mintcdn.com/userpilot/wmxQrQyJqRV1jwFr/images/spacingandsizes\(1\).png?fit=max\&auto=format\&n=wmxQrQyJqRV1jwFr\&q=85\&s=75583fe8ac5600c5c0e6272af3e747e8) ### **Shadows** * Use Groups to save shadow presets. * Define offset, blur, color, and opacity. ![shadows](https://mintcdn.com/userpilot/na1wejgjnIBkViY6/images/Shadows\(1\).png?fit=max\&auto=format\&n=na1wejgjnIBkViY6\&q=85\&s=7f3413b6f44514a0d0a2c523cac56273) ### **Assets** * Use Groups to organize images, logos, or icons. * Upload and categorize assets. ![assets](https://mintcdn.com/userpilot/vqOGYtQAPmJlvSl9/Screenshot2026-07-03at4.39.39PM.png?fit=max\&auto=format\&n=vqOGYtQAPmJlvSl9\&q=85\&s=18b34161bf3ac30649f492a722bce18e) After setting up your design library, you can use these **token groups** throughout your Userpilot **content**, including the **Theme**, **Chrome Extension**, **Resource Center**, **Emails**, **Mobile content**, etc., to ensure a **consistent** and **unified experience** and **reduce creation time** by simply selecting **predefined styles**. ![example-on-content-builder](https://mintcdn.com/userpilot/6mq0DeMvNIpVRWN5/images/choosetype\(1\).png?fit=max\&auto=format\&n=6mq0DeMvNIpVRWN5\&q=85\&s=d21555fef6283c5decde9d554005d5e6) > [!NOTE] > > The design library currently supports the web app themes and content. We’ll expand its capabilities to include the mobile app theme and content next. # Set up a mobile app theme Source: https://docs.userpilot.com/configure/themes-and-design-library/set-up-a-mobile-app-theme Set up a mobile app theme in Userpilot to customize colors, fonts, and buttons, ensuring consistent, on-brand mobile experiences across devices. ## Overview Userpilot makes it easy to customize your mobile content theme so your mobile experiences look and feel consistent on any device. You can control colors, fonts, and button styles, ensuring your brand remains cohesive and professional across all operating systems. With these flexible styling options, you can deliver a seamless, on-brand experience without needing to manually adjust each guide for mobile users. ## **How to create a mobile theme** You can start creating your theme by navigating to [Configure > Themes](https://run.userpilot.io/themes) from the sidebar. ![2. Select Email from the list of items (4).png](https://mintcdn.com/userpilot/c8PWT_qZmVtj1uO0/images/Screenshot-2026-07-08-at-9.35.30-PM.png?fit=max\&auto=format\&n=c8PWT_qZmVtj1uO0\&q=85\&s=9f39f0ca3c37a08f341661758a57848a) You'll see two tabs, Web and Mobile, allowing you to customize your theme for each supported platform. To get started, simply click on **Mobile,** then **Create Theme,** and begin designing your custom theme ![mobile (2).png](https://mintcdn.com/userpilot/wmxQrQyJqRV1jwFr/images/mobile\(2\).png?fit=max\&auto=format\&n=wmxQrQyJqRV1jwFr\&q=85\&s=24d65afffac8622cff3476cb4bd3fe71) > [!NOTE] > > Userpilot provides default Light and Dark themes to help you get started quickly. You can start creating your theme by giving it a **clear**, **memorable name** to help you **organize** and **easily find** it when building your Userpilot content. Userpilot will also use the **primary color** you choose as the **foundation** to help **customize** your theme automatically. ![mobile (3).png](https://mintcdn.com/userpilot/wmxQrQyJqRV1jwFr/images/mobile\(3\).png?fit=max\&auto=format\&n=wmxQrQyJqRV1jwFr\&q=85\&s=887fda9d43e85557b080ab6d07046ac6) ## Carousels and Slideouts tabs The Carousel Theme in Userpilot lets you fully customize the appearance of your mobile carousels so they match your brand and deliver a seamless, engaging user experience on mobile devices. This ensures your onboarding and feature tours feel consistent with the rest of your app. Sections in the carousel include: * **General:** set the font family and content alignment to ensure text displays clearly and aligns properly on mobile screens. * **Color:** choose the background color, title color, and text color to match your brand’s palette for a consistent look. * **Button:** customize the background color, label color, border color, border width, and border radius to keep buttons on-brand and easy to interact with. * **Dismiss Carousel:** adjust the color and icon color for the dismiss button to make exiting the carousel clear and consistent with your design. * **Progress:** set the progress indicator color to align with your brand and provide users with clear visual feedback on their place in the carousel. * **Slideouts only - Backdrop:** set the backdrop color and opacity to create focus by dimming the background when the slideout is active. ![Carousels (1).png](https://mintcdn.com/userpilot/0hCXw5eVRU6iqpYB/images/Carousels\(1\).png?fit=max\&auto=format\&n=0hCXw5eVRU6iqpYB\&q=85\&s=fd712ef7967a2bc6b76fe245e3181ba2) ![Slideout (1).png](https://mintcdn.com/userpilot/na1wejgjnIBkViY6/images/Slideout\(1\).png?fit=max\&auto=format\&n=na1wejgjnIBkViY6\&q=85\&s=7e09494fe47b4ac342906ea9901857b4) ## Surveys tab The Mobile Survey Theme allows you to customize the look of your surveys on mobile screens, giving you control over colors, fonts, progress indicators, and backdrops so your surveys stay on-brand and easy for users to navigate. * **General:** set the background color, primary color, corner radius, and survey position on mobile screens to match your brand and control layout. * **Font:** choose the font family and text color to ensure readability and consistent typography on mobile devices. * **Progress Bar:** select the progress type and set its color to guide users visually through multi-step surveys. * **Backdrop:** define the backdrop color and opacity to focus user attention by dimming the background behind your survey. ![Surveys (1).png](https://mintcdn.com/userpilot/na1wejgjnIBkViY6/images/Surveys\(1\).png?fit=max\&auto=format\&n=na1wejgjnIBkViY6\&q=85\&s=15e6c8090721dbb7b3c482bc7441b8bc) > [!NOTE] > > You can view your changes in both **iOS** and **Android** layouts by clicking the icons above the mobile mockup in the preview area. This lets you see exactly how your designs will look on each platform. > [!NOTE] > > Font family is set by default to **“System Default”**, which Userpilot automatically maps to **San Francisco on iOS and Roboto on Android**. If you want to use your own brand font instead, you can easily upload it using the Upload Custom Font option. > [!NOTE] > > **General notes:** > > * Your Userpilot account must always have one **default theme** to use when creating content, but you can change it anytime. > * The **default theme can’t be deleted**. > * To delete the current default theme, first set another theme as the default, then you can remove the one you no longer want. > * If you’ve created content using a specific theme, you can’t delete that theme until you **unlink all content** by changing its theme assignments. > * You can view all content linked to a theme using the **“View content using this theme”** option in the theme’s action menu. > > ![Screenshot 2025-06-29 at 22.29.44 (3).png](https://mintcdn.com/userpilot/0BXmctFYfQD3s2Lh/images/Screenshot2025-06-29at22.29.44\(3\).png?fit=max\&auto=format\&n=0BXmctFYfQD3s2Lh\&q=85\&s=35785e36a717af6edd83517339cfc998) > * You can **duplicate**, **rename**, and **update** themes anytime. > * Click the **“Save”** button to keep your changes, since the theme page doesn’t autosave like other areas in Userpilot. ## FAQs > **Can I use the same theme for both web and mobile content?** > > No. Themes are platform-specific, the Themes page has separate **Web** and **Mobile** tabs, and a mobile theme only applies to mobile content (carousels, slideouts, surveys). You'll need to create and customise them independently. # Set up a web app theme Source: https://docs.userpilot.com/configure/themes-and-design-library/set-up-a-web-app-theme Set up a web app theme in Userpilot to customize colors, fonts, and UI styles, ensuring consistent, brand-aligned in-app experiences. ## **Overview** Userpilot allows you to customize your theme to match your brand’s identity effortlessly. You can control colors, fonts, button styles, and other design elements to ensure a consistent user experience. With flexible styling options, you can maintain brand consistency across all in-app experiences without needing constant design updates. ## **How to create a theme** You can start creating your theme by navigating to [Configure > Themes](https://run.userpilot.io/themes) from the settings icon on the top-right side. ![Image](https://mintcdn.com/userpilot/QDCCHNUaGEa4NnAi/images/image-45.png?fit=max\&auto=format\&n=QDCCHNUaGEa4NnAi\&q=85\&s=5ee7b81bdb8e4e3b695cf3697fa94380) You'll see two tabs, Web and Mobile, allowing you to customize your theme for each supported platform. To get started, simply click on **Web,** then on **Create Theme,** and begin designing your custom theme. ![Image](https://mintcdn.com/userpilot/QDCCHNUaGEa4NnAi/images/image-46.png?fit=max\&auto=format\&n=QDCCHNUaGEa4NnAi\&q=85\&s=0d8eaec9b8c775a4b77de67c2e738590) > [!NOTE] > > Userpilot provides default Light and Dark themes to help you get started quickly. You can start creating your theme by giving it a **clear**, **memorable name** to help you **organize** and **easily find** it when building your Userpilot content. Userpilot will also use the **primary color** you choose as the **foundation** to help **customize** your theme automatically. ![Image](https://mintcdn.com/userpilot/QDCCHNUaGEa4NnAi/images/image-47.png?fit=max\&auto=format\&n=QDCCHNUaGEa4NnAi\&q=85\&s=de30ec54cdeee05ac5fa72df4bbca789) ## General Through the General tab, you have a centralized location to apply changes across all theme elements, including flows, spotlights, banners, checklists, emails, and surveys. If you'd like to customize settings for a specific feature, simply navigate to that feature and apply custom style settings. These changes will apply only to that feature and will override the settings in the General tab. ![General (2).png](https://mintcdn.com/userpilot/0hCXw5eVRU6iqpYB/images/General\(2\).png?fit=max\&auto=format\&n=0hCXw5eVRU6iqpYB\&q=85\&s=c6e19c37939f7bda9f905fd9860fa4b2) The General tab includes multiple sections that provide advanced options to help you customize your content and ensure it matches your design system, such as: * **Container:** Set the background color and z-index for your content container to control layering and base styling. * **Font:** Choose the default font family and font color to ensure text matches your brand’s typography guidelines and meets accessibility contrast requirements. * **Link:** Customize link color and hover color, and choose whether links open in a new browser tab for better user control and clarity. * **Box & Border:** Adjust border type, shadow, and corner radius to style your content’s container with depth and rounded edges. * **Progress Bar:** Configure the progress bar for Flow UI patterns with a group of steps, setting its type, color, padding, and position to guide users clearly through multi-step experiences. * **Backdrop:** Set the backdrop color and opacity to create focus and separation from the underlying UI when displaying overlays. * **Close button:** Customize the icon, size, color, hover states, background, and position of the close button to ensure it aligns with your brand and is easy for users to find. > [!NOTE] > > **Useful terms to know** > > * **Z-index** > Z-index decides which elements appear on top of others on your screen. Think of it like stacking sheets of paper; whatever has a higher z-index is on top of the pile and will be visible above other parts of your app. > * **Why use it?** > When you set the z-index in Userpilot, you make sure related content like modals, banners, or checklists don’t get hidden behind your app’s content. It helps keep your messages clear and easy for users to see. ## **Customizing Your Font** To customize your content’s font, search for the **Font** section and click the drop-down menu. Choose **Custom** and select **Add New Custom Font**. ![Screenshot 2025-06-29 at 22.16.09 (1).png](https://mintcdn.com/userpilot/0BXmctFYfQD3s2Lh/images/Screenshot2025-06-29at22.16.09\(1\).png?fit=max\&auto=format\&n=0BXmctFYfQD3s2Lh\&q=85\&s=47dabfc39a3d10fd371676ae61aeda60) A pop-up will appear where you can name your Font Family. The Custom CSS will be pre-filled in *comment* mode (with slashes), so it won't be active yet. ![Screenshot 2025-06-29 at 22.17.53 (1).png](https://mintcdn.com/userpilot/0BXmctFYfQD3s2Lh/images/Screenshot2025-06-29at22.17.53\(1\).png?fit=max\&auto=format\&n=0BXmctFYfQD3s2Lh\&q=85\&s=b928b95f638cdd573b7c93980c6098e6) To activate the CSS, simply remove the slashes (/) and then click the **Add Font** button. ## Button styles Through the Button Styles tab, you can create multiple button styles tailored to your use cases and design system. This saves time by eliminating the need to customize button settings each time you create content in Userpilot. Your saved styles will always be ready to use whenever you build content in flows, banners, emails, and surveys. ![buttons (4).png](https://mintcdn.com/userpilot/6mq0DeMvNIpVRWN5/images/buttons\(4\).png?fit=max\&auto=format\&n=6mq0DeMvNIpVRWN5\&q=85\&s=9a542ffa15c832aa5f7408b0c4b28ad0) To create a new button style, click **Create Button**, and Userpilot will then add a new section where you can fully customize your button’s appearance to match your brand. ![buttons (5).png](https://mintcdn.com/userpilot/6mq0DeMvNIpVRWN5/images/buttons\(5\).png?fit=max\&auto=format\&n=6mq0DeMvNIpVRWN5\&q=85\&s=7d8a6856f7ef74d645716a88c137f71c) The Button section has lots of options to help you style your buttons so they match your design system, like: * **Background color:** Set the button’s default color to match your brand palette. * **Background color on hover:** Choose the color the button changes to when hovered for clear interactive feedback. * **Text color:** Define the label color for strong readability and brand consistency. * **Text color on hover:** Customize the label color when hovered to maintain contrast and visibility. * **Default state contrast ratio:** Check accessibility contrast between background and text in the default state. * **Hover state contrast ratio:** Verify accessibility contrast in the hover state for better usability. * **Padding:** Control the internal spacing to ensure balanced and comfortable button labels. * **Corner radius:** Adjust the roundness of the button’s corners to match your design system. * **Typography:** Select the font style and weight to keep buttons consistent with your brand’s typography. * **Border:** Enable or disable borders and define their style for outlined or solid button designs. * **Border color:** Set the border color for the button’s default state to align with your palette. * **Border color on hover:** Choose the border color when hovered to enhance interactive feedback. * **Border thickness:** Define the border width for subtle or bold visual effects. > [!NOTE] > > To save time, create a primary button style, then duplicate it and tweak the colors for your secondary button if they share a similar design. ## Flows Under the Flows tab, you’ll find a variety of options for customizing each Userpilot UI pattern to match your design system. We know that small design details can make a big difference in user experience and engagement, so Userpilot gives you the flexibility to fine-tune even the finer points to make your content more visually appealing and on-brand. The available UI pattern tabs include: * **Modals** * **Slideouts** * **Tooltips** * **Driven Actions** Each UI pattern has its own customization options and sections similar to the general settings shown here (like background color, font, box & border, progress bar, backdrop, buttons, and close button). This consistent structure makes it easy to apply cohesive styles across all your in-app flows while tailoring details for each pattern. Feel free to explore each tab to unlock maximum customization and deliver a polished, engaging user experience. ![Modals (3).png](https://mintcdn.com/userpilot/0hCXw5eVRU6iqpYB/images/Modals\(3\).png?fit=max\&auto=format\&n=0hCXw5eVRU6iqpYB\&q=85\&s=4e61736edb1526411593c4779f46936f) ## Spotlights Under the Spotlights tab, you can customize your in-app spotlight experiences to perfectly match your brand and design system. The Spotlights tab is split into two subtabs: * **Native Tooltips & Hotspots** * **General:** Customize background color, content alignment, z-index, and padding for consistent positioning and layering. * **Box & Border:** Set border type, shadow, and corner radius to match your brand’s depth and shape standards. * **Badge:** Configure icon, shape, size, opacity, fill colors (default and hover), and border styles to draw attention to hotspots without clashing with your design system. * **Label:** Style the text container of tooltips by adjusting background and hover colors, font settings, padding, border, and corner radius, with built-in contrast checking for accessibility. * **Hotspot:** Define hotspot color, size, animation type, and behavior (like pulse animation) to make it stand out without being distracting. ![Native tooltips (1).png](https://mintcdn.com/userpilot/0hCXw5eVRU6iqpYB/images/Nativetooltips\(1\).png?fit=max\&auto=format\&n=0hCXw5eVRU6iqpYB\&q=85\&s=808d8aea01c5a5e2b1775ec53cf0ad97) * **Buttons** * **General:** Set background and hover colors, padding, corner radius, and z-index for button placement. * **Font:** Choose text color and hover color, typography, and ensure accessibility with built-in contrast ratio checks. * **Border:** Customize border type, shadow or solid, color, and corner radius to match your design system perfectly. ## Embeds The Embeds tab lets you customize in-app notification for both Banners and Cards to match your brand and design system, ensuring important messages look polished and consistent. Banners & Cards style sections: * **General:** Set background color, font color, z-index, and position, with contrast checks for accessibility. * **Box & Border:** Customize border style, width, shadow, corner radius, and choose full-width or container layout. * **Buttons:** Style the primary button with options for background and hover colors, text colors, padding, corner radius, typography, and borders, all with contrast checks. * **Close Button:** Adjust icon, size, background and hover colors, corner radius, position, icon color, and default behavior. ![Image](https://mintcdn.com/userpilot/QDCCHNUaGEa4NnAi/images/image-48.png?fit=max\&auto=format\&n=QDCCHNUaGEa4NnAi\&q=85\&s=0f8b8b3d89e1fb9b88bc1de831346bf6) ## Checklists The Checklists tab lets you fully customize the look and feel of your in-app task lists to match your brand, making onboarding experiences clear, engaging, and consistent. It’s divided into two subtabs: * **Widget** * **General:** Set header, background, progress, carets, CTA, and completion colors, with an option to auto-open the widget for smoother onboarding. * **Text:** Customize font family and header/body font colors with contrast checks for clear, accessible reading. * **Minimize Button:** Style the caret and close icons with control over colors, hover states, size, and corner radius to match your brand. ![widget (1).png](https://mintcdn.com/userpilot/AID1pCY-bQgJdWy3/images/widget\(1\).png?fit=max\&auto=format\&n=AID1pCY-bQgJdWy3\&q=85\&s=35bc9ae2d159a4f248f34b2a946418e8) * **Beacon** * Select beacon type (icon in circle, square, text only, or icon & text). * Set color, background color, position on the page, and z-index. * Adjust position easily for perfect placement in your app layout. ![beacon (1).png](https://mintcdn.com/userpilot/6mq0DeMvNIpVRWN5/images/beacon\(1\).png?fit=max\&auto=format\&n=6mq0DeMvNIpVRWN5\&q=85\&s=806b6385cac6ee5c0741182c904459f8) ## Surveys The Surveys tab lets you customize the look and feel of your in-app surveys to keep them consistent with your brand and visually engaging for users. * **General:** Set primary color, background color, and position to control where your survey appears and ensure it matches your brand. * **Box & Border:** Customize border type, shadow color, and corner radius \*\*\*\*to style the survey container consistently. * **Font:** Choose font family and text color, with contrast checks to keep text clear and accessible. * **Progress Bar:** Select progress type and set the bar color to visually guide users through multi-step surveys. * **Backdrop:** Define color and opacity to create focus by dimming the background behind your survey. * **Close Button:** Style the close icon with background and hover colors, icon color, size, and corner radius for a polished, on-brand look. ![Screenshot 2025-06-29 at 21.55.59 (1).png](https://mintcdn.com/userpilot/0BXmctFYfQD3s2Lh/images/Screenshot2025-06-29at21.55.59\(1\).png?fit=max\&auto=format\&n=0BXmctFYfQD3s2Lh\&q=85\&s=db4be22456173f530c3579b671d64ca7) > [!NOTE] > > **Note** > > If you're on the Enterprise plan, you get even more flexibility with your themes by using the Custom CSS tab and the [Design Library](https://docs.userpilot.com/configure/themes-and-design-library/set-up-a-design-library). Once you've set up your Design Library, you'll see extra options under colors, font family, and spacing, letting you either choose from pre-defined library groups or customize styles on the spot with the custom option. > > ![buttons (7).png](https://mintcdn.com/userpilot/6mq0DeMvNIpVRWN5/images/buttons\(7\).png?fit=max\&auto=format\&n=6mq0DeMvNIpVRWN5\&q=85\&s=afb89daf43c81d40263ef95d42babe95) > [!NOTE] > > **General notes:** > > * Your Userpilot account must always have one **default theme** to use when creating content, but you can change it anytime. > * The **default theme can’t be deleted**. > * To delete the current default theme, first set another theme as the default, then you can remove the one you no longer want. > * If you’ve created content using a specific theme, you can’t delete that theme until you **unlink all content** by changing its theme assignments. > * You can view all content linked to a theme using the **“View content using this theme”** option in the theme’s action menu. > > ![Screenshot 2025-06-29 at 22.29.44 (2).png](https://mintcdn.com/userpilot/0BXmctFYfQD3s2Lh/images/Screenshot2025-06-29at22.29.44\(2\).png?fit=max\&auto=format\&n=0BXmctFYfQD3s2Lh\&q=85\&s=ea3c10ad62f592c45a69aef490035be1) > * You can **duplicate**, **rename**, and **update** themes anytime. > * Click the **“Save”** button to keep your changes, since the theme page doesn’t autosave like other areas in Userpilot. # Theme Switching Source: https://docs.userpilot.com/configure/themes-and-design-library/switching-themes Learn how to switch themes like light and dark mode in Userpilot using userpilot.theme() to match your app UI in real time. ### Overview Theme Switching in Userpilot allows you to dynamically adapt in-app experiences, such as modals, tooltips, banners, and checklists, to match your application’s active theme (for example, light mode or dark mode). This is especially useful for products that support multiple visual themes and want Userpilot content to feel native, consistent, and seamless within the host application. By using the `userpilot.theme()` function, you can control which theme Userpilot uses at runtime, ensuring that content shown in live mode always aligns with your product’s UI state. ### Use cases * **Light/Dark mode support:** When your application allows users to toggle between light and dark modes, Userpilot content should adapt automatically to avoid visual clashes or accessibility issues. * **Brand or tenant-based theming:** For multi-tenant or white-labeled products, different customers may use different brand themes. Theme Switching ensures the right Userpilot theme is applied per tenant or account. * **Improved accessibility and readability:** Ensuring correct contrast, background, and typography based on the theme improves usability and WCAG compliance. ### How theme switching works in Userpilot Userpilot supports theme control via a JavaScript API method, `userpilot.theme()`. This method tells Userpilot which theme should be active so that all live content (flows, tooltips, banners, etc.) renders using the correct theme styles. > [!WARNING] > > Important: Theme switching affects **content shown in live mode**. It does not change your app’s theme, only how Userpilot content is displayed. ### How to switch between themes for live content 1. First, make sure you have created the relevant themes (e.g. `light`, `dark`) in your Userpilot theme settings. 2. Your application should already know which theme is active (for example, from user preferences, system settings, or a global state). 3. When the theme changes (or on initial page load), call the Userpilot API with the appropriate theme name: ```javascript theme={null} userpilot.theme('dark'); ``` or ```javascript theme={null} userpilot.theme('light'); ``` 4. Once set, any Userpilot content that appears in live mode will automatically render using the selected theme, without needing to reload the page or re-trigger flows. > [!NOTE] > > * You can dynamically change the primary color of a theme using `userpilot.theme({ primary: "PRIMARY_COLOR" })`. This allows you to customize the primary color per user or account without the need to create and maintain 20+ separate themes. It’s especially useful for applications that support color customization or white-labeling. > * You can disable the applied theme at any time by calling `userpilot.theme(0)`. This stops the selected theme from rendering on live Userpilot content without needing a page reload or flow re-trigger. ### Applying themes to the Resource Center Resource Center and NPS surveys don’t have their own dedicated Userpilot themes, so they don’t automatically adapt when you call `userpilot.theme()`. Currently, you can adapt Resource Center content to your active theme by passing a second argument with a `resource_center_schema` matching your theme name: ```javascript theme={null} userpilot.theme('dark', { resource_center_schema: 'dark' }); ``` This adds a `[data-app-theme="dark"]` attribute you can use to scope your custom CSS, prefixing it before your existing Resource Center element selectors. For example: ```css theme={null} [data-app-theme="dark"] #navigation-bar, [data-app-theme="dark"] #userpilot-resource-centre, [data-app-theme="dark"] #widget-headers { /* your dark-mode styles */ } ``` ### Best practices 1. Call `userpilot.theme()` early in your app lifecycle (e.g. after user login or app initialization) 2. Re-call the method when the theme changes, especially if users can toggle themes at runtime 3. Keep theme naming consistent between your app and Userpilot (e.g. `light`, `dark`, `high-contrast`) 4. Test in live mode, not just previews, to ensure the behavior matches real user experiences # Mobile Event Auto-Capture Source: https://docs.userpilot.com/data-events/autocapture/mobile-events-autocapture Automatically capture supported mobile user interactions with the Userpilot SDK without manual event instrumentation. Mobile event auto-capture allows Userpilot to automatically collect supported user interactions inside your mobile app, without requiring developers to manually instrument every event. Once enabled, Userpilot starts capturing common interactions such as taps, text changes, and screen views, giving your team a foundation of behavioral data from day one. ## Overview With mobile event auto-capture, the Userpilot SDK listens for supported native interactions and records them automatically as raw events. These raw events can then be labeled in Userpilot to turn them into named, trackable events, ready to use in analytics, reports, segments, and mobile experiences. This differs from manual event tracking, where developers write a tracking call for each individual event. Auto-capture removes that overhead for common interactions, while labeling gives your team control over which raw events matter and what they should be called. Auto-capture is enabled at the SDK level using two flags: ```swift theme={null} // iOS enableScreenAutoCapture() enableInteractionAutoCapture() // Android enableScreenAutoCapture = true enableInteractionAutoCapture = true ``` ## Use cases 1. **Understand how users interact with key mobile screens:** see which elements users tap, change, or engage with, without pre-defining every event 2. **Build reports based on common mobile interactions:** use auto-captured events as the basis for funnel, trend, or engagement reports 3. **Create segments based on mobile behavior:** group users by the interactions they've completed, such as tapping a specific button or reaching a particular screen 4. **Reduce the need for manual event instrumentation:** cover a broad set of interactions automatically, so engineering effort can focus on more complex or custom tracking needs 5. **Identify drop-offs or friction points in mobile journeys:** spot where users stop interacting or abandon a flow by reviewing which events fire and which don't ## How mobile event auto-capture works After installing and configuring the Userpilot mobile SDK, auto-capture begins collecting raw interaction data in the background. Here's the general flow: 1. **Install or update the Userpilot mobile SDK** and enable auto-capture using the SDK flags above 2. **Userpilot starts collecting raw events** - supported interactions across your app are captured automatically 3. **Raw events appear in Userpilot** - navigate to [Data → Explore raw events](https://run.userpilot.io/events/explore-raw-events?tab=mobile) to review what has been captured ![Screenshot 2026 06 03 At 22 31 02](https://mintcdn.com/userpilot/J5sPPjEOslFUBac9/images/Screenshot-2026-06-03-at-22.31.02.png?fit=max\&auto=format\&n=J5sPPjEOslFUBac9\&q=85\&s=8855aa03e99593c6b25c3b842ebc0d64) 4. **Label raw events** A **Mobile Labeled Event** turns a raw, auto-captured interaction into a named, trackable event. You define it in a right-hand slideout with two tabs: **Details** and **Targeting**. **Targeting** The **Targeting** tab contains two independently toggle-able blocks - **iOS** and **Android**. A single labeled event can target one platform or both. The platform you originally captured the event on is shown first and comes pre-filled. For each enabled platform, you define three things: **Screen:** where the event fires. Choose *Any Screen* (anywhere in the app) or *Specific Screen* (constrained by conditions like `screen_name equals "Cart"`). **Event properties:** the element's fingerprint. These identify the specific element and determine whether an interaction matches this event. The available properties differ between iOS and Android, but the strongest identifiers are available on both: 1. **Hierarchy** - the element's structural position on screen; reliable on its own 2. **Target class** - the element's native UI class or type 3. **Element ID** - Accessibility identifier (iOS) or Full resource ID (Android); the most precise match when present 4. **Accessibility label** - the element's accessibility text Each platform also exposes additional properties - for example, *Owner target class* and *Target view name* on iOS; *View tag* and *Target view class* on Android - plus action-specific properties like *Placeholder* (text changes), *Tab name* (selections), and *Dialog title* (presented views). > [!NOTE] > > You don't need to enable all properties. Hierarchy, Target class, and the platform's ID, when available, are usually enough to identify the element. Additional properties are available under **Advanced filters** and should only be added when you need to disambiguate further, such as for list rows or two controls that differ only by label. Enabling too many properties makes the event too strict and can cause valid interactions to be missed. At least one property is required. > > ![Screenshot 2026 06 03 At 22 57 48](https://mintcdn.com/userpilot/J5sPPjEOslFUBac9/images/Screenshot-2026-06-03-at-22.57.48.png?fit=max\&auto=format\&n=J5sPPjEOslFUBac9\&q=85\&s=437f3174692163d8aa3fd07c997ae6eb) **Text Contains (optional):** matches the element's visible text using *Contains* or *Equals* - useful when identical elements differ only by label, for example, *"Add to Cart"* vs *"Remove"*. ![Screenshot 2026 06 03 At 22 58 47](https://mintcdn.com/userpilot/J5sPPjEOslFUBac9/images/Screenshot-2026-06-03-at-22.58.47.png?fit=max\&auto=format\&n=J5sPPjEOslFUBac9\&q=85\&s=2d4b864e86179dd35bdebebe9e1daefd) ### Importing from raw events You can label the event for Android, iOS, or both by enabling each OS toggle. To make this easier, Userpilot offers an option to view captured events from the other OS that match the selected attribute from the original event. You can then select the matching event and import its values to track the same interaction across Android and iOS as one labeled event. ![Screenshot 2026 06 07 At 15 40 29](https://mintcdn.com/userpilot/J5sPPjEOslFUBac9/images/Screenshot-2026-06-07-at-15.40.29.png?fit=max\&auto=format\&n=J5sPPjEOslFUBac9\&q=85\&s=241937cf107874d919402dfc6e1f09bd) ![Screenshot 2026 06 07 At 15 41 56](https://mintcdn.com/userpilot/J5sPPjEOslFUBac9/images/Screenshot-2026-06-07-at-15.41.56.png?fit=max\&auto=format\&n=J5sPPjEOslFUBac9\&q=85\&s=9a919c3e8ac788724bc8ac0b9f1ed04a) ## Supported interaction types The following interaction types are captured automatically when auto-capture is enabled: | Interaction category | Interaction type | Meaning | | :- | :- | :- | | Tapped | Tap | The user tapped/clicked on an element, such as a button, card, icon, link, or menu item | | Changed text | text field changed | The user entered, edited, or deleted text inside an input field, such as search, email, name, comment, or password fields | | Changed selection | radio button selected | The user selected one option from a set of radio buttons. Usually only one option can be selected at a time | | Changed selection | chip selected | The user selected a chip/tag/pill-style option, such as a filter, category, or quick choice | | Changed selection | list item selected | The user selected an item from a list, such as choosing a country, user, product, or setting | | Changed selection | spinner selected | The user selected an option from a dropdown/spinner menu. Common in Android forms | | Changed selection | tab host selected | The user selected a tab inside a tab container or tab host. Usually used when switching between sections | | Changed selection | tab selected | The user tapped a specific tab, such as Overview, Settings, Analytics, etc | | Changed selection | menu item selected | The user selected an item from a menu, overflow menu, or contextual menu | | Changed value | switch changed | The user turned a switch on or off. Example: enabling notifications | | Changed value | checkbox selected | The user checked or unchecked a checkbox. Often used for multi-select options or consent | | Changed value | toggle button selected | The user selected or toggled a button that has an active/inactive state. Similar to a switch but often styled as a button | | Changed value | slider changed | The user changed a slider value, such as volume, price range, percentage, or brightness | | Changed value | date picker changed | The user selected or changed a date from a date picker | | Changed value | time picker changed | The user selected or changed a time from a time picker | | Presented view | view presented | A screen, modal, bottom sheet, dialog, or view became visible to the user | ![Screenshot 2026 06 03 At 22 47 41](https://mintcdn.com/userpilot/J5sPPjEOslFUBac9/images/Screenshot-2026-06-03-at-22.47.41.png?fit=max\&auto=format\&n=J5sPPjEOslFUBac9\&q=85\&s=dc9fbb439fdfe3d79b1f8b9bff67f2fe) ## Viewing and using auto-captured events 1. Once events are labeled, they are available across Userpilot for analysis and targeting. You can use labeled mobile events in: 2. **Reports** - build funnels, trends, or engagement reports based on specific mobile interactions 3. **Segments** - group users by whether they have or haven't completed a particular interaction 4. **Mobile experiences** - trigger in-app experiences when a user performs a specific action, such as tapping a button or reaching a particular screen To review your labeled mobile events, navigate to [Events → Overview](https://run.userpilot.io/events/overview), then filter the data by selecting **Mobile** from the platform filter. ![Screenshot 2026 06 03 At 23 07 55](https://mintcdn.com/userpilot/J5sPPjEOslFUBac9/images/Screenshot-2026-06-03-at-23.07.55.png?fit=max\&auto=format\&n=J5sPPjEOslFUBac9\&q=85\&s=3c0f201e01baa8b2fd635300d0707344) > [!NOTE] > > The availability of auto-captured events in reports, segments, and experiences may depend on the mobile SDK version and platform support. Check with your engineering team to confirm your current setup. > **Developer setup guides** > > Use the technical setup guides below to enable Mobile Event Auto-Capture in your app: > > * [iOS auto-capture setup](/developer/installation/mobile/ios/auto-capture) > * [Android auto-capture setup](/developer/installation/mobile/android/auto-capture) # Raw Events Source: https://docs.userpilot.com/data-events/autocapture/raw-events Userpilot can automatically capture events using the raw events auto-capturing feature, which captures events like Clicks, Text Inputs, and Form Submissions. ### Overview Userpilot's raw events auto-capture automatically captures Clicks, Text Inputs, and Form Submissions, so you don't need to visually label events with the Chrome extension; you can label them directly from the auto-capture events page. It starts collecting data the moment auto-capture is enabled, reflected in Userpilot in real time. Auto-capture powers advanced segmentation, content triggering, and analytical reporting, letting nontechnical teams (product and UI/UX) and even stakeholders make data-driven decisions. ### Use Cases * Surface your most interacted-with features by occurrence count, then easily flag and label the important ones * Get an intuitive view of feature usage and where users spend their time on your product * Once labeled, use the events to trigger content and track user activity for segmentation ### Raw Events Auto-Capture Enabling/Excluding Under [Configure - Settings](https://run.userpilot.io/settings/data-capture-privacy), raw events auto-capturing can be enabled/disabled. This has to be enabled for Userpilot to collect users' interactions (clicks, text input, form submissions). ![](https://mintcdn.com/userpilot/O9Qz3xXrHx29CSpn/data-events/autocapture/assets/images/raw-events-settings.png?fit=max\&auto=format\&n=O9Qz3xXrHx29CSpn\&q=85\&s=24ed1f5f5665bc4335c82064d866febd) Excluding certain elements, domains, and locations can also be added under the exclude fields not to be auto-captured by Userpilot. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/autocapture/assets/images/Screenshot2025-01-25175249.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=2da2f3bec05765ad9fb8f382706e91c6) ### Types of interactions captured * **Click**: Any clicks on a button, link, or element (meaningful clicks/actions). In other words, clicking buttons with underlying mutations, links, or elements, each with a descriptive title. * **Text Input**: Typing a character or more into a text field, with each interaction tied to the field’s placeholder or input name. The description may include the placeholder text or the input name, depending on the field. * **Form Submission**: The user submits a form, typically via a button or submit input which triggers the underlying action or mutation to process the data entered in the form. ### Labeling Raw Events > [!NOTE] > > This is only available for Growth & Enterprise plans Turn a Raw Event into a [**Labeled Event**](../autocapture/visual-labeler) by clicking "Label Event" or "Visually label" next to the captured Raw Event. Some settings are pre-filled based on how the event was captured (Name, Domain, URL path, and CSS selectors), and you can customize them as needed. ![](https://mintcdn.com/userpilot/O9Qz3xXrHx29CSpn/data-events/autocapture/assets/images/explore-raw-events.png?fit=max\&auto=format\&n=O9Qz3xXrHx29CSpn\&q=85\&s=bdc6c71c9530e515b7d01aaec42a3114) After the event has been labeled, it will appear under [Overview](https://run.userpilot.io/events/overview) with the rest of the events (tracked events, custom events). It can be used for segmentation, content triggering, and creating analytical reports. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/autocapture/assets/images/events.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=1115ca53f63642bdcec8af4efa13cb2b) > [!NOTE] > > Once you label a Raw Event, its **historical data** (collected before labeling) becomes available in **Event Overview** and **Analytics** (report creation). **Segments** and **Content Triggering**, however, only consider data collected after the event was labeled. [YouTube video player](https://www.youtube.com/embed/25YE0k-8Lrg?si=nrtbWvAIFRGcB8uy) > [!WARNING] > > Changing the configuration/targeting settings of a Labeled Event affects the historical data collected. ### Supported CSS Selectors for Autocapture Raw Events **Tag Name** Examples: `div`, `span`, `button`, `a`, `form`, `font`, `path`, `svg`, `input`, etc... **Tag Name with Class/ID** Examples: | `a.MuiButtonBase-root.MuiListItem-button.MuiListItem-gutters` | | | - | - | | `#main-container-id, #table-container-id` | | | `div#root` | | **Nth-child** Example: `div.MuiPaper-root.MuiPaper-rounded:nth-child(3)` **Attribute Selectors** * **Specific Value**: `[attr="V"]` - Select elements that have attribute `attr` with a specific value of `V`. Example: `div[role="button"][aria-haspopup="true"][aria-disabled="false"]` * **The Attribute Value Starts With**: `[attr^="V"]` - Select elements that have attribute `attr` with a value starting with `V`. Example: `div[tabindex="0"][role="button"][aria-labelledby^="period"]` * **The Attribute Value Ends With:** `[attr$="V"]` - Select elements that have attribute `attr` with a value ending in `V` * **The Attribute Value Contains:** `[attr*="V"]` - Select elements that have attribute `attr` with a value containing `V` * **The Attribute Starts With a Value Followed by Hyphen:** `[attr|="V"]` - Select elements that have attribute `attr` with an exact value of `V` or start with `V` followed by a hyphen (`V-`). Example: `[role~="dialog"][aria-describedby|="alert-dialog"]` * **The Attribute Value is a Space-Separated List of Words:** `[attr~="V"]` - Select elements that have attribute `attr` for which the value is a space-separated list of words, one of which is the whole word `V`. Example: `[role~="dialog"][aria-describedby|="alert-dialog"]` **Combinators** * **Child Combinator** examples: | `#main-container-id > div.MuiPaper-root.jss1058.jss1046.MuiPaper-elevation0 > div.MuiTabs-root > div > div > a:nth-child(2)[href="/flows/ordering"]` | | | - | - | | `body > div.MuiDrawer-root.jss84.MuiDrawer-modal > div.MuiPaper-root.MuiDrawer-paper.jss85.MuiDrawer-paperAnchorLeft.MuiPaper-elevation16 > div > ul > span:nth-child(1) > a[tabindex="-1"][role="button"][href="/users"]` | | * **Descendant Combinator** examples: | `div#root button p` | | | | - | - | :- | | `#main-container-id button > span p` | | | | `id=["main-container-id"] button div` | | | | `div#menu-segment_id li > div` | | | ### FAQs > **How do Raw Events give better insights on what to track?** > > Raw Events start to collect data once Userpilot is installed on your web app and auto-capturing is enabled, meaning that you would start collecting data on all your users' interactions giving measures on your user behavior and what to track to be used in analytics, content triggering, targeting, and segmentation. > **Are Raw Events supported in Integrations?** > > Yes, after labeling the raw events, you can send them through our HubSpot, Salesforce, and Webhook integrations. We do have plans to add labeled events to our native integrations as well. # Labeled Events (Visually/CSS) Source: https://docs.userpilot.com/data-events/autocapture/visual-labeler Label and track user interactions like clicks, text inputs, and form submissions without code using the Visual Labeler or CSS selectors. > [!NOTE] > > This is only available for Growth & Enterprise plans ### Overview Labeling events allows user interactions in your platform to be tracked without the need for any technical knowledge or development interference. Whether they were Clicks, Text Inputs, or Form Submissions, all can be tracked by using the Visual Labeler, directly adding the related CSS selectors, or even labeling [Raw Events](../autocapture/raw-events) that have been automatically captured. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/autocapture/assets/images/Screenshot2025-02-01112453.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=6898d372378e1b9d481d36d1543f15f0) Labeled events have the feature of collecting historical data that has been captured even before the event has been labeled, meaning that historical data will show for the relevant event once labeled. This data would've been captured by Userpilot from the moment it was installed on your web app and auto-capturing was enabled. > [!NOTE] > > For Labeled Events to collect data, Auto-Caputure has to be enabled under [Settings](https://run.userpilot.io/settings/data-capture-privacy) > > ![](https://mintlify.s3.us-west-1.amazonaws.com/userpilot/data-events/autocapture/assets/images/Screenshot2025-01-31070858.png) ### Use Cases * Identify how much your users are interacting with a certain part of your product * Get an intuitive view of your feature usage * Trigger content based on user behavior * Track users' activity and understand what features they are interacting with * Defining a goal using events to measure the impact of your in-flows on user behavior ### How to Label Events 1. **Visual Labeler:** This option will allow you to directly enter your web app with Userpilot's builder to click on the desired element you want to track. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/autocapture/assets/images/Screenshot2025-02-01112832.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=ca4dd51bf56f60efa8ca07b65eaf0928) This can be done directly under the [Visual Labaler](https://run.userpilot.io/events/visual_labeler) tab, Create Event, or by entering your web app directly and opening Userpilot's Chrome Extension. 2. **CSS Selectors:** Directly input the CSS selectors of the desired element, in addition to the rest of the settings for the related Labeled Event to be created, and show all the relevant data. 3. **Raw Events:** Label any auto-captured raw event under the raw events table, [click here](../autocapture/raw-events) for more info. The Events table in the [Overview tab](https://run.userpilot.io/events/overview) contains all events, including created Labeled Events, and all other event types (tracked events and custom events). ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/autocapture/assets/images/events.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=1115ca53f63642bdcec8af4efa13cb2b) > [!NOTE] > > **Historical data** that's collected for **Labeled Events** (before labeling) can be seen/used in **Event Overview** and **Analytics** (report creation). While creating **Segments** and **Content Triggering** would only consider data that was collected after Labeling the event. [YouTube video player](https://www.youtube.com/embed/93JkMnBz_N4?si=-lLxaHCulPutnciZ) > [!WARNING] > > Changing the configuration/targeting settings of a Labeled Event affects the historical data collected as well. ### FAQs > **Are Labeled Events supported in Integrations?** > > Yes, we send them through our HubSpot, Salesforce, and Webhook integrations. We do have plans on adding labeled events to our native integrations too. > **Does a Labeled Event support collecting data from multiple elements if they're the same?** > > Collecting data from multiple elements that share the same CSS Selectors is indeed supported, this can be enabled in the Userpilot Chrome Extension under "Include all matching selector" > > ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/autocapture/assets/images/Screenshot2025-01-29121029.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=ece2895c323757fca107e5a194237c56) # Events Dashboard Source: https://docs.userpilot.com/data-events/events-dashboard Track user interactions and engagement to make informed product decisions. ### Overview To make data-driven decisions, it is important to understand the impact of your users' interactions and in-app flows on your growth goals and feature adoption. Userpilot allows you to track your users' behavior in your app in real-time and provides multiple ways to track users' interactions (technical and non-technical) by labeling and tracking events within your product, offering a comprehensive view of its usage. > [!NOTE] > > There is a 25 total event limit on Starter plans ### Use Cases * Track users' activity and understand what features they are interacting with * Trigger content based on user behavior * Identify where your users are spending their time on your product * Get an intuitive view of your feature usage * Defining a goal using an event to measure the impact of your in-flows on user behavior ### Event Types All event types can track user interactions that consist of Clicks, Text Inputs, Form Submissions, or even Backend Events. Those events will show in the Events table and have different icons for easier differentiation. * [**Labeled Events**](../data-events/autocapture/visual-labeler): Create an event using Userpilot without technical interference, either using the Visual Labeler or directly adding the CSS Selectors. * [**Tracked Events**](../data-events/tracked-events/tracked-events): Events can be added by calling the tracking method in your code. This option is available for both web and mobile applications, as it allows sending metadata (event properties) and can include backend events. * [**Custom Events**](../data-events/tracked-events/custom-events): A combination of events, with custom events you can select multiple events created previously and merge them into one new event. ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/data-events/assets/images/Screenshot2025-02-04172123.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=3ae9ecf7f4370ea326e69d036bd14dd2) > [!NOTE] > > All statuses doesn't include Archived by default ### Event Dashboards Under the [Events Overview](https://run.userpilot.io/events/overview) tab, all event-related details can be seen with a variety of filters to show event data accordingly, these filters would affect all of the data showing in the dashboards. | Filter | Action | | - | - | | Type | Event Type (all, labeled, tracked, custom, feature tags) | | Platform | Data collected on web, mobile, or offline (sent vie backend) | | Segment | Data related to a certain segment | | Company | Data related to a certain company | | Page | Data collected on a certain page | | Category | Data related to certain event categories | | Time Period | Data collected for a certain time period | Active users/companies, total events occurred, and average occurrences per user can be seen at the top, the percentages next to each would be comparing the current numbers with the past period for the same chosen time period (e.g Last 90 days, would compare the data with the 90 days before that). ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/data-events/assets/images/Screenshot2025-02-04171556.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=1c82b64fa1f78675840e84bf665846dc) > [!NOTE] > > Custom Events are not considered in any total events count, since they would already be included in the related event the custom event was created from. **Overview** The Overview chart would show the total events that occurred, unique users, and companies based on a Daily/Weekly/Monthly period based on your selection and according to the selected time period. Previous Period can be clicked to show you the lines/bars of the period before the selected one for comparison. ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/data-events/assets/images/Screenshot2025-02-04171700.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=bcab0191ed45a9d6c5e49185e4ab7915) Moreover, clicking on a certain date range will show you up to the top 20 events that occurred in that period. ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/data-events/assets/images/Screenshot2025-02-04153423.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=455c6672a5ad6deb64ef243a6940247d) **Event occurrence by User/Company** This chart will show you the most/least 20 events that occurred for your users or companies, you can also custom choose up to 20 events to see their related data. ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/data-events/assets/images/Screenshot2025-02-04181418.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=716c183212b3f8a201bd1b38344c88f3) **Events** The Events table displays all the events that have been created or labeled, the status of the event, and analytics for each event. They can be archived directly from the table, in addition to showing the related event attributes (metadata). ![](https://mintcdn.com/userpilot/SiOsZSwV5TbJs4t6/data-events/assets/images/Screenshot2025-02-05154717.png?fit=max\&auto=format\&n=SiOsZSwV5TbJs4t6\&q=85\&s=a3b971dabaffc19788f13c8f0acf555d) Each event can be entered to see its related data similar to the data displayed on the main Events page. In addition to the User/Company activity table that shows the users that have occurred the event, the number of occurrences, and when it first/last occurred. As the related event's settings can be entered, and references where the event is currently being used. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/assets/images/Screenshot2025-02-05180323.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=7cfdf17a3681d4f6d9ad90a6598f6780) > [!NOTE] > > Tracked Events Metadata can be seen when clicking on the Overview chart. This can be customized under the arrange columns button and up to 2 properties or event attributes can be selected for each event. > > ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/assets/images/Screenshot2025-02-06072532.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=87dcff4d2ac54a3f599b4684a6b3e5f4) [YouTube video player](https://www.youtube.com/embed/TDsnSs0fdSQ?si=JSD0_zjmZZ1JKdPQ) ### FAQs > **Do archived events still collect data?** > > Yes, even archived events would still collect data but it cannot be seen within the options or used anywhere else. > **What would happen if I exceed my 25 event limit and still send tracked events?** > > For events created in your account, you would see a banner preventing you from creating further events, as for tracked events sent through the SDK (via code), they would still be received but in an Archived state. > > ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/assets/images/Screenshot2025-02-06075447.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=77d5faf2846b5bec9f260950f46acbb1) > **How can I delete an event?** > > Events need to be archived first, then there should be a Delete option showing, this applies for labeled and custom events. > > ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/assets/images/Screenshot2025-02-06075320.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=c048177c01fcb5f163521b20b77c566b) # Mobile Screen Auto Capture Source: https://docs.userpilot.com/data-events/mobile-screen-tracking/mobile-screen-auto-capture Automatically capture mobile screens through the Userpilot SDK, review detected screens, and use them in targeting, analytics, reports, and segments. Automatically capture mobile screens through the Userpilot SDK, reduce manual tracking work, and review detected screens before using them in targeting, analytics, reports, and segments. ## Overview Mobile screen auto-capture simplifies how screens are tracked in your app by removing the need for manual setup. After calling the auto-capture function from your code, Userpilot automatically detects and captures screens through the SDK without requiring you to define them one by one. You can configure your screen rules and select the operating system (iOS or Android) where tracking should apply, and Userpilot will handle collecting screen-level analytics seamlessly in the background. Mobile screen auto-capture removes the need to manually call screen tracking APIs, such as: ```swift theme={null} Userpilot.screen("Profile") ``` Once enabled, the SDK automatically detects screen transitions using native lifecycle hooks: * Android: Activity/Fragment lifecycle tracking * iOS: UIViewController lifecycle (method swizzling) Captured screens are sent to Userpilot and appear under **Untagged Screens** for review and tagging. ## Use cases 1. You want to reduce engineering dependency 2. Your app has frequent screen changes 3. You want automatic coverage of all screens 4. You prefer reviewing screens later instead of defining them upfront ## How it works 1. SDK listens to native lifecycle events 2. Screen transitions are detected automatically 3. Screen metadata is generated per platform rules 4. Data is sent to Userpilot backend 5. Screens appear in **Untagged Screens** 6. Tag your mobile screens so you can target and display engagement and feedback content to users based on those screens, as well as use them in reports and segments ## Mobile Screen Tracking: Auto-Capture vs Manual Setup ### **If auto-capture is off** You can still use the existing function `userpilot.screen("screen name")` to manually tag your mobile screens, just select the OS you want. These screens will appear under [Mobile Screens → Untagged Screens](https://run.userpilot.io/mobile-screens/untagged), where you can review and tag any new screens as needed. ![Screenshot 2026 04 29 At 12 14 10 1](https://mintcdn.com/userpilot/MoMixLQUtnFxQdsB/images/Screenshot-2026-04-29-at-12.14.10-1.png?fit=max\&auto=format\&n=MoMixLQUtnFxQdsB\&q=85\&s=53988ecef17f2b9dbcf29ba78187384e) Examples of manually captured screens ```kotlin Android theme={null} // Manifest // Activity class ProfileActivity : AppCompatActivity() { override fun onResume() { super.onResume() Userpilot.screen("Profile") } } ``` ```swift iOS theme={null} class ProfileViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) Userpilot.screen("Profile") } } ``` ### **If auto-capture is on** Your developer needs to enable Auto-Capture in the SDK. Once enabled, the SDK will automatically collect screen data and ignore any manually tracked screen events. ### Android ```kotlin theme={null} { "screen_name": "ProfileActivity", "pathname": "com.sample.ProfileActivity", "parentClassName": "com.sample.MainActivity" } ``` 1. `screen_name` : pulled from `android:label` in the manifest, or the class name as a fallback 2. `pathname` : the full class path 3. `parentClassName` : the parent activity (Fragments only) iOS ```swift theme={null} { "screen_name": "ProfileViewController" } ``` 1. `screen_name` : the `UIViewController` class name After your engineering team installs the **mobile auto-capture script**, Userpilot will start detecting screens automatically. You can then review and tag those screens from **Data → Mobile Screens → Untagged Screens**. Follow these steps: 1. Go to **Data → Mobile Screens** from the left navigation 2. Open the **Untagged Screens** tab 3. Find the screen you want to configure and click **Tag screen** 4. In the **Details** tab, fill in the screen information: 1. **Title:** Add a clear name for the screen 2. **Description:** Explain what this screen represents 3. **Category:** Optionally group the screen under a category 5. Go to the **Targeting** tab 1. Select the operating system where this screen should be tracked, such as **iOS** or **Android** 2. Enable both operating systems if you want to combine the same screen across iOS and Android into one tracked screen 6. Save your changes ![Screenshot 2026 05 10 At 12 51 08 2](https://mintcdn.com/userpilot/vxyIYWWUbQTEuLsN/images/Screenshot-2026-05-10-at-12.51.08-2.png?fit=max\&auto=format\&n=vxyIYWWUbQTEuLsN\&q=85\&s=b1627feed26382bf85f0bc8045b74137) After a screen is tagged, it will appear under the **Overview** table, where you can monitor key metrics such as **unique users**, **unique companies**, **total views**, and **average views per user**. To analyze a specific screen in more detail, click the **screen title** from the **Tagged screens** table. This will open a dedicated dashboard for that screen, where you can review its performance and understand how users are interacting with it. ![Screenshot 2026 05 10 At 12 26 13 3](https://mintcdn.com/userpilot/vxyIYWWUbQTEuLsN/images/Screenshot-2026-05-10-at-12.26.13-3.png?fit=max\&auto=format\&n=vxyIYWWUbQTEuLsN\&q=85\&s=2c437d9ca70b19da334bd1e5109ef0bd) ## **Important** If you’re currently tracking mobile screens manually and want to switch to auto-capture, here’s what you need to know: After enabling auto-capture: 1. Go to **Tagged Screens** under the [**Overview**](https://run.userpilot.io/mobile-screens) tab 2. Review your existing manually tracked screens 3. Compare the naming between your old manual screen names (in Overview) and the new auto-captured screens in **Untagged Screens** #### **Why is this step important** Auto-captured screen names may differ from your existing manually tracked ones. If not handled properly, this can break your targeting, cause engagements or surveys to stop triggering, and split your data across different screen names. #### **Handling name differences (Important)** Auto-captured screen names may not match your old ones. **Android** | Type | Value | | :- | :- | | Manual | Profile | | Auto-capture | ProfileActivity | | Pathname | com.sample.ProfileActivity | **iOS** | Type | Value | | :- | :- | | Manual | Profile | | Auto-capture | ProfileViewController | #### **What you should do** Create a new screen using the auto-captured name, then update your tagging rules: 1. Include both names 2. Use “Match any conditions” ![Screenshot 2026 04 29 At 12 19 33 1](https://mintcdn.com/userpilot/MoMixLQUtnFxQdsB/images/Screenshot-2026-04-29-at-12.19.33-1.png?fit=max\&auto=format\&n=MoMixLQUtnFxQdsB\&q=85\&s=52dae35cbd9337bb4d048d79be35b293) # Track Mobile Screens Manually Source: https://docs.userpilot.com/data-events/mobile-screen-tracking/track-mobile-screens-manually Manually track mobile screens with the Userpilot SDK to record screen views, trigger experiences, and use screen data in analytics, reports, and segments. Learn how to manually track screens in your mobile app using Userpilot’s SDK. Manual screen tracking helps you record screen views, trigger mobile experiences, and use screen data in analytics, reports, and segmentation. ## Overview A **mobile screen** is the mobile equivalent of a page in a web app. For example, this could be a Home screen, Profile screen, Settings screen, Checkout screen, or Subscription screen. In Userpilot, tracking screens is required to unlock core mobile engagement and analytics capabilities. When a user navigates to a tracked screen, Userpilot records the screen view, triggers any eligible in-app experiences, and attributes later events to the most recently tracked screen for better analytics context. Manual screen tracking means your developers define exactly when a screen should be tracked and what name should be sent to Userpilot. This gives your team full control over screen naming, tracking logic, and consistency across platforms. To set it up, choose the relevant mobile framework in this [article](https://docs.userpilot.com/developer/installation/mobile/overview) and follow the screen tracking instructions. ## Use cases 1. **Trigger experiences on specific screens:** Show mobile experiences only when users reach a specific screen, such as showing onboarding guidance on the Home screen or an upgrade prompt on the Subscription screen. 2. **Build better reports and segments:** Use screen views to understand where users go inside your mobile app, then create reports or segments based on those visits. 3. **Support cross-platform tracking consistency:** If your app is available on both iOS and Android, manual tracking helps you use the same screen names across both platforms. 4. **Control exactly when a screen is tracked:** Manual tracking gives your developers control over when the screen event is sent, which is useful for complex navigation flows or screens that should only be tracked after specific conditions are met. ## How to track mobile screens manually After installing the **Userpilot mobile SDK**, you can access mobile screen tracking from the left navigation by going to [**Data → Mobile Screens**](https://run.userpilot.io/mobile-screens). From there, you will be able to view and manage the screens tracked from your mobile app. ![Screenshot 2026 05 10 At 12 59 44 1](https://mintcdn.com/userpilot/vxyIYWWUbQTEuLsN/images/Screenshot-2026-05-10-at-12.59.44-1.png?fit=max\&auto=format\&n=vxyIYWWUbQTEuLsN\&q=85\&s=55b1af5ed2c14cf6da2dbcde9d483764) Click the **Create screen** at the top-right side of the page. Then, fill in the screen details: 1. **Name:** Add a clear screen name, such as `Profile`, `Checkout`, or `Settings`. 2. **Screen Key:** This is the key that will be used in the tracking snippet. 3. **Description:** Add a short description to explain what this screen represents or what you want to track. 4. **Category:** Optionally group the screen under a category to keep your screens organized. For example, if the user opens the Profile screen, you can send: ```kotlin theme={null} userpilot.screen("Profile") ``` or: ```swift theme={null} userpilot.screen("Profile") ``` After clicking **Create**, copy the generated snippet and share it with your engineering team. Your developers should add this snippet to the app code so it is called when the user navigates to that specific screen. ![Screenshot 2026 05 10 At 12 20 50 1](https://mintcdn.com/userpilot/vxyIYWWUbQTEuLsN/images/Screenshot-2026-05-10-at-12.20.50-1.png?fit=max\&auto=format\&n=vxyIYWWUbQTEuLsN\&q=85\&s=356358ae6731961bbbe6c46b9703e1f8) ## Android example For Android, call `screen` when the screen becomes visible to the user. ```kotlin theme={null} class ProfileActivity : AppCompatActivity() { override fun onResume() { super.onResume() userpilot.screen("Profile") } } ``` In this example, Userpilot tracks a screen view called `Profile` when the `ProfileActivity` resumes. ## iOS example For iOS, call `screen` when the view appears. ```swift theme={null} class ProfileViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) userpilot.screen("Profile") } } ``` In this example, Userpilot tracks a screen view called `Profile` when the `ProfileViewController` appears. ## Recommended naming conventions Use screen names that are easy to understand in Userpilot. For example: | Recommended | Avoid | | :- | :- | | `Home` | `HomeActivityV2` | | `Profile` | `ProfileViewController` | | `Checkout` | `CheckoutFragmentFinal` | | `Subscription` | `SubScreenNew` | Once your engineering team adds the tracking snippet to your app code, Userpilot will start collecting screen data when users visit that screen. You can view the screen’s performance from **Data → Mobile Screens,** where tracked screens will appear in the **Untagged screens** table. From there, you can tag the screens and monitor key metrics such as **unique users**, **unique companies**, **total views**, and **average views per user**. To analyze a specific screen in more detail, click the **screen title** in the table. This will open a dedicated dashboard for that screen, where you can review its performance and understand how users are interacting with it. ![Screenshot 2026 05 10 At 12 59 44 1](https://mintcdn.com/userpilot/vxyIYWWUbQTEuLsN/images/Screenshot-2026-05-10-at-12.59.44-1.png?fit=max\&auto=format\&n=vxyIYWWUbQTEuLsN\&q=85\&s=55b1af5ed2c14cf6da2dbcde9d483764) ![Screenshot 2026 05 10 At 12 26 13 3](https://mintcdn.com/userpilot/vxyIYWWUbQTEuLsN/images/Screenshot-2026-05-10-at-12.26.13-3.png?fit=max\&auto=format\&n=vxyIYWWUbQTEuLsN\&q=85\&s=2c437d9ca70b19da334bd1e5109ef0bd) # Page Tracking Source: https://docs.userpilot.com/data-events/page-tracking/pages-dashboard Track page visits and uncover insights into user behaviour throughout your application. Tagging [Pages](https://run.userpilot.io/pages/overview) enables you to monitor the page views within your application, allowing you to keep track of how many users are accessing each page and use this information to trigger personalized experiences based on behaviour. ![Pages 1.png](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/page-tracking/assets/images/image-1.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=e65df1cd69276c4abfed1d7017142880) ## Use Cases * **Monitor in-app navigation:** Track how users move through your app in real-time. Use Page data in reports to identify where users are dropping off, looping, or engaging the most. * **Detect spikes & drops in activity:** Stay ahead of potential issues by monitoring unexpected behaviours in page activity. Proactively catch bugs or UX problems—before users report them. * **Target users by page visits:** Create personalized experiences by targeting users based on the pages they've visited—or ignored. Trigger engagement content when users land on specific pages, or re-engage those who haven’t. * **Merge similar pages for simplicity:** Avoid the clutter of tracking too many page variants. Get big picture data by merging pages with similar URLs or structures. * **Measure engagement depth:** Use **Total time on page** and **Avg. time on page** to see which pages hold users' attention and which ones users click away from quickly — helping you distinguish a genuinely popular page from one that's just frequently (but briefly) visited. ## Getting started with Pages You can tag a Page using one of two methods: 1. Click ‘Create Page’ and manually input the desired settings ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/page-tracking/assets/images/image-3.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=fae414d9edd7133047d23d9a5b289510) 1. Choose to tag a page from the ‘[Untagged pages](https://run.userpilot.io/pages/untagged)’. From here, you can see a list of all URLs where Userpilot has been installed which have not yet been tagged, along with the page views of each over the last 90 days. A URL with a query string is listed with each parameter value replaced by `*`, and a URL stays listed while some of its views match no Page, for example when an **Equals** condition doesn't cover its query string. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/page-tracking/assets/images/image-2.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=427e2d51f8a0bb4ab671103cab9f501f) After naming your page and providing a description, you’ll be directed to the ‘Targeting’ tab to define the specifics of the Page you’d like to tag. **Domains:** Narrow the page targeting by specifying the relevant domains. 1. **All domains:** Capture the activity of this page on any domain where the Userpilot script is installed 2. **Specific domains:** Capture the activity only in cases where the domain matches / doesn’t match a specific string. In a domain that must match, use an asterisk (\*) for a part that changes; it stands for one or more characters, as in `*.example.com`. With only **doesn't match** domains, a Page counts views on every domain except those. **Path:** Define the URL path of the page that you’d like to track. Multiple conditions can be added with the option for specifying if the pages targeted must match all conditions or any condition. 1. **Match any conditions:** Page must match at least one condition to be included. 2. **Match all conditions:** Page must match all conditions to be included. To leave pages out, add **Doesn't contain** conditions and choose **Match all conditions**. Under **Match any conditions**, a **Doesn't contain** condition matches almost every URL, so the Page counts nearly every view. > [!NOTE] > > If a page URL contains dynamic elements, use the **Matches regex** option and replace the dynamic part of the URL with an asterisk (\*) as shown below. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/page-tracking/assets/images/image-4.gif?s=197c966017c1a31869d979c519ddd108) ## How path conditions match When a Page counts views, each path condition compares its text with the part of the URL after the domain: the path, the query string, and the fragment. For `https://app.example.com/reports/12?tab=users#summary`, a condition sees `/reports/12?tab=users#summary`. On a non-default port, the text starts with the port, as in `:8080/reports/12`. If your installation sets the `pageview` option of [`userpilotSettings`](/developer/installation/SDK-APIs#userpilotsettings-object) to `pathonly` or `withhash`, conditions see only the path, or only the path and the fragment. Matching ignores case. Conditions see the URL as the browser encodes it, so a space appears as `%20`. | Condition | Counts a view when the URL | Example | | - | - | - | | **Equals** | is the condition's text, with or without a trailing `/` | `/settings` matches `/settings` and `/settings/` | | **Starts with** | begins with the condition's text | `/reports` matches `/reports/12` and `/reportsxyz` | | **Contains** | includes the condition's text anywhere | `tab=users` matches `/reports/12?tab=users` | | **Ends with** | ends with the condition's text, with or without a trailing `/` | `/edit` matches `/flows/12/edit` and `/flows/12/edit/` | | **Matches regex** | matches the pattern, as described below | `/flows/*/settings` matches `/flows/12/settings` | Each condition except **Matches regex** has an opposite (**Doesn't equal**, **Doesn't start with**, **Doesn't contain**, and **Doesn't end with**) that counts the views the condition doesn't. Because conditions see the query string and the fragment, **Equals** `/dashboard` doesn't match `/dashboard?utm_source=email` or `/dashboard#state=1`. To match a page with any query string or fragment, use **Starts with** `/dashboard/`. ### Trailing and leading slashes In **Equals**, **Starts with**, and **Ends with**, a trailing `/` in a condition also matches the page without it: * **Equals** `/settings/` matches `/settings` and `/settings/`. * **Starts with** `/reports/` matches `/reports`, `/reports` with a query string or fragment, and every page under `/reports/`, but not `/reportsxyz`. * **Ends with** `/edit/` matches `/flows/12/edit` and `/flows/12/edit/`. **Equals** and **Starts with** read a condition that doesn't start with `/` as if it did, so `settings` matches `/settings`. A condition that starts with a port, such as `:8080/reports`, is read as written. ### Matches regex Without regular expression syntax, a **Matches regex** condition reads `*` as any text, including none, and `?` as a literal question mark. For example, `/flows/*/settings` matches `/flows/12/settings`, `/users/*` matches `/users/` and `/users/42`, and `/users?id=*` matches `/users?id=42`. Other characters, such as `.` and `+`, keep their regular expression meaning, and the pattern can match anywhere in the URL. `*` also spans `/`, so `/flows/*/settings` also matches `/flows/1/2/settings`. To match exactly one segment and nothing after it, use a regular expression such as `^/flows/[^/]+/settings$`. When the condition uses regular expression syntax (`^`, `$`, `\`, `|`, `(`, `[`, `.*`, or `.+`) and nothing beyond characters, `.`, escaped characters such as `\.` and `\d`, character classes such as `[0-9]`, groups, `|`, `^`, `$`, and the `*`, `+`, and `?` quantifiers, it also matches as written, ignoring case. There, `*`, `+`, and `?` are quantifiers, so `^/home/?$` matches `/home` and `/home/`. A condition that uses other syntax, such as `{4}` or a lookahead, isn't matched as written, so `^/orders/\d{4}/?$` doesn't match `/orders/2024`. Patterns run on the RE2 engine, and a condition with a backreference, such as `\1`, can't be saved. ## Page analytics Clicking into a Tagged Page will provide useful insights into how the Page is performing, including: * **User views:** The number of unique users that have visited the page within the time frame specified. * **Company views:** The number of unique companies that have visited the page within the time frame specified. * **Total views:** The total number of time the page has been viewed within the specified time period. * **Avg. views per users:** The average number of times a user has viewed the page within the specified time period (Total views divided by User views). * **Total time on page:** The cumulative amount of time all users have spent on the page within the selected filters. * **Avg. time per page view:** The average amount of time spent on the page per view within the selected filters (Total time on page ÷ Total views). > [!NOTE] > > Time on page data started being collected on **July 5, 2026**. Periods that include dates before this will show partial data only. ## FAQs > **How are Page views calculated?** > > A page view is counted when a user navigates to a page that matches the path requirements outlined in the Page settings. A new page view will be recorded in the case that: > > * The user’s session ends (after 30 minutes of inactivity) and the user then becomes active again. > * The user refreshes the page. # Select The Right Element Source: https://docs.userpilot.com/data-events/select-eleme-events Detecting the right element in Userpilot is crucial for providing a personalized user experience through product tours, tooltips, and flow steps. ### Overview Detecting the right element in Userpilot is crucial for providing a personalized user experience through product tours, tooltips, and flow steps. Proper element selection ensures that the right features are highlighted and tracked for user engagement. The platform allows automatic detection of elements, with an option for manual adjustments when needed, ensuring accurate implementation of onboarding flows. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/assets/images/file-mKA8VMI1UD.gif?s=8760e1c21c7e86bf9ca6f3ae8d1b6a93) ### Use Cases * Highlighting specific elements on a web application interface. * Continuing a flow even when the element is not visible on the page. * Modifying element detection to ensure proper onboarding functionality. ### Main Section #### What is a CSS Selector? A **CSS selector** is a pattern used to select and target HTML elements on a web page. These selectors allow Userpilot to accurately identify and interact with page elements. CSS selectors can target elements based on their ID, class, type, or other attributes. Inspecting the element with your browser’s "**Inspect**" tool can help you find the CSS selectors used by a particular element. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/assets/images/file-eTgnuJBF8a.gif?s=57e6d99f46d3a5fec8a1c3df1914233d) #### How Userpilot Detects Elements Automatically When adding a new step in Userpilot, the builder automatically detects elements and selects the best CSS selector for them. It visually highlights the element on the page with a rectangle around the target. Based on this, Userpilot determines the optimal placement for UI patterns, such as tooltips or driven actions, on your app. #### When to Manually Adjust CSS Selectors Manual adjustments should only be made when automatic detection doesn’t work as expected, which occurs in rare cases. These include situations where: * The element is not detected properly, possibly due to dynamic content or complex page structures. * There are too many similar elements, causing the wrong one to be selected. #### How to Adjust CSS Selectors Manually 1. Enable the **'Manual Detection'** option from the **Individual** menu. 2. Once enabled, you can edit the CSS selectors for greater precision. 3. To ensure that multiple similar elements are handled correctly, consider adjusting the **element order** or adding a text string filter to further specify the element. ![Screen Recording Element Detection](https://mintcdn.com/userpilot/quWc8y2bjqjIq0AN/images/screen-recording-element-detection.gif?s=9ca77f2315ca9a5e967785af989cf9bd) If the selected element contains dynamic selectors that may change with each page load or interaction, Userpilot provides an [**Exclude List**](https://docs.userpilot.com/configure/exclude-lists/dynamic-attributes) feature to help mitigate this issue. By working with your development team, you can exclude dynamic selectors from being added when Userpilot detects the element. This ensures that once the dynamic selectors are added to the exclude list, any future element selections containing those selectors will be automatically ignored by Userpilot. > [!NOTE] > > Elements previously selected with dynamic selectors won't be automatically excluded. You'll need to manually reselect them after adding the CSS selectors to the Exclude List to ensure proper tracking. ### FAQs > **How do I deal with multiple similar elements that could be mistakenly selected?** > > If you encounter this issue, manually adjust the CSS selectors by specifying the correct element using its unique attributes or text content. > **Why do I need to reselect elements after adding selectors to the Exclude List?** > > Elements previously selected with dynamic selectors won't be automatically excluded. You’ll need to reselect those elements after updating the Exclude List to ensure they are properly tracked and targeted moving forward. # Custom Events Source: https://docs.userpilot.com/data-events/tracked-events/custom-events Custom Events enable you to group multiple events into a single category, providing more accurate and comprehensive analytics. ### Overview By consolidating events, you can track key actions more effectively, measure user engagement, and gain deeper insights into your app's performance. This approach enhances your ability to monitor specific workflows and feature usage over time, ultimately improving data-driven decision-making. ### Use cases * Seamlessly track the completion of important processes by consolidating multiple events into a single custom event, eliminating the need to check individual events to determine the completion rate. * Use custom events to trigger content when any grouped event occurs, simplifying audience settings by removing the need for multiple conditions. ### How to create Custom Events You can create custom events in two ways: 1. Click on the Create Event button on the [Events](https://run.userpilot.io/events/overview) page, select "Create custom event", and choose the events you wish to group from the dropdown list. ![Image](https://mintcdn.com/userpilot/LBLAtBIdDQqJEP96/images/image-19.png?fit=max\&auto=format\&n=LBLAtBIdDQqJEP96\&q=85\&s=edc74da2a5f6cea31c742d398b54d8eb) 2. Select the desired events from the overview page and then click the "Create Custom Event" button. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/tracked-events/assets/images/Screenshot2025-02-02at3.56.42PM.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=0d925bc5424387d385ef0b456dd41241) > [!WARNING] > > Once a custom event is created, it cannot be modified. ### FAQs > **Are custom events supported in integrations?** > > No, custom events cannot be sent to a third-party platform via integrations. > **Can I group multiple labeled events and create a custom event?** > > Currently, the ability to use labeled events for creating custom events is not available. However, our dev team is actively working on providing the support for this ability in the future. # Tracked Events Source: https://docs.userpilot.com/data-events/tracked-events/tracked-events Sending tracked events and passing them to Userpilot is an easy and straightforward approach by calling the tracking method. ### Overview Sending tracked events and passing them to Userpilot is an easy and straightforward approach by calling the tracking method. This is the technical approach for sending tracked events from both Web and Mobile applications, which could contain metadata (event properties). We also support creating tracked events through Userpilot content by clicking on a certain button, and directly creating the tracked events from the events dashboard. ### Use Cases * Trigger content based on user behavior * Analyze visitors' behavior and activity on your web app * Identify how much users are interacting with a certain element of your product * Defining a goal using events to measure the impact of your in-flows on user behavior * Enable business teams to make efficient decisions about increasing the effectiveness of their digital marketing activities ### Tracked Events Function Sending tracked events and metadata to Userpilot by calling the `userpilot.track()` function. > [!CAUTION] > > You can have up to **10,000 unique event names** and **512 KB** total size per application. If you're approaching or hitting these limits, it's a sign to revisit how you're sending events. > > **Don't** use `userpilot.track()` for every click or input with dynamic IDs in the name (e.g. `button-click-976728`). For those, use [auto-capture and labeling](/data-events/autocapture/visual-labeler) instead, you get one stable event per interaction type without hitting the limit. > > **Do** reserve tracked events for things only your app can tell product teams about (payments, DB changes, key state changes) and put the detail in **metadata**, e.g. `userpilot.track("List toggle", { listId: "976728" })` instead of a new event name per ID eg. `list-toggle-976728`. ```javascript Web theme={null} //tracking your events using the Track method //Specify more information about your event (metadata) //Another example ``` ```javascript iOS theme={null} userpilot.track("Added to Cart", properties: ["itemId": "sku_456", "price": 29.99]) ``` ```javascript Android theme={null} userpilot.track("Added to Cart", mapOf("itemId" to "sku_456", "price" to 29.99)) ``` ### Button Tracked Events Userpilot can track events through button clicks by enabling the Track event function for any button within the Userpilot content. Once the user clicks on the button after publishing the related content, data is sent to your Userpilot account showing the tracked event data. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/tracked-events/assets/images/Screenshot2025-02-21182750.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=d9f97875fef022fe0aa1d69669da97e8) > [!NOTE] > > The event needs to occur at least once (related button clicked/triggered live) to show in your dashboard ### Tracked Events Dashboard Once any Tracked Event has been sent to Userpilot it should be found in the [Events Dashboard](https://run.userpilot.io/events/overview) under the Events table. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/tracked-events/assets/images/Screenshot2025-02-21192759.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=92799ece7f1543e2ee7f58a4ac06e9f5) Entering an event will show the related analytics, in addition to the Overview chart that can be clicked to see the related event occurrences in a certain time period and the event metadata or any properties. In addition to the User/Company activity table that shows the total occurrences, and first/last occurrence of the event. There are also other ways to create tracked events such as directly creating them from the Events dashboard by adding the name and event key to track the event in your web app, then sharing it with your dev team to be called upon certain actions by your users. Sending tracked events through the [HTTP Rest API](https://docs.userpilot.com/api-references/real-time/track-event) and the [Bulk Data Import API](https://docs.userpilot.com/api-references/import/event-schema#4-track-event) is also supported. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-events/tracked-events/assets/images/Screenshot2025-02-21194350.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=638e9750372d37b83340ec24f87e59da) ### FAQs > **How long does it take for Tracked Event to show in Userpilot?** > > Tracked events would show right away, they're updated in real time. > **Can Tracked Events be deleted?** > > No, there is no current way to fully delete a tracked event, but they can be archived and won't show anywhere in the app once done till they're unarchived again. # Event Schema Source: https://docs.userpilot.com/data-sync/event-schema The event schema is the structure of the data that is exported from Userpilot Data Sync. ## Event Structure | **Attribute** | **Type** | **Description** | | - | - | - | | `app_token` | String | Unique identifier for the app. | | `event_type` | String | Type of event (`identify_user`, `identify_company`, `interaction`, `track`, `track_feature`, `page_view`, `session_start`). | | `event_name` | String | Name of the event. For some types, this is empty or contains details (see below). | | `user_id` | String | Unique identifier for the user (for `identify_user` events). | | `company_id` | String | Unique identifier for the company (for `identify_company` events). | | `source` | String | Source of the event (`web-client`, `backend-http`, `backend-hubspot`, `backend-salesforce`, `backend-segment`). | | `inserted_at` | DateTime (UTC) | Timestamp of the event (microsecond precision). | | `internal_event_id` | String | Unique identifier calculated using xxHash64 of the composite key (`app_token`, `event_type`, `event_name`, `user_id`, `company_id`, `inserted_at`). | | `hostname` | String | Hostname where the event took place. | | `pathname` | String | Page path where the event occurred. | | `screen_width` | Integer | User's screen width (px). | | `screen_height` | Integer | User's screen height (px). | | `device_type` | String | Device type (`desktop`, `mobile`, etc.). | | `user_agent` | String | Browser's user agent string. | | `operating_system` | String | User's operating system. | | `browser` | String | Browser name (e.g., `Chrome`). | | `browser_language` | String | Browser language setting. | | `session` | String | Session number (for SDK events, auto-updated for `web-client`). | | `country_code` | String | 2-letter country code. | | `metadata` | Object | The attributes object varies depending on the `event_type` (see below). | ### Internal Event ID The `internal_event_id` is a unique identifier for each event, calculated using the xxHash64 algorithm on a composite key consisting of the following fields: * `app_token` * `event_type` * `event_name` * `user_id` * `company_id` * `inserted_at` This identifier provides a deterministic way to uniquely identify events based on their core attributes, which is useful for deduplication and event tracking across systems. ### Event Name & Type The `event_name` field tracks the name of the event and varies depending on the `event_type`: * **`For identify_user, identify_company, session_start, and page_view events:`** This field will be empty. * **`For track events:`** The value will represent the name of the event being passed programmatically or through the button-tracking feature. * **`For track_feature events:`** The value will contain the `id` of the tracked feature tag. * **`For interaction events:`** * The format is `{entity_type}:{entity_id}:{entity_interaction_status}` * Example: `checklist:2:SEEN`, `experience:448:DISMISSED`, `resource_center:1:ENGAGED`, `experiment:9:SEEN`. > [!NOTE] > > When a flow is part of an experiment, both `experiment:*` and `experience:*` events are generated to track experiment participation and flow interaction respectively. *** ### Source The `source` field indicates where the event was captured. The default value is `web-client`, but it can vary depending on the source of the event: * **web-client**: Captured through the web SDK. * **backend-http**: Captured via HTTP endpoints. * **backend-hubspot**: Captured through the HubSpot integration. * **backend-salesforce**: Captured through the Salesforce integration. * **backend-segment**: Captured through SegmentIO integration. *** ### Attributes Schema The `metadata` field varies depending on the event type: * **`For identify_user, identify_company, and track events:`** This field will contain custom attributes passed during the event tracking. * **`For page_view, session_start and track_feature events:`** This field will be an empty object. * **`For interaction events:`** Attributes vary based on the engagement entity type and provide specific interaction details (see below). *** ## Interaction Attributes Below are common interaction events and the relevant attributes associated with each. ### Surveys (`survey_module` events) | **Attribute** | **Type** | **Description** | | - | - | - | | `feedback` | String | Feedback provided by the user, often a unique identifier or actual feedback text. | | `interaction_id` | String | Unique ID of the survey module (e.g., `survey_module_49`). | | `is_dismissed` | String | Indicates whether the survey was dismissed by the user (`1` for yes, `0` for no). | | `parent_id` | String | The ID of the parent survey. | | `status` | String | Current status of the survey interaction (e.g., `COMPLETED`, `DISMISSED`). | | `survey_question` | String | The question presented to the user. | | `variant` | String | Survey type (e.g., `multiple_choice`, `open_text`). | **Example:** ```json theme={null} { "event_name": "survey_module:49:COMPLETED", "attributes": { "feedback": "318b72c9-b1ad-4bf6-854b-26d32ac9d281", "interaction_id": "survey_module_49", "is_dismissed": "0", "parent_id": "survey_13", "status": "COMPLETED", "survey_question": "", "variant": "multiple_choice" } } ``` ### Checklists (`checklist` and `checklist_task` events) | **Attribute** | **Type** | **Description** | | - | - | - | | `interaction_id` | String | Unique ID of the checklist or checklist task (e.g., `checklist_10`). | | `is_dismissed` | String | Indicates whether the checklist or task was dismissed by the user (`1` for yes, `0` for no). | | `parent_id` | String | The ID of the parent checklist, if applicable. | | `status` | String | Status of the checklist interaction (e.g., `SEEN`, `ENGAGED`). | **Example:** ```json theme={null} { "event_name": "checklist:10:SEEN", "attributes": { "interaction_id": "checklist_10", "is_dismissed": "", "parent_id": "", "status": "SEEN" } } ``` ### Resource Center and Articles (`resource_center`, `resource_center_article` events) | **Attribute** | **Type** | **Description** | | - | - | - | | `article_title` | String | Title of the article the user engaged with. | | `article_url` | String | URL of the article. | | `interaction_id` | String | Unique ID for the resource center interaction or article (e.g., `resource_center_article_6FAA69`). | | `is_dismissed` | String | Indicates if the resource center or article interaction was dismissed. | | `parent_id` | String | The ID of the parent resource center entity (if applicable). | | `status` | String | Status of the interaction (e.g., `ENGAGED`). | **Example:** ```json theme={null} { "event_name": "resource_center_article:6FAA69EBAFEEDF5E641ACF6D52847FC98AC44437591FC39D8BE67BDCF4570023:ENGAGED", "attributes": { "article_title": "How to Delete a Flow - Userpilot Knowledge Base", "article_url": "https://docs.userpilot.com/", "interaction_id": "resource_center_article_6FAA69EBAFEEDF5E641ACF6D52847FC98AC44437591FC39D8BE67BDCF4570023", "is_dismissed": "0", "parent_id": "resource_center_1", "status": "ENGAGED" } } ``` ### NPS Events | **Attribute** | **Type** | **Description** | | - | - | - | | `feedback` | String | Feedback provided by the user (can be text or a unique ID). | | `follow_up_question` | String | A follow-up question posed to the user after providing the NPS score. | | `interaction_id` | String | Unique ID for the NPS interaction (e.g., `nps_1`). | | `score` | String | NPS score given by the user (typically between 0 and 10). | | `status` | String | Status of the NPS interaction (e.g., `SUBMITTED_FEEDBACK`, `SUBMITTED_SCORE`). | | `survey_question` | String | The specific NPS question posed to the user. | | `submission_id` | String | Unique identifier for the NPS submission. | **Example:** ```json theme={null} { "event_name": "nps:1:SUBMITTED_FEEDBACK", "attributes": { "feedback": "Great tool, highly recommended!", "follow_up_question": "Is there anything else we can improve?", "interaction_id": "nps_1", "score": "9", "status": "COMPLETED", "submission_id": "2024-05-02", "survey_question": "How likely is it that you would recommend us to a friend or colleague?" } } ``` ### Flow and Flow Steps (`experience`, `experience_step` events) | **Attribute** | **Type** | **Description** | | - | - | - | | `interaction_id` | String | Unique ID of the experience or experience step (e.g., `experience_493`). | | `is_dismissed` | String | Indicates if the experience was dismissed (`1` for yes, `0` for no). | | `status` | String | The status of the experience (e.g., `COMPLETED`, `DISMISSED`). | | `total_dismissed` | String | The total number of times the experience was dismissed by the user. | **Example:** ```json theme={null} { "event_name": "experience:493:DISMISSED", "attributes": { "interaction_id": "experience_493", "is_dismissed": "1", "status": "DISMISSED", "total_dismissed": "1" } } ``` > [!NOTE] > > **Flows in Experiments:** When a flow is part of a test experiment, the system generates BOTH `experiment:*` and `experience:*` events: > > * `experiment:*` events track experiment participation and variant assignment > * `experience:*` events track flow interaction ### Experiments (`experiment` events) When flows are used testing experiments, Userpilot tracks experiment participation through dedicated experiment events. These events capture which variant (test group) a user was assigned to. | **Attribute** | **Type** | **Description** | | - | - | - | | `interaction_id` | String | Unique ID of the experiment (e.g., `experiment_9`). | | `is_dismissed` | String | Indicates whether the experiment interaction was dismissed by the user (`1` for yes, `0` for no). | | `never_show_again` | String | Indicates if the user chose to never see this again (`1` for yes, `0` for no). | | `status` | String | Current status of the experiment interaction (e.g., `SEEN`, `COMPLETED`, `DISMISSED`). | | `total_dismissed` | String | The total number of times the experiment was dismissed by the user. | | `variant` | String | The experiment group the user was assigned to. Contains the variant name (A, B, or C). | **Variant Values:** The `variant` attribute indicates which test group the user was assigned to. Possible values are "A", "B", or "C" depending on the experiment type (Controlled A/B Test, Head-to-Head A/B Test, or Controlled Multivariate Test). For more details on experiment types and how variants are assigned, see the [Experiments documentation](https://docs.userpilot.com/in-app-engagement/flows/use-cases/AB-testing). **Example:** ```json theme={null} { "event_name": "experiment:9:SEEN", "attributes": { "interaction_id": "experiment_9", "is_dismissed": "0", "never_show_again": "0", "status": "SEEN", "total_dismissed": "0", "variant": "B" } } ``` ### Workflows Workflow-related events are exported as `interaction` events, so they're included in your exported data automatically. Two types are captured: * **Workflow-level events** — track the overall workflow, e.g. `workflow:16:SEEN` and `workflow:16:COMPLETED`. * **Workflow node events** — track individual steps within a workflow, e.g. `experience_step:vagqc:SEEN` and `experience_step:vagqc:COMPLETED`. The workflow node events have the following meanings: * `SEEN` indicates that the user has completed the previous node and is ready to engage with the next node. * `COMPLETED` indicates that the user has completed the current node, either by interacting with it or because a configured delay for that node has elapsed. Additionally, content that is part of a workflow (for example, Flows or Surveys) follows the normal content interaction model. These events are exported the same way as if the content were used outside of a workflow. **Example:** ```json theme={null} { "event_name": "workflow:16:SEEN", "attributes": { "interaction_id": "workflow_16", "is_dismissed": "0", "never_show_again": "0", "status": "SEEN", "total_completed": "0", "total_dismissed": "0", "total_engaged": "0", "total_seen": "0" } } ``` ```json theme={null} { "event_name": "experience_step:vagqc:SEEN", "attributes": { "interaction_id": "experience_step_vagqc", "is_dismissed": "0", "never_show_again": "0", "parent_id": "experience_67", "status": "SEEN", "total_completed": "0", "total_dismissed": "0", "total_engaged": "0", "total_seen": "0" } } ``` > [!WARNING] > > **Schema Evolution** > > Userpilot may add new properties or event types over time. Your data processing pipelines should be designed to handle such changes gracefully (e.g., by not breaking if new, unexpected fields appear). # Frequently Asked Questions Source: https://docs.userpilot.com/data-sync/faq Userpilot Data Sync is an add-on feature and may have an associated cost depending on your Userpilot subscription plan. > **What are the costs associated with Userpilot Data Sync?** > > Userpilot Data Sync is an add-on feature and may have an associated cost > depending on your Userpilot subscription plan. Please contact your Userpilot > Account Manager or our sales team for specific pricing details. Additionally, > remember that you will incur costs from your cloud storage provider (Amazon > S3, Google Cloud Storage) for storing the exported data and for any data > transfer or API request charges they may levy. > **How far back can historical data be synced when I first set up Data Sync?** > > We support backfill up to 365 days and sync back on a weekly basis up to 365 > days from the current sync day. > **What is the typical latency for data to appear in my storage after an event occurs?** > > The latency depends on your configured sync frequency. For daily syncs, data > for a given day will be exported after that day (UTC) concludes; specifically, > for the daily sync, we do the sync daily at 3 AM UTC. There is also the > processing time for the sync job itself. > **Can I change my storage destination or configuration details later?** > > You can't change the destination configurations (like the S3 bucket, or GCS > project details) after the initial setup of a Data Sync job. However, you can > modify other export job details such as the job title, sync period/frequency, > data filters, and export formats through the Data Sync settings page in > Userpilot. > **How is data security and privacy handled during the sync process?** > > Userpilot uses secure, encrypted connections (HTTPS) to transfer data to your > cloud storage provider. You are responsible for securing your cloud storage > credentials and the storage bucket/container itself according to best > practices. Userpilot only requires write access to the specified location. We > recommend using dedicated credentials with minimal privileges for the Data > Sync feature. > **What happens if my Userpilot subscription changes or if I disable Data Sync?** > > If your subscription plan changes and no longer includes Data Sync, or if you > manually disable the feature, Userpilot will stop exporting new data to your > storage. The data already exported will remain in your storage under your > control. > **Which Userpilot events are included in the Data Sync?** > > Data Sync typically exports all raw event data tracked by your Userpilot > installation, including tracked events, labeled events, flow interactions, > checklist engagement, NPS responses, survey submissions, etc. For a detailed > list and schemas, please refer to the "Data Sync: Event Schema Reference" > document. > **In what time zone is the `inserted_at` field in the exported data?** > > Event timestamps in the raw data export are typically in Coordinated Universal > Time (UTC). You will need to convert these to your desired local time zone > during your data processing or analysis if required. > **Who should I contact for support if I have issues with Data Sync?** > > If you encounter any problems or have questions not covered in this > documentation, please reach out to the Userpilot support team through your > usual support channels (e.g., in-app chat, email > [support@userpilot.com](mailto:support@userpilot.com)). Provide as much detail > as possible about your issue for a faster resolution. # Data sync overview Source: https://docs.userpilot.com/data-sync/overview Userpilot Data Sync is designed to provide you with direct, granular access to your raw event data, empowering you to integrate Userpilot insights seamlessly into your broader data strategy. Userpilot Data Sync is designed to provide you with direct, granular access to your raw event data, empowering you to integrate Userpilot insights seamlessly into your broader data strategy. This section offers an overview of what Data Sync is, the key benefits it offers, and common ways our customers are using it to drive their business forward. ![](https://mintcdn.com/userpilot/QymSwMjgqLFdqNO_/data-sync/assets/images/2025-06-22-data-sync-integrations-diagram.png?fit=max\&auto=format\&n=QymSwMjgqLFdqNO_\&q=85\&s=dad8414c3ecd9b2be743e9c8791e2f12) ## What is Userpilot Data Sync? Userpilot Data Sync is an add-on that automatically exports your raw Userpilot event stream into your own cloud storage (data lake), from where your own pipeline can feed it into a warehouse or BI tool. It breaks down silos by delivering unfiltered, granular event data so you can run custom analyses, build bespoke reports, and integrate with external systems, all on a reliable, scalable pipeline designed for businesses of any size. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-sync/assets/images/flow.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=8b33b33bbb1cd343f83306ba61bc0ad9) ## Why Use Userpilot Data Sync? * **Deep, granular insights:** Gain full access to every tracked interaction rather than relying on aggregated metrics. * **Seamless integration:** Export data to your own cloud storage (AWS S3, Google Cloud Storage, or Azure Blob Storage), then feed it into your data warehousing (Snowflake, BigQuery, Redshift) and BI stack (Tableau, Power BI, Looker) via your own ETL pipeline, for a unified view alongside CRM, sales, or support data. * **Advanced analytics:** Leverage SQL, Python/R, and custom models (e.g., funnel drop-offs, attribution, churn/upsell predictions) in your own environment. * **360° data enrichment:** Combine Userpilot events with other business sources for a complete customer journey. * **Ownership & compliance:** Retain, archive, and manage your data under your own policies (GDPR, CCPA). * **Reliable, scalable automation:** Schedule recurring exports to ensure your teams always have fresh data without manual effort. # Prerequisites Source: https://docs.userpilot.com/data-sync/prerequisites Before you can begin leveraging the powerful capabilities of Userpilot Data Sync, it’s essential to ensure that a few prerequisites are met. This section outlines the necessary account requirements, destination storage setup, and technical considerations to ensure a smooth and successful Data Sync implementation. ## Account Requirements To utilize Userpilot Data Sync, please ensure the following account-related conditions are satisfied: * **Appropriate Userpilot Plan:** Data Sync is an add-on feature. Your Userpilot subscription plan must include access to the Data Sync functionality. If you are unsure about your current plan or wish to enable Data Sync, please contact your Userpilot Account Manager or our support team for assistance. * **Necessary User Permissions:** The user configuring Data Sync within your Userpilot account must have the appropriate administrative permissions. Typically, this requires an admin role or a custom role with specific permissions to access and manage Data Sync settings. Please verify your user role and permissions within the Userpilot dashboard. ## Destination Storage Setup & Required IAM/Role Policies Userpilot Data Sync exports your event data to your chosen cloud storage destination. You will need to have an active account and a configured storage location with one of our supported cloud providers. As of the current documentation, Userpilot Data Sync supports the following: > [!NOTE] > > **🔒 Security First: IAM Best Practices** > > When configuring access for Userpilot Data Sync to your cloud storage, always follow the principle of least privilege. Create a dedicated IAM user/role or service account with only the necessary write permissions (e.g., s3:PutObject for S3) to the specific bucket and path prefix. Avoid using root account credentials. * **Amazon S3:** You will need an S3 bucket, the bucket name, its region, and appropriate AWS IAM credentials (Access Key ID and Secret Access Key) that grant Userpilot write access to the specified bucket and path prefix. * **Google Cloud Storage (GCS):** You will need a GCS bucket, your Google Cloud Project ID, and a service account credentials with permissions to write to the bucket. You will also specify a path prefix. * **Azure Blob Storage:** You will need an Azure Storage Account and a container. Provide a **Connection String** that grants Userpilot write access to the container, and optionally a path prefix. **Key considerations for your storage destination:** * Ensure the storage location (bucket/container) is created in the region that best suits your data residency and performance needs. * Configure access permissions meticulously. Userpilot will require write access to the specified path within your storage to deliver the data files. It is best practice to create dedicated credentials or roles with the principle of least privilege. * Note down all necessary details (bucket/container names, regions, access keys, path prefixes, etc.) as you will need them during the Data Sync configuration process in Userpilot. *** ### AWS: Creating a Cross-Account IAM Role If you prefer to grant access via an **Assume Role** rather than long-lived Access Keys, follow these steps in the AWS Console: 1. **Identify (or create) your S3 bucket** that will receive exported data. 2. **Create an IAM policy** with the minimum S3 permissions: ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Sid": "UserpilotS3AccessStatement", "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::", "arn:aws:s3:::/*" ] } ] } ``` 3. **Get your Userpilot App Token:** * In your Userpilot account, navigate to **Settings** > **Environment**. * Copy your app token(USERPILOT.JS token). You will need this to complete the trust policy in the next step. 4. **Create a role** with a **Custom trust policy**: Replace `` in the trust policy below with the app token you copied in step 3: ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::243475302045:user/userpilot-data-sync" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "" } } } ] } ``` 5. **Attach the policy** from step 2 to the role. 6. **Create the role** and copy its ARN (e.g., `arn:aws:iam::774305589997:role/ForUserpilotDataSyncRole`). Provide this ARN in Userpilot when you choose the **Cross-account IAM Role** authentication method. *** ## Technical Considerations While Userpilot Data Sync is designed to be user-friendly, some technical understanding can be beneficial for a seamless experience and effective utilization of the synced data: * **Understanding Data Formats:** Be aware of the data format in which Userpilot will deliver the files (e.g., Avro, JSON, Parquet). Understanding the structure of these formats will be crucial for parsing and ingesting the data into your downstream systems. * **Familiarity with Data Warehousing and ETL/ELT:** While not strictly required to set up the sync, a basic understanding of data warehousing concepts and ETL (Extract, Transform, Load) or ELT (Extract, Load, Transform) processes will be highly beneficial for your data teams who will be consuming and analyzing the synced data. > [!NOTE] > > **🛠️ Prepare Your Data Environment** > > Before using Data Sync, make sure your data team is ready. Familiarity with your data format (e.g., Avro, JSON, Parquet), cloud storage, and ETL tools will make syncing and analyzing Userpilot data much smoother. * **Network Configuration:** In most scenarios, Userpilot will connect to your cloud storage provider’s public endpoints, and no special network configurations are needed. However, if your organization has strict outbound firewall policies, ensure that Userpilot’s egress IP addresses are whitelisted to access the necessary cloud storage APIs. * **Data Volume and Storage Costs:** Consider the volume of event data your application generates. Regular data syncs can accumulate significant amounts of data over time. Be mindful of the storage costs associated with your chosen cloud provider and implement appropriate data lifecycle management policies on your storage bucket/container if necessary. # Setup and configuration Source: https://docs.userpilot.com/data-sync/setup-and-configuration This section provides a comprehensive, step-by-step guide to help you begin using the Data Sync feature. > [!NOTE] > > This feature is available as an add-on for Growth and Enterprise plans. Please contact your Userpilot account manager to have it enabled. > [!NOTE] > > Before you begin, make sure you've reviewed the [Prerequisites](./prerequisites) to ensure your account and cloud storage are ready for Data Sync. This section provides a comprehensive, step-by-step guide to help you begin using the Data Sync feature. It covers everything from initially accessing the feature within the platform to configuring your first data export destination, creating and customizing sync jobs, and effectively managing your ongoing data exports. We will draw heavily on the user interface elements visible in the provided screenshots to make these instructions as clear and practical as possible. ## Accessing Data Sync To access the Data Sync feature: 1. Log in to your Userpilot account. 2. In the left-hand navigation menu, click on **Data**. 3. Under the Data section, click on **Data Sync**. This will take you to the Data Sync dashboard. ## The Data Sync Dashboard The Data Sync dashboard provides an overview of all your configured export jobs. From here, you can see key information at a glance and manage your existing jobs or create new ones. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-sync/assets/images/file-D6pzeBZ4IN.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=e8ea816710696507125248ebdfe5d443) **Dashboard Information:** For each export job listed, you will see the following details: * **Name:** The custom name you assigned to the export job. * **Destination:** The cloud storage provider and bucket where the data is being sent (e.g., Amazon S3, Google Cloud Storage). * **Status:** The current status of the export job (e.g., Live, Disabled, Ended). * **Created by:** The Userpilot user who created the export job. * **Last Sync:** The date and time of the most recent successful data export for that job. **Filtering Export Jobs:** You can filter the list of export jobs to quickly find specific ones: * **By Status:** Click the "Status" dropdown (defaulting to "All statuses") to filter by: * All statuses * Live * Disabled * Ended * **By Environment:** Click the "Environment" dropdown (defaulting to "All Environments") to filter by: * Production * Staging *** ## Creating a New Export Job Setting up a new export job involves a straightforward wizard that guides you through the necessary configuration steps. Once you click the "Create Export" button on the Data Sync dashboard, the creation process begins. The wizard typically involves the following main stages, which we will detail using Google Cloud Storage as an example: 1. **Select Export Destination & Review Data Structure** 2. **Setup Connection** 3. **Verify Connection** 4. **Setup Export Configuration** 5. **Review Export Configuration** ### Step 1: Select Export Destination & Review Data Structure In the first step of creating an export, you will choose where you want your data to be sent and get an overview of the data structure. ![](https://mintcdn.com/userpilot/esxwYsE9z5gRs3KE/data-sync/assets/images/create-export-main-ui.png?fit=max\&auto=format\&n=esxwYsE9z5gRs3KE\&q=85\&s=e21c62fff046ee0169fe17531b300066) * **Choose your cloud storage provider:** * **Amazon S3:** Amazon Simple Storage Service. * **Google Cloud Storage:** Google Cloud Storage Service. * **Azure Blob Storage:** Microsoft Azure’s object storage solution. Select the provider you wish to use. * **Review example data structure:** On the right side of the screen, you will see examples of the data you will be exporting: * **Example of event schema:** This shows the structure of a single raw event object, detailing the fields and their data types (e.g., **`app_token`** , **`event_type`** , **`user_id`** , **`inserted_at`** , **`metadata`** ). For a complete breakdown, see our [Event Schema Reference](./event-schema). * **Example of tree structure:** This illustrates the directory (folder) structure that Userpilot will create in your cloud storage bucket to organize the exported data files (e.g., **`userpilot_datasync_{SYNC_ID}_{APP_TOKEN}/{Date}/all_events.avro`** ). For more details, refer to [Understanding Your Data](./understand-data). ![](https://mintcdn.com/userpilot/esxwYsE9z5gRs3KE/data-sync/assets/images/create-export-main-ui.png?fit=max\&auto=format\&n=esxwYsE9z5gRs3KE\&q=85\&s=e21c62fff046ee0169fe17531b300066) Once you have reviewed this information and selected your provider, click **Next: Setup Connection** to proceed. ### Step 2: Setup Connection In this step, you’ll provide the credentials or role-based access required for Userpilot to write data to your chosen cloud storage provider. The exact fields shown depend on both the provider and the authentication method you select. #### Choose Authentication Method After selecting a provider, an additional drop-down appears for **Authentication Method**. The table below summarises the options currently supported: | Provider | Authentication methods | | - | - | | Amazon S3 | 1. Access Key & Secret Key 2. Cross-account IAM Role (Assume Role) | | Google Cloud Storage | Access Key & Secret Key | | Azure Blob Storage | Connection String | *** #### Amazon S3 ##### Method A: Access Key & Secret Key Fields shown: * **Region** * **Bucket** * **Access Key ID** * **Secret Access Key** * **File Prefix** (optional) ![](https://mintcdn.com/userpilot/esxwYsE9z5gRs3KE/data-sync/assets/images/aws-access-secret-auth.png?fit=max\&auto=format\&n=esxwYsE9z5gRs3KE\&q=85\&s=8142a0af396b4ce6b566596b7505bd86) ##### Method B: Cross-account IAM Role (Assume Role) Fields shown: * **Region** * **Bucket** * **Role ARN** – IAM role in your AWS account that Userpilot should assume. * **File Prefix** (optional) ![](https://mintcdn.com/userpilot/esxwYsE9z5gRs3KE/data-sync/assets/images/aws-iam-role-auth.png?fit=max\&auto=format\&n=esxwYsE9z5gRs3KE\&q=85\&s=67ade6dd0db41d0998eaab0196afe111) > [!NOTE] > > Need help creating the cross-account IAM role? See the full step-by-step guide in [Prerequisites → AWS: Creating a Cross-Account IAM Role](./prerequisites#aws-creating-a-cross-account-iam-role). *** #### Google Cloud Storage Fields shown: * **Region** * **Bucket** * **Secret Access Key** * **File Prefix** (optional) *** #### Azure Blob Storage Authentication method: **Connection String** Fields shown: * **Storage Account Location / Region** * **Container** (the Azure equivalent of a bucket) * **Connection String** – copied from the Azure Portal. * **File Prefix** (optional) > [!WARNING] > > **🔑 Important: Secure Your Credentials!** > > Treat Access Keys, Secret Keys, Role ARNs, files, and Connection Strings like passwords. Store them securely and never share them publicly. Ensure each credential has the minimum necessary permissions. After filling in the required connection details, click **Next: Verify Connection** to test the integration. ### Step 3: Verify Connection Once you provide your connection details, Userpilot will attempt to verify the connection to your cloud storage bucket. This is done by trying to upload a small test file to the bucket and then deleting it if successful. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-sync/assets/images/file-A0SwYXMYX3.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=76cad64ea4d3a738b936bd19c61890cd) If the connection is successful, you will see a confirmation message: > Success! Test file was successfully uploaded and deleted from the Google Cloud Storage bucket. After finishing the configuration, data will begin to export within the next hour. If the connection fails, an error message will be displayed, providing information about the cause of the failure. You will need to review your connection settings and credentials and try again. > [!NOTE] > > **🔍 Troubleshooting Tip: Connection Failed?** > > If connection verification fails, double-check: > > 1. Bucket/Container Name: Exact match, case-sensitive. > 2. Region: Correct region selected for your bucket. > 3. Credentials: Access Key, Secret Key, or Service Account JSON are accurate and have not expired. > 4. Permissions: The provided credentials have write access to the bucket/container and the specified path prefix (if any). > 5. Firewall/Network: Ensure Userpilot IPs can reach your storage provider. > > For more detailed solutions, check our [Troubleshooting Guide](./troubleshooting). Upon successful verification, click **Next: Setup Export**. ### Step 4: Setup Data Export Configuration After successfully verifying your connection, the next step is to configure the specifics of your data export. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-sync/assets/images/file-RKEEWHU7M2.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=848ae1a209ebe56ec728e45e81752d72) This involves setting up Job Details and Event Filters: **Job Details:** * **Job Title:** Give your export job a descriptive name that will help you identify it later (e.g., "Daily Production Event Export to GCS", "Identified users"). * **Recurring Period:** Define how often you want the data to be exported. Common options include "Every day", but other frequencies might be available. Select the desired period from the dropdown. * **Export Format:** Choose the file format for your exported data. "Avro format" is shown as an example. Other formats like JSON or CSV might be available depending on the implementation. * **Environment:** Select the Userpilot environment from which to export data (e.g., "Production", "Staging"). * **Export time period (Optional):** You can specify a date range for the data to be exported. For example, you might want to export historical data from "Apr 1, 2023 - Today". If left unspecified, it will default to ongoing exports and to fill back the data up to 365 days from today. **Event Filters:** This section allows you to refine which data is included in your export. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-sync/assets/images/file-opFlE9sA2V.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=22413ffa256cfc42f9c2b3cdf7214d9d) * **Event Types:** Choose which types of events to include. You can select "All events" or specify particular event types (e.g., identify users/companies, interactions events, page views, tracked events, …etc). Select event types from the dropdown or search to include them. * **Segments (Optional):** If you have defined user segments in Userpilot, you can choose to export data only for users belonging to specific segments. Search for and select the desired segments. * **Users (Optional):** Filter the export to include data only for specific users. Search for users to add them to the filter. Leave empty to include all users (unless other filters apply). * **Companies (Optional):** Filter the export to include data only for specific companies. Search for companies to add them to the filter. Leave empty to include all companies (unless other filters apply). * **Exclude users/companies you have set in the web app using exclusion lists (Checkbox):** If you have global exclusion lists set up in Userpilot, checking this box will ensure that data from those excluded users/companies is not included in this export job. Once you have configured all the job details and event filters, click **Next: Review**. ### Review Export Configuration This is the final step before creating your export job. It provides a summary of all the settings you have configured, allowing you to review everything one last time. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-sync/assets/images/file-wPeIWFr5N7.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=84fefd083a8e976f55d5f039758e8451) Review the setup details, If all the settings are correct, click **Create Export** to finalize and activate your new data export job. If you need to make any changes, you can click the **Back** button to return to previous steps in the wizard. *** ## Managing Export Jobs Once your export jobs are created, you can manage them from the Data Sync dashboard and individual job detail pages. ### Viewing Job Status on Dashboard After creating an export job, it will appear on the Data Sync dashboard. You can quickly see its status (e.g., "Live", "Disabled", "Ended") along with other key details. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-sync/assets/images/file-kcO3WHDUuS.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=106ec6ddd613fdeafdbd4f07f3b9f829) ### Viewing Individual Export Job Details & Run History To see more details about a specific export job and its run history, click on its name from the Data Sync dashboard. This page will show you a list of individual export runs for that job, including: * **Sync date:** The date or date range for which the data was synced. * **Status:** The status of that specific sync run (e.g., "Completed", "Failed"). * **Completed At:** The date and time when the sync run finished. * **Records exported:** The number of records (events) exported during that run. * **Errors:** If a sync run failed, this column will indicate an error. Hovering over an error icon may provide more details. **Examples of Run History Views:** * **No Exports Yet:** If a job is newly created and hasn't run yet, you might see a message like: "No exports available. You should wait for the next run to be available." * **Successful Runs:** For jobs that have run successfully, you will see a list of completed syncs with the number of records exported. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-sync/assets/images/file-TfJSvDkhQW.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=af4d6151f312599ef17f840c641d3dd1) * **Failed Runs:** If a sync fails, the status will show "Failed", and there will be an error indicator. Hovering over the error icon (a warning triangle) will display a tooltip with the error message, such as "Connection failed to the storage provider". If you encounter persistent issues, please consult our [Troubleshooting Guide](./troubleshooting). ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-sync/assets/images/file-h5itVkXHFx.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=2e103abf3101c6cafb60a9bbeebcc94d) On this page, you will also typically find a **Manage** button in the top-right corner, which allows you to modify the settings of the selected export job. ### Managing Export Settings You can access and manage the settings for an existing export job from the Data Sync overview, by clicking the "..." button > Edit Settings, or from the individual export job details page (where you view the run history) and clicking the "Manage" button This will open a side panel titled "Manage Export Settings" where you can modify various aspects of your job. **Settings you can typically manage:** * **Export Name:** You can update the name of your export job. * **Status (Enable/Disable Export):** * A toggle switch ("Data is automatically exported") allows you to enable (Live) or disable the export job. Disabling a job will pause future exports without deleting the configuration. * **Connection Details (Read only)** * **Job Settings:** * **Recurring Period:** Change how often the export runs (e.g., from Daily to Hourly). * **Export Format:** Modify the file format of the exported data (e.g., from Avro to JSON format). * **Export time period (Optional):** Adjust the date range for the data export. * **Filters:** * You can modify the event filters applied to the export job, similar to how they were set during creation: * **Event Types:** Add or remove specific event types. * **Segments (Optional):** Update segment-based filtering. * **Users (Optional):** Modify user-specific filters. * **Companies (Optional):** Adjust company-specific filters. * **Exclude users/companies you have set in the web app using exclusion lists:** Toggle this checkbox. **Saving Changes:** After making any modifications in the "Manage Export Settings" panel, click the **Save** button to apply them. If you wish to discard your changes, click **Cancel**. ## Deleting an Export Job To delete an export job, go to the Data Sync dashboard, then click "..." > Delete on the job you want to delete. Deleting a job will stop all future exports and remove its configuration from Userpilot. This action is permanent and cannot be undone. ![](https://mintcdn.com/userpilot/O1qxAMlVdU0FgGNP/data-sync/assets/images/file-0iWFGEsuc8.png?fit=max\&auto=format\&n=O1qxAMlVdU0FgGNP\&q=85\&s=9df67fb9673584fde775d09e00fe329a) # Troubleshooting Source: https://docs.userpilot.com/data-sync/troubleshooting This section provides solutions to common issues that may arise when using Userpilot Data Sync. Even with careful setup, you might occasionally encounter issues with Userpilot Data Sync. This section covers common problems and how to diagnose and resolve them. ## Common Sync Issues and Resolutions > **Issue: Sync Fails with Authentication Error** > > * **Symptoms:** Sync history shows failed attempts with error messages related to authentication, access denied, or invalid credentials. > * **Possible Causes:** > * Incorrect Access Key ID, Secret Access Key, or Connection String/SAS Token provided during configuration. > * Credentials have expired or been revoked (e.g., rotated AWS keys, expired SAS token). > * Insufficient permissions for the provided credentials (e.g., IAM user/role lacks `s3:PutObject` permission, GCS service account lacks `Storage Object Creator` role). > * Typo in bucket name, project ID, or container name. > * **Resolution:** > 1. Carefully verify all credentials and configuration parameters entered in the Userpilot Data Sync settings against your cloud provider console. > > 2. Ensure the credentials are still active and have not expired. > > 3. Check the permissions granted to the IAM user/role or service account in your cloud provider. Ensure they have the necessary write permissions to the specified bucket/container and path prefix. > > 4. If using IP whitelisting, ensure Userpilot’s IPs (if applicable) are allowed. > > 5. After correcting, try re-saving the configuration or manually triggering a sync if possible. > > > [!WARNING] > > > > **❗ Security Best Practice: Credential Management** > > > > Always use dedicated credentials (e.g., a specific IAM user or service account) for Userpilot Data Sync with the minimum necessary permissions (principle of least privilege). Regularly review and rotate these credentials as per your security policy. > **Issue: Sync Completes, but No New Files Appear in Storage** > > * **Symptoms:** Userpilot indicates the sync was successful, but you don’t see new data files in your S3 bucket, or GCS bucket for the expected period. > * **Possible Causes:** > * Incorrect path prefix specified in Userpilot Data Sync settings, causing files to be written to an unexpected location within your bucket/container. > * Delays in your cloud storage provider reflecting new objects (rare, but possible for very short periods). > * No new event data was generated in Userpilot during the sync period (e.g., for a new setup with no user activity yet). > * Filters on your storage browser or listing commands are hiding the files. > * **Resolution:** > 1. Double-check the **Path Prefix** in your Userpilot Data Sync configuration. Ensure it matches exactly where you expect the files. > 2. Use your cloud provider’s console or CLI tools to list objects at the exact path prefix without any filters. > 3. Verify that there was actual user activity in your application that would have generated Userpilot events during the sync window. > 4. Check Userpilot’s sync logs (if available) for any specific file names or paths it attempted to write. > **Issue: Data Discrepancies or Missing Data** > > * **Symptoms:** You notice that the data in your storage seems incomplete compared to what you see in the Userpilot dashboard, or specific events/properties are missing. > * **Possible Causes:** > * Sync latency: Data for the most recent period might not have been included in the latest sync batch yet (especially for hourly syncs). > * Schema changes: If Userpilot updated its event schema and your ingestion process wasn’t prepared, it might fail to parse new fields or events. > * Errors during your ETL/ELT process: The issue might be in how you are loading or transforming the data after it lands in your storage. > * Partial sync failure: A sync job might have partially completed before encountering an error. > * **Resolution:** > 1. Allow for some latency, especially if comparing to real-time Userpilot dashboards. Check the timestamp of the last successful sync. > 2. Review our [Event Schema Reference](./event-schema) for any recent changes. Ensure your downstream processes can handle the current schema. > 3. Thoroughly examine your own data ingestion and transformation logs for errors. > 4. Check Userpilot’s sync history for any reported partial failures or errors. > 5. Contact Userpilot support if you suspect an issue with the data export itself. > **Issue: Slow Sync Performance** > > * **Symptoms:** Sync jobs are taking an unusually long time to complete. > * **Possible Causes:** > * Very large data volumes being exported. > * Throttling by your cloud storage provider if API limits are exceeded (less common for standard usage). > * Temporary network issues between Userpilot and the cloud storage provider. > * **Resolution:** > 1. If data volume is the cause, this might be normal. Consider if a less frequent sync (e.g., daily instead of hourly) is acceptable if performance is a concern. > 2. Monitor for persistent slowness. If it’s a consistent issue, contact Userpilot support to investigate. ## Data Validation Tips It’s good practice to periodically validate the synced data to ensure integrity and completeness. * **File Counts:** Check if the number of files being delivered per sync period is consistent (accounting for variations in event volume). * **Record Counts:** After ingesting data into your warehouse, run basic counts. For example, count the number of events per day and compare this trend with general activity levels in the Userpilot dashboard. Expect some differences due to sync timing and processing, but look for major discrepancies. * **Schema Checks:** Periodically verify that the data being received matches the expected schema. Tools in your data pipeline can often be configured to alert on schema drifts. * **Spot-Checking Key Metrics:** Calculate a few key metrics from the raw data (e.g., number of specific key events, active user counts for a past period) and compare them directionally with Userpilot’s analytics. Remember that Data Sync provides raw data, and Userpilot’s internal analytics may have different calculation logic or aggregation windows. > [!NOTE] > > **ℹ️ Note: Comparing with Userpilot Analytics** > > Direct 1:1 matching of numbers between your synced raw data and Userpilot’s aggregated dashboard analytics can be challenging due to differences in processing times, time zone handling (raw data is typically UTC), and specific metric definitions. Focus on directional consistency and trends rather than exact matches for high-level validation. # Understanding Your Data Source: https://docs.userpilot.com/data-sync/understand-data Once Userpilot Data Sync is active and data is flowing into your storage destination, the next step is to understand its structure and content. This knowledge is crucial for effectively ingesting, processing, and analyzing the data to extract meaningful insights. This section covers the typical file organization, data formats, and key entities you will encounter. Once Userpilot Data Sync is active and data is flowing into your storage destination, the next step is to understand its structure and content. This knowledge is crucial for effectively ingesting, processing, and analyzing the data to extract meaningful insights. This section covers the typical file organization, data formats, and key entities you will encounter. ## File Structure and Naming Conventions Userpilot exports data in a structured manner to your cloud storage, typically organized by date and possibly event type, to facilitate easier management and querying. * **Directory Structure:** Data files are usually organized hierarchically. A common pattern is by date: ``` [your_path_prefix]/userpilot_datasync_{SYNC_ID}_{APP_TOKEN}/YYYY-MM-DD/ ``` * For example, data synced on May 10, 2025, might be found in a path like `userpilot_events/userpilot_datasync_13214_NX-123454/2025-05-10/`. * **File Naming Conventions:** Files within these directories often include timestamps or unique identifiers to prevent overwrites and indicate the batch of data they contain. * **Data Granularity per File:** Each file typically contains data for a specific time window (e.g., one hour or one day, depending on your sync frequency). ## Data Format Details Userpilot exports data in a well-defined format to ensure consistency and ease of parsing. The most common formats for raw event data are JSON (JavaScript Object Notation) or Parquet. ### JSON * Data is typically provided as one JSON object per line (NDJSON/JSON Lines). * Each line represents a single event. * **Use Cases:** Easy to read and parse by many systems, widely supported in data pipelines and analytics tools. * Example: ```json theme={null} { "app_token": "NX-123ASVR", "event_type": "track", "event_name": "Subscription Purchased", "source": "web-client", "user_id": "user-001", "company_id": "company-001", "inserted_at": "2025-05-11T07:45:30.123456Z", "hostname": "app.example.com", "pathname": "/features/subscription", "screen_width": 1920, "screen_height": 1080, "operating_system": "Windows", "browser": "Chrome", "browser_language": "en-US", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36", "device_type": "desktop", "country_code": "US", "metadata": { "subscription_id": "sub-001", "subscription_type": "monthly", "subscription_price": 10, "subscription_currency": "USD" } } ``` ### CSV * Data is provided as plain text, with each line representing a record and fields separated by commas. The first line is a header row with column names. * **Use Cases:** Easily imported into spreadsheets, relational databases, and many data analysis tools. Best for flat data structures where all records have the same set of fields. * **Example:** ```csv theme={null} app_token,event_type,event_name,source,user_id,company_id,hostname,pathname,country_code,screen_width,screen_height,operating_system,browser,browser_language,user_agent,device_type,inserted_at NX-APP-TOKEN,page_view,,web-client,user-123,company-456,example.com,/,US,1920,1080,macOS,Chrome,en-US,Mozilla/5.0...,desktop,2025-05-06T12:30:00.123Z ``` > [!NOTE] > > **Note:** The `metadata` field may be stringified as JSON, omitted, or flattened depending on implementation. ### Parquet * Parquet is a columnar storage file format optimized for use with big data processing frameworks. * **Use Cases:** Ideal for large-scale analytics, efficient storage, and fast querying in data lakes and warehouses. Supported by tools like Apache Spark, Pandas (Python), and most modern data platforms. * **Note:** You will need tools or libraries that can read this format (e.g., Apache Spark, Pandas in Python with `pyarrow` or `fastparquet`). ### Apache Avro * Avro is a row-oriented data serialization framework that stores data in a compact binary format, with the schema included alongside the data. * **Use Cases:** Common in Apache Kafka, Hadoop, and for long-term data archival where schema evolution is important. Supports adding/removing fields over time without breaking downstream consumers. * **Note:** Data is stored in binary format. Use Avro libraries in your programming language of choice (Java, Python, etc.) to read and write Avro files. The schema is used to interpret the binary data. ## Folder Structure Userpilot Data Sync organizes your exported data in a clear, hierarchical folder structure to make it easy to locate, process, and analyze your data. This structure is designed to support both granular event analysis and high-level reporting. Below is an overview of the folder structure you will find in your storage destination: ```bash theme={null} userpilot_datasync_{Sync_ID}_{APP_TOKEN}/ ├── all_companies/ │ └── all_companies.avro ├── all_users/ │ └── all_users.avro ├── {Date}/ │ ├── all_events.avro │ ├── feature_tags/ │ │ ├── matched_events/ │ │ │ └── track_feature_{ID}.avro │ │ ├── feature_tags_breakdown.avro │ │ └── feature_tags_definitions.avro │ ├── interaction/ │ │ └── matched_events/ │ │ └── interaction_{Type}_{Id}.avro │ ├── labeled_events/ │ │ ├── matched_events/ │ │ │ └── track_labeled_{ID}.avro │ │ ├── labeled_events_breakdown.avro │ │ └── labeled_events_definitions.avro │ ├── tagged_pages/ │ │ ├── matched_events/ │ │ │ └── page_view_{ID}.avro │ │ ├── tagged_pages_breakdown.avro │ │ └── tagged_pages_definitions.avro │ ├── trackable_events/ │ │ ├── matched_events/ │ │ │ └── track_event_{NAME}.avro │ │ ├── trackable_events_breakdown.avro │ │ └── trackable_events_definitions.avro │ ├── session_start/ │ │ └── matched_events/ │ │ └── session_start.avro │ ├── users/ │ │ └── identify_user.avro │ └── companies/ │ └── identify_company.avro ``` ### Folder & File Descriptions * **all\_companies/** and **all\_users/** * Contain a snapshot of all identified companies and users, respectively, as of the latest sync. Each file (e.g., `all_companies.avro`, `all_users.avro`) includes the most recent state and all auto-captured and custom properties for each entity. * **\{Date}/** * Each date folder contains all data synced for that specific day. This allows for easy partitioning and historical analysis. * **all\_events.avro**: All raw events captured on that date, across all event types. * **feature\_tags/**, **labeled\_events/**, **tagged\_pages/**, **trackable\_events/**, **interaction/**: * Each of these folders contains: * **matched\_events/**: Raw events for each identified/tagged/labeled event (e.g., `track_feature_{ID}.avro` , `track_labeled_{ID}.avro` , etc.). * **breakdown files** (e.g., `feature_tags_breakdown.avro` ): Aggregated counts and engagement metrics for that day. * **definitions files** (e.g., `feature_tags_definitions.avro` ): Metadata such as name, description, and category, allowing you to map event IDs to human-readable definitions. * **users/** and **companies/**: Contain user and company identification events for that day (`identify_user.avro`, `identify_company.avro`). ### How to Use This Structure * Use the **all\_companies** and **all\_users** files to get the latest state and properties for all entities in your app. * Use the  folders to analyze daily event activity, engagement, and breakdowns. * The **matched\_events** folders provide access to all raw events for each feature, label, or tag, enabling deep-dive analysis. * The **breakdown** and **definitions** files make it easy to join raw event data with descriptive metadata for reporting and analytics. > [!NOTE] > > **Planning for Data Ingestion:** > > A clear understanding of the file structure and naming is vital for setting up automated ETL/ELT pipelines to load this data into your data warehouse or data lake. Your ingestion scripts will rely on these patterns to discover new files. ## Retrospective Export Runs Userpilot automatically performs retrospective export runs every Saturday to ensure your exported data reflects the most current configuration settings. **Retrospective exports only run for configuration data that requires labeling** — specifically tagged pages and labeled events. This process ensures that historical events are re-exported with the correct data when configuration changes occur. ### How It Works Each Saturday, the Userpilot system: 1. **Detects Configuration Changes:** The system checks for "touched events" — events that have been affected by changes to configuration settings that require labeling, including: * **Tagged Pages:** Changes to Targeting Settings like Domains and Path matching that determine which page views match a tagged page * **Labeled Events:** Updates to Domains and Path matching or CSS selector and Text that determine which events match a labeled event definition 2. **Identifies Affected Events:** When configuration changes are detected, the system identifies all historical events that may have been impacted by these changes. 3. **Runs Retrospective Export:** The system performs a retrospective export on all old partitions up to the previous 365 days, re-exporting data for the affected events to capture the correct raw data after your updates to Targeting Settings for tagged pages or matching criteria for labeled events. ### Affected Folders Only the following folders are updated during retrospective export runs: * **`tagged_pages/*`** — All files within the `tagged_pages` directory structure, including: * `tagged_pages/matched_events/page_view_{ID}.avro` * `tagged_pages/tagged_pages_breakdown.avro` * `tagged_pages/tagged_pages_definitions.avro` * **`labeled_events/*`** — All files within the `labeled_events` directory structure, including: * `labeled_events/matched_events/track_labeled_{ID}.avro` * `labeled_events/labeled_events_breakdown.avro` * `labeled_events/labeled_events_definitions.avro` All other folders (e.g., `feature_tags/`, `trackable_events/`, `interaction/`, `users/`, `companies/`, `all_events.avro`) are **not** affected by retrospective exports and remain unchanged. ### Why This Matters When you update configuration settings for tagged pages or labeled events, such as: * Modifying Targeting Settings (Domains and Path matching) for tagged pages * Changing Domains and Path matching or CSS selector and Text for labeled events The original events may have been exported with data that no longer matches your current configuration. The retrospective export ensures that: * Historical data reflects your current configuration rules * Events are correctly categorized and matched based on updated settings * Your analytics and reporting remain accurate even after configuration changes ### What to Expect * **Frequency:** Retrospective exports run automatically every Saturday * **Scope:** Covers all partitions up to 365 days in the past * **Impact:** Only affected events (those matching changed configurations) are re-exported * **File Updates:** Updated files will appear in your storage destination with the corrected data > [!NOTE] > > **Data Consistency:** > > If you notice updated files in your storage destination on Saturdays, this is likely due to a retrospective export run affecting the `tagged_pages/*` and `labeled_events/*` folders. > > **Recommended Approach:** Setting up your data warehouse or database to read from S3 directly/remotely is preferable, as this keeps data consistency responsibility under Userpilot. When files are updated, your queries will automatically reflect the latest data without requiring manual intervention. > > **Alternative Approach:** If you are copying data into your own storage and ingesting the mutable folders (`tagged_pages/*` and `labeled_events/*`), ensure you are rewriting entire data parts on weekly updates to match Userpilot's data sync behavior. This means replacing the entire affected partition or data segment rather than attempting incremental updates, as the retrospective export process overwrites files completely. # Custom Domain Hosting Source: https://docs.userpilot.com/developer/custom-domain Host the Userpilot JavaScript SDK under your own domain to bypass ad blockers and internal restrictions on third-party domains. > [!NOTE] > > This feature is available on Enterprise and Growth plans. #### **Overview** Adblock or browser safety extensions use a set of parameters that defines what will be blocked. Therefore, they sometimes tend to block Userpilot as it's a foreign domain for your end-users. Custom domain hosting is a solution that allows you to host the Userpilot Javascript SDK under your domain (e.g. [mydomain.example.com](http://mydomain.example.com) instead of [js.userpilot.io](http://js.userpilot.io)). Most of the work will be done from our end; it can take up to 5 business days for the process to complete. This solution is targeted at resolving issues involving cases for both adblockers and internal restrictions regarding which domain the requests are initiated from. #### **How to implement custom domain hosting** **1. Create the domain** Create the desired domain name that you would like to apply, for example, [mydomain.example.com](http://mydomain.example.com) **2. Put in the Request** Reach out to our support team by submitting a chat ticket from our help beacon or by emailing us at [support@userpilot.co](mailto:support@userpilot.com). You will need to provide us with your custom domain created in step 1 and your account app token. **3. Wait for the CNAME record** We will provide you with a CNAME record name and value to define in your DNS provider to issue an SSL certificate under the provided custom domain. Once the SSL certificate is issued, we will provide you with the domain to which you can CNAME the custom domain. **4. Install the script** You will need to specify the new installation object (The original script looks like [this](../developer/installation/web)) , and this will be modified on your end to become: ```javascript theme={null} You can call `userpilot.identify` multiple times on the same page as user properties change. *** ## userpilot.reload The `userpilot.reload` function refreshes Userpilot content based on the current page state or URL. This is especially important in Single Page Applications (SPAs), where the URL or page content can change without a full page reload. **When to use:** * After calling `userpilot.identify` (never before). * On every route or URL change in SPAs (e.g., React, Angular, Vue). * When you want Userpilot to re-evaluate which content to show based on the new page state. **Example: React route change** ```javascript theme={null} import { useLocation } from "react-router-dom"; const location = useLocation(); React.useEffect(() => { if (isAuthenticated) { userpilot.reload(); } }, [location]); ``` **Example: Angular router events** ```typescript theme={null} import { Router, NavigationEnd } from '@angular/router'; import { filter } from 'rxjs/operators'; constructor(private router: Router) { this.router.events.pipe( filter(event => event instanceof NavigationEnd) ).subscribe((event: NavigationEnd) => { const token = localStorage.getItem('token'); if (token) { userpilot.reload(); } }); } ``` **Example: Vue router** ```javascript theme={null} router.afterEach((route) => { userpilot.reload(); }); ``` > **Note:** Calling `userpilot.reload` before identifying the user will have no effect. Always ensure the user is identified first. *** ## Summary Table | Function | When to Use | Example Use Case | | - | - | - | | `userpilot.identify` | On first load after login, or when user properties change | User logs in, updates profile info | | `userpilot.reload` | After identify, on SPA route changes, or to refresh Userpilot UI | User navigates to a new page in an SPA | *** For more details, see the [JavaScript SDK Reference](/developer/installation/SDK-APIs) or contact [support@userpilot.com](mailto:support@userpilot.com). # JavaScript SDK Source: https://docs.userpilot.com/developer/installation/SDK-APIs The Userpilot JavaScript SDK allows you to identify users, track events, trigger content, and manage sessions directly from your web application. ## Installation ```bash theme={null} npm i userpilot ``` ```javascript theme={null} import { Userpilot } from 'userpilot' Userpilot.initialize(app_token, userpilotSettings); ``` Or include via CDN: ```html theme={null} ``` ### userpilotSettings Object | key | Description | Example | | - | - | - | | token | Account App Token | NX-98ii14b | | version | Pinned SDK Version | 1.730 | | connection | Enable long-polling mode | polling \| websocket | | endpoint | API endpoint for when proxying | api.example.com/socket | | domain | JS SDK domain for CNAME | js.example.com | | pageview | Specifies what to collect from a URL | pathonly \| withhash \| fullurl | | auto\_props | Collecting auto properties | true \| false | | sri | Sub Resource Integrity value | sha384-AivuiHqNxK7LKiAbFB+8god8cikUQmz | | auto\_capture | Define autocapture privacy settings | See [Element Privacy Settings](/developer/installation/sdk/settings/privacy) | | crossSubdomainSessionCookie | Keep sessions and active flows across subdomains (disabled by default) | `true` | | callbacks | Listen to events that occur on the SDK side | See [Callbacks](/developer/installation/sdk/settings/callbacks) | | integrations\_interfaces | Define the integration interface | See [Integrations Interfaces](/developer/installation/sdk/settings/integrations_interfaces) | #### Keep sessions and active flows across subdomains Set `crossSubdomainSessionCookie` to `true` to keep a user's session and in-progress flow when they move between subdomains. The SDK stores the session ID and flow progress in first-party cookies for the same user and session. This option defaults to `false` and has no effect when `disableCookies` is `true`. ```javascript theme={null} window.userpilotSettings = { token: "YOUR_APP_TOKEN", crossSubdomainSessionCookie: true }; ``` *** ## Getting Started To use the SDK, ensure it is loaded on your page. Then, you can call the available methods on the `userpilot` object. *** ## Methods ### **identify(userId, )** Identifies the current user with an ID and optional properties. ```javascript theme={null} userpilot.identify("123", { name: "John", email: "john@example.com", created_at: "1519205055" // Additional user properties }); ``` *** ### **anonymous()** Assigns a session-based unique ID for the current user on public pages. ```javascript theme={null} userpilot.anonymous(); ``` *** ### **reload()** Updates Userpilot content when the page state changes. ```javascript theme={null} userpilot.reload(); userpilot.reload({ url: 'https://example.com/hello' }); ``` *** ### **track(name, )** Tracks a custom event for the current user. ```javascript theme={null} userpilot.track("invitedAgent", { name: "Sam", email: "sam@example.com" }); ``` *** ### **triggerById(contentId)** Forces specific Userpilot content to show for the current user. ```javascript theme={null} userpilot.triggerById(17); ``` *** ### **on(event, callback)** Registers a callback for a Userpilot event. ```javascript theme={null} userpilot.on('completed', function(event) { alert('Flow completed'); }); ``` #### Event Examples ```javascript theme={null} // Flow started userpilot.on('started', (event) => { /* ... */ }); // Flow completed userpilot.on('completed', (event) => { /* ... */ }); // Flow dismissed userpilot.on('dismissed', (event) => { /* ... */ }); // Step interaction userpilot.on('step', (event) => { /* ... */ }); ``` *** ### **off(event)** Removes a callback function attached to an event. *** ### **once(event, callback)** Registers a callback to be triggered only once. *** ### **end()** End the currently running flow. *** ### **clean()** Clears Userpilot storage and session data. *** ### **destroy()** Fully clears Userpilot storage and removes all active content. *** ### **suppress()** Fully stops Userpilot operations on the SDK side. *** ### **unsuppress()** Resumes SDK operations. *** ## Parameters | Method | Parameters | Description | | - | - | - | | identify | userId, properties? | Identify a user and set properties | | anonymous | | Unique session-based ID | | reload | options? | Reload Userpilot content | | track | name, properties? | Track a custom event | | triggerById | contentId | Show specific content | | on | event, callback | Listen for Userpilot events | | off | event | Remove event listener | | once | event, callback | Listen for event once | | end | | End the current flow | | clean | | Clear Userpilot storage | | destroy | | Remove all Userpilot data and content | | suppress | | Stop Userpilot operations on the SDK | | unsuppress | | Resume Userpilot after suppression | *** # Anonymous Users Source: https://docs.userpilot.com/developer/installation/anonymous-user To display Userpilot content to users who aren't logged in or are browsing your public website anonymously, you can utilize the function. ### Overview To trigger Userpilot content for users who are not authenticated or are browsing your public website anonymously, implement the `userpilot.anonymous()` function. This method allows Userpilot to automatically generate a unique user ID, enabling the display of targeted content without requiring any user-specific data. ### **Implementation Steps** 1. **Include the Userpilot Script:** Insert the following script into the `` section of your webpage: ```javascript theme={null} ``` Replace `"appToken"` with your actual [Userpilot application token.](https://run.userpilot.io/environment) 2. **Initialize Userpilot Anonymously:** After including the script, call the `userpilot.anonymous()` function: ```javascript theme={null} ``` This function generates a unique user ID, allowing the user to view the triggered content without the need to pass any personal data. > [!WARNING] > > Keep in mind that users accessing pages with the Userpilot script installed are counted as monthly active users (MAU). Userpilot tracks the number of unique users who have logged into the system over the past 30 days. Therefore, to be counted towards your MAU, a user must be identified using either `userpilot.identify()` or `userpilot.anonymous()`, ensuring they are recognized as unique users within the specified period. ### **Restricting Content to Anonymous Users** If you wish to display specific Flows, Checklists, Spotlights, or Resource Center modules exclusively to anonymous users, adjust the Audience condition as follows: Set the condition to 'User Data' => 'user id' => doesn't exist. ![images/file-JY10MI7UrC.png](https://mintcdn.com/userpilot/OsTbaFrNYV-atQEF/images/Screenshot-2026-05-25-at-3.18.03-PM.png?fit=max\&auto=format\&n=OsTbaFrNYV-atQEF\&q=85\&s=25c5b7169bd9e2d20dbd169786c5bc6c) > [!NOTE] > > Any Userpilot content with the Audience setting set to "All users" will appear for the anonymous user. This configuration ensures that the designated content is shown only to users without an assigned user ID, effectively targeting anonymous visitors. By implementing the `userpilot.anonymous()` function, you can effectively engage with anonymous users, providing them with tailored content and enhancing their overall experience on your platform. # Auto Capture Source: https://docs.userpilot.com/developer/installation/mobile/android/auto-capture Configure auto capture for the Userpilot Android SDK Configure automatic screen and interaction capture for Android View and Compose applications using the Userpilot Gradle Plugin and Userpilot SDK. The Userpilot Android SDK can automatically capture Android View and Compose screens and interactions, enabling analytics and engagement without manual screen and event tracking. Use this guide for SDK-side configuration; build-time instrumentation is configured in the app module with the Userpilot Gradle Plugin. *** ## SDK Configuration Auto capture behavior is configured through `UserpilotConfig` during SDK initialization. ```kotlin theme={null} val userpilot = Userpilot(context, "") { enableScreenAutoCapture = true enableInteractionAutoCapture = true enableInteractionTextCapture = true enableInteractionAccessibilityLabelCapture = true enableInteractionValueCapture = false uiFramework = UiFramework.Compose // or UiFramework.View } ``` | **Option** | **Type** | **Default** | **Description** | | - | - | - | - | | `enableScreenAutoCapture` | `Boolean` | `false` | Enables automatic screen tracking. View apps use Activity/Fragment lifecycle tracking. Compose apps should use `UserpilotComposeNavigationTracker(...)` for navigation-aware screen capture. | | `enableInteractionAutoCapture` | `Boolean` | `false` | Enables automatic interaction capture. Requires Gradle plugin instrumentation for the UI framework being captured. | | `enableInteractionTextCapture` | `Boolean` | `true` | If `false`, user-visible text is redacted from captured interaction payloads. | | `enableInteractionAccessibilityLabelCapture` | `Boolean` | `true` | If `false`, accessibility labels/content descriptions are not captured. | | `enableInteractionValueCapture` | `Boolean` | `false` | Enables value payload capture: `is_checked`, selected date/time, slider/seekbar values, and on spinner / ListPopupWindow / Compose collections both `selected_index` and `selected_value`. `AdapterView` list item clicks always include `selected_index`; `selected_value` only when this flag is true. | | `uiFramework` | `UiFramework?` | `null` | Optional framework hint: `UiFramework.View` or `UiFramework.Compose`. | > [!TIP] > > **Note** > > Auto Capture automatically tracks screen views and ignores manually sent `screen` events while enabled. > > If you are migrating from manual screen tracking, please review the migration guide [here](https://docs.userpilot.com/data-events/mobile-screen-tracking/mobile-screen-auto-capture#important). *** ## Gradle Plugin Configuration Auto capture also needs build-time instrumentation in the app module. The plugin is distributed through Maven Central. Add the Userpilot Gradle plugin to your app module `build.gradle` and replace `` with the [latest release version](https://central.sonatype.com/artifact/com.userpilot.plugin/com.userpilot.plugin.gradle.plugin). ```kotlin theme={null} plugins { id("com.userpilot.plugin") version "" } userpilot { enabled.set(true) viewEnabled.set(true) composeEnabled.set(true) composeHierarchy { row.set(true) column.set(false) box.set(false) card.set(true) surface.set(true) } logging.set(false) } ``` | **Option** | **Default** | **Description** | | - | - | - | | `enabled` | `true` | Master switch for plugin instrumentation. | | `viewEnabled` | `false` | Instruments Android View call sites. | | `composeEnabled` | `false` | Instruments Compose call sites. | | `composeHierarchy.row` | `true` | Adds inline `Row` nodes to Compose hierarchy paths. | | `composeHierarchy.column` | `false` | Adds inline `Column` nodes to Compose hierarchy paths. | | `composeHierarchy.box` | `false` | Adds inline `Box` nodes to Compose hierarchy paths. | | `composeHierarchy.card` | `true` | Adds Material 3 card-family nodes to Compose hierarchy paths. | | `composeHierarchy.surface` | `true` | Adds Material 3 `Surface` nodes to Compose hierarchy paths. | | `logging` | `false` | Enables plugin diagnostic logging during builds. | Enable `viewEnabled`, `composeEnabled`, or both depending on the UI frameworks used by the app. *** ## Android View Capture View screen capture is handled by Activity and Fragment lifecycle callbacks. Internal Userpilot surfaces are excluded. ### View Screen Tracking Activities are tracked from lifecycle resume events using the manifest label when available, otherwise the Activity class name. Fragments are tracked by class name, except dialog/bottom-sheet fragments and fragments marked with `userpilotIgnoreScreen`. Use `userpilotIgnoreScreen` when a View `Activity` or `Fragment` is auxiliary content that should not emit its own automatic screen-view event. Typical cases: a host activity that delegates the visible screen to a child, or a fragment composed as one slot inside a split-pane / tabbed / nested-fragment layout where the parent already identifies the screen. ```kotlin theme={null} import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import com.userpilot.autoCapture.view.apis.userpilotIgnoreScreen class DetailsHostActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) userpilotIgnoreScreen = true } } class DetailsPaneFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) userpilotIgnoreScreen = true } } ``` Captured View interactions include: * Clicks and long clicks * Clickable spans * Dialog and bottom-sheet presentation * Dialog button clicks * Adapter item clicks and selections * Text changes * CompoundButton, SeekBar, Slider, RangeSlider, date picker, and time picker changes ### Improving Android Autocapture | **Interaction** | **Supported Types** | **Required Listener** | | - | - | - | | View Click | All subclasses of `View`. | `View.OnClickListener` must be set to the target view. | | In-Text Link Click | `TextView` and its subclasses. | In-text link must be generated using a `ClickableSpan` that overrides the `onClick` method. | | Dialog Button Click | All dialog types that implement `DialogInterface` (e.g. `AlertDialog`). | `DialogInterface.OnClickListener` must be set to the target dialog button. | | Dialog List Item Click | Platform native and AndroidX implementations of `AlertDialog`. | Must attach a `DialogInterface.OnClickListener` to the target dialog's list using `AlertDialog.Builder.setItems()`. | | List Item Click | All subclasses of `AdapterView`. | `AdapterView.OnItemClickListener` must be set to the target list. | | Spinner Item Selected | All subclasses of `AdapterView`, but works best with `Spinner` and its subclasses. | `AdapterView.OnItemSelectedListener` must be set to the target view. | | Toggle Control Value Changed | All subclasses of `CompoundButton` (e.g. `ToggleButton`, `CheckBox`, `Switch`) except `RadioButton`. | `CompoundButton.OnCheckedChangeListener` must be set to the target view. | | Radio Group Value Changed | `RadioGroup` and its subclasses. | `RadioGroup.OnCheckedChangeListener` must be set to the target view. | | Text Input Value Changed | `EditText` and its subclasses. | `TextWatcher` must be set to the input field using `TextView.addTextChangedListener()`. | | Date Value Changed | `DatePickerDialog` | `DatePickerDialog.OnDateSetListener` must be set to the target dialog. | | Time Value Changed | `TimePickerDialog` | `TimePickerDialog.OnTimeSetListener` must be set to the target dialog. | | SeekBar Value Changed | `SeekBar` | `SeekBar.OnSeekBarChangeListener` must be set to the target view. | | Slider Value Changed | Material component library implementations of `Slider` and `RangeSlider`. | No required listeners needed for value change capture. The SDK attaches its own listener. | ### View Privacy Helpers Use these helpers for View-specific privacy and payload control: * `Userpilot.redactText(view)` * `Userpilot.ignoreInteractions(view)` * `Userpilot.overrideTargetText(view, title)` * `Userpilot.clearTargetTextOverride(view)` * `Userpilot.enableCompoundButtonValueChangeCapture(compoundButton)` * `Userpilot.disableCompoundButtonValueChangeCapture(compoundButton)` * `Userpilot.setShouldCaptureValueOnInteractionChangeEvents(shouldCaptureValue)` XML tags are also supported: ```xml theme={null} android:tag="userpilotIgnoreInteractions" android:tag="userpilotRedactText" ``` *** ## Compose Capture The following Compose interactions are supported: * Clickable and combined-clickable taps * Toggleable, checkbox, switch, radio button, and icon toggle changes * Selectable items, tabs, navigation items, chips, and dropdown menu items * Text field changes, debounced per field holder * Slider, date picker, and time picker changes * Dialog, alert dialog, modal bottom sheet, and exposed dropdown presentation ### Limitations * **Material2 Compose components are intentionally not instrumented.** Use Material3 — the wrappers are tuned for Material3 **1.3.+** and are expected to work with Material3 **1.1.0+**. Apps pinned to Material3 below 1.1.0 may see missing or misbehaving Compose instrumentation. Plain foundation controls (`BasicTextField` via the `String` and `TextFieldValue` overloads) are covered regardless of Material version. * **Compose screen tracking is opt-in.** Activities and Fragments are tracked automatically when `enableScreenAutoCapture = true` via the same Activity / Fragment lifecycle pipeline that View auto capture uses. Compose-driven screens require one of the explicit helpers: `UserpilotComposeNavigationTracker(navController)`, `UserpilotComposeNavigationTracker(backStack = ...)`, or `Modifier.userpilotScreen("title")` (see [Compose Screen Tracking](#compose-screen-tracking)). There is no automatic route-name detection — pick one of these wrappers per nav graph or per screen. * **Custom-built Compose components are not auto-instrumented.** The Gradle plugin rewrites known foundation / Material3 call sites (`Modifier.clickable`, `Modifier.toggleable`, `Button`, `Checkbox`, `ModalBottomSheet`, etc.). A composable that implements its own interaction handling without going through those primitives won't be captured. Use the supported building blocks to inherit auto capture for free. ### Compose Screen Tracking ```kotlin Navigation 2 theme={null} @Composable fun App() { val navController = rememberNavController() UserpilotComposeNavigationTracker( navController = navController, screenNameResolver = { route -> route.substringAfterLast(".") }, screenFilter = { route -> !route.startsWith("debug") }, ) } ``` ```kotlin Navigation 3 theme={null} sealed class Screen { data object Home : Screen() data class Product(val id: Int) : Screen() data object Debug : Screen() } @Composable fun App() { val backStack = remember { mutableStateListOf(Screen.Home) } UserpilotComposeNavigationTracker( backStack = backStack, screenNameResolver = { key -> when (key) { Screen.Home -> "Home" is Screen.Product -> "Product ${key.id}" Screen.Debug -> "Debug" } }, screenFilter = { key -> key !is Screen.Debug }, ) } ``` For a single Compose screen outside of a nav graph (for example, a one-off screen launched from XML, a settings overlay, or any Composable hosted directly in an Activity), attach `Modifier.userpilotScreen(...)` to the screen's root container. It fires a `screen(...)` event every time the host lifecycle reaches `RESUMED`, so the report is aligned with the user actually seeing the screen rather than with composition entry: ```kotlin theme={null} @Composable fun ProfileScreen() { Column( modifier = Modifier .fillMaxSize() .userpilotScreen(screenTitle = "Profile"), ) { // screen content } } ``` ### Compose Privacy Helpers Use subtree-scoped helpers for broad privacy rules. The default is to opt out / redact everything in the wrapped subtree: ```kotlin theme={null} UserpilotIgnoreInteractions { SensitiveSettings() } UserpilotRedactText { ProfileForm() } ``` Both helpers also accept an explicit boolean so you can carve out an exception inside a larger opt-out — for example, re-enable a specific section while everything around it is ignored: ```kotlin theme={null} UserpilotIgnoreInteractions { SensitiveSettings() UserpilotIgnoreInteractions(ignore = false) { // captured normally, even though the outer scope opts out FeedbackPrompt() } } ``` Use modifier-level helpers for one element. Unlike the subtree composables, the effect does **not** propagate into the element's `content` slot, and an explicit modifier value wins over any inherited scope: ```kotlin theme={null} Button( modifier = Modifier.userpilotIgnoreInteractions(), onClick = onClick, ) { Text("Internal action") } TextField( modifier = Modifier.userpilotRedactText(), value = value, onValueChange = onValueChange, ) ``` Both modifier helpers accept the same `ignore: Boolean = true` / `redact: Boolean = true` parameter, so you can use them to undo an inherited scope at the element level: ```kotlin theme={null} UserpilotRedactText { ProfileForm() // The job title is product copy, not PII — keep it captured. Text( text = "Senior Engineer", modifier = Modifier.userpilotRedactText(redact = false), ) } ``` *** ## Runtime Controls To stop or resume collection without changing options — for example a "private mode" — use: ```kotlin theme={null} Userpilot.stopAutoCapture() // stops capture; preserves options Userpilot.resumeAutoCapture() // resumes capture ``` Both calls are idempotent. They preserve the options supplied during SDK initialization. While stopped, no screen or interaction events are recorded regardless of other configuration. This is a global toggle that overrides per-view settings. # Callbacks Source: https://docs.userpilot.com/developer/installation/mobile/android/callbacks Configure navigation, analytics, and experience callbacks Configure how your app handles navigation, analytics events, and experience lifecycle callbacks. ## Navigation Handler Defines how your app handles deep link route triggers configured in Userpilot experiences, requiring you to route the user to the appropriate location. ```kotlin theme={null} public interface UserpilotNavigationHandler { public fun navigateTo(uri: Uri) } ``` The Userpilot SDK automatically handles navigation if you haven't implemented `UserpilotNavigationHandler`. When a deep link with a defined scheme and host is present in the manifest file, the SDK will open the corresponding Activity. If the link is external, the SDK will handle it appropriately. For complete control over link handling, implement `UserpilotNavigationHandler` to customize behavior for all link types. The `useInAppBrowser` flag (when you do not implement `UserpilotNavigationHandler`) determines whether to open URLs using an in-app browser (e.g., Chrome Custom Tabs) or fall back to the system browser. * When `useInAppBrowser` is `true`: opens using Chrome Custom Tabs for a faster, integrated experience. * When `useInAppBrowser` is `false` or not set: opens using the default system browser or another fallback. Set `navigationHandler` in the SDK configuration when initializing Userpilot, so it is available for startup notification taps. Navigation callbacks run on the main thread. In SDK 1.4.0 and later, the latest link received during initialization is held until a handler is assigned or SDK initialization finishes. If no handler is assigned, the SDK uses its normal URI-opening behavior. This startup delay does not wait for a router that your app initializes asynchronously; buffer the URI in your handler if your navigation UI is not ready. ## Analytics Listener Receives callbacks whenever the SDK tracks an event, screen, or identifies a user. Use this to integrate with another analytics tool. ```kotlin theme={null} public interface UserpilotAnalyticsListener { /** * Notifies the listener when a Userpilot analytics event is tracked. * * @param type The type of analytic being tracked. Possible values are: * - `Identify`: Represents an identify event. * - `Screen`: Represents a screen tracking event. * - `Event`: Represents a custom event. * @param value The primary value of the analytic being tracked: * - For `Identify`: The user ID. * - For `Screen`: The screen title. * - For `Event`: The event name. * @param properties Additional context or metadata associated with the analytic, provided as a `Payload` object. */ public fun didTrack(type: UserpilotAnalytic, value: String, properties: Payload) } public enum class UserpilotAnalytic { Identify, Screen, Event } ``` ## Experience Listener Receives callbacks when experiences start, complete, or are dismissed, and when step state changes. Implement this to forward data to another destination or to react to user actions. ```kotlin theme={null} public interface UserpilotExperienceListener { /** * Notifies the listener of changes in the state of a Userpilot experience. * * @param experienceType The current experience type. * @param experienceId A unique identifier for the experience. * @param experienceState The current state of the experience. */ public fun onExperienceStateChanged( experienceType: UserpilotExperienceType, experienceId: Int?, experienceState: UserpilotExperienceState ) /** * Notifies the listener when the state of a specific step in a Userpilot experience changes. * * @param experienceType The current experience type. * @param experienceId A unique identifier for the experience. * @param stepId A unique identifier for the experience step. * @param stepState The current step state of the experience. * @param step The index of the current step in the experience. * @param totalSteps The total number of steps in the experience. */ @Suppress("LongParameterList") public fun onExperienceStepStateChanged( experienceType: UserpilotExperienceType, experienceId: Int, stepId: Int, stepState: UserpilotExperienceState, step: Int?, totalSteps: Int? ) } /** * Enum representing the possible states of a Userpilot experience. */ public enum class UserpilotExperienceType { Flow, Survey, NPS } /** * Enum representing the possible states of a Userpilot experience. */ public enum class UserpilotExperienceState { /** * Indicates that the experience has started. */ Started, /** * Indicates that the experience has successfully completed. */ Completed, /** * Indicates that the experience was dismissed before completion. */ Dismissed, /** * Indicates that the experience/step was skipped. */ Skipped, /** * Indicates that the experience/step was submitted. */ Submitted } ``` # Custom URL Scheme Source: https://docs.userpilot.com/developer/installation/mobile/android/custom_scheme Register and handle the Userpilot custom URL scheme on Android to preview draft experiences and route push notification taps. The Userpilot Android SDK supports a custom URL scheme that lets you preview experiences on a real device before publishing them, and that carries push notification taps back into your app. Setting it up is two steps: add an `` to your `AndroidManifest.xml` so Android routes matching links to your Activity, then forward the incoming `Intent` to the Userpilot SDK. The SDK does all the validation, so you hand it every Intent your Activity receives and act on the result. *** ## Register the custom URL scheme Add an intent filter with the custom scheme inside the desired `` element. Register it on the Activity that hosts your app, which is usually the one already marked with ``. Replace `APP_TOKEN` with your app's Userpilot token, which you can obtain from your [Environments Page](https://run.userpilot.io/environment). For example, if your token is `NX-12345678`, your scheme value is `userpilot-nx-12345678`. Set `android:launchMode="singleTop"` so an incoming link is delivered to the running Activity through `onNewIntent` instead of creating a second instance of it. ```xml theme={null} ``` Keep `android:host="sdk"` in this filter. Userpilot validates that the incoming URL uses the `sdk` host before handling preview or push-notification links. *** Use the token exactly as passed to the SDK, in lowercase. Keep the `STG-` prefix for staging: `STG-NX-12345678` uses `userpilot-stg-nx-12345678`, while `NX-12345678` uses `userpilot-nx-12345678`. ## Handle the custom URL scheme Forward the Intent to `userpilot.onNewIntent(intent)`. It returns `true` if the link was a Userpilot link and the SDK handled it, and `false` if your app should handle the Intent itself. An Intent reaches your Activity through either `onCreate` (a cold launch from a link) or `onNewIntent` (a link that arrives while your app is already running). Override both and forward from each, otherwise links only work in one of the two cases. ```kotlin theme={null} override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) handleLinkIntent(intent) } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) handleLinkIntent(intent) } private fun handleLinkIntent(intent: Intent?) { val userpilotHandled = userpilot.onNewIntent(intent) if (userpilotHandled) return // Not a Userpilot link, so the application should handle it } ``` > [!WARNING] > > **`onNewIntent` takes a single argument** > > The signature is `onNewIntent(intent: Intent?): Boolean`. Earlier guides showed a two-argument form that passed the Activity as well; that overload does not exist. The other overload, `onNewIntent(intent: Map)`, is for forwarding a push notification payload you already parsed yourself, not for deep links. *** ## Supported links The SDK only claims an Intent when the scheme is one of the accepted values, the host is `sdk`, and the action is `VIEW` or `MAIN`. Every other Intent is passed back to your app. | **Link** | **Purpose** | | - | - | | `userpilot-{token}://sdk/experience_preview/{experience_id}` | Previews a draft Flow or Survey. This is the link behind the Builder's QR code. | | `userpilot-{token}://sdk/notification` | Routes a push notification tap to the SDK. Query parameters carry the notification payload. | > [!NOTE] > > You don't build the `sdk/notification` link yourself. The SDK posts each notification with a matching `PendingIntent`, so a tap arrives at your Activity as a normal deep link and is covered by the same `onNewIntent` call. See [Push Notifications](./push-notifications) for the delivery setup. *** ## Preview an experience with a QR code Userpilot can render a draft experience on your device before you publish it, so you can check layout, copy, and behavior on a real screen. Preview is supported for **Flows** and **Surveys**. The preview is delivered over the custom URL scheme. It works once your app is installed on the device and the scheme is registered and handled as described above. **1. Open the experience** Open a Flow or Survey in the Experience Builder and enter **Edit Mode**. **2. Start the preview** Select **Preview** in the top-right corner of the builder. A popup with a **QR code** appears. **3. Scan the QR code** Scan the code with your device camera. Your app opens and the experience appears. Points to keep in mind: * You don't need to apply or save your changes before previewing them. * The experience doesn't need to be live. * A preview bypasses the experience's targeting and frequency settings, so it appears even when the current user wouldn't normally qualify for it. * Once you close the experience, the app resumes normal behavior. *** ## Test push notifications You can send a test push to a specific user from the Userpilot dashboard and verify delivery and tap handling on a real device without sending anything to your wider audience. The notification doesn't need to be live, so you can test it while it's still a draft. * Test pushes work in both the staging and production environments. * Test pushes are **not** counted as real sends, and they're excluded from analytics. * Test pushes bypass the notification's targeting and frequency settings. For the dashboard steps, see [Mobile push notifications](/in-app-engagement/mobile-content/mobile-push-notifications). Because a notification tap arrives as a deep link on Android, a test push also exercises your `onNewIntent` handling. If the notification appears but tapping it does nothing, the scheme registration is the first thing to check. *** ## Test a deep link from the command line Open a link with `adb` to confirm your scheme is registered and your handler runs, without going through the Builder: ```bash theme={null} adb shell am start -a android.intent.action.VIEW \ -d "userpilot-nx-12345678://sdk/experience_preview/12345" ``` Replace the scheme with your own token and `12345` with a real experience ID. Enable SDK logging during setup to see the SDK report which link it received and what it did with it. *** ## Troubleshooting > **Nothing happens when I scan the QR code** > > Confirm the scheme in `AndroidManifest.xml` matches your token exactly, lowercased. A mismatched scheme means Android never routes the link to your Activity, so your handler is not called at all. Verify the registration with the `adb shell am start` command above: if `adb` reports no matching Activity, the intent filter is the problem. > **The app opens but the experience doesn't appear** > > Android routed the link, so registration is correct and the problem is in the handling step. Check that you forward the Intent from **both** `onCreate` and `onNewIntent`. A cold launch only passes through `onCreate`, so handling just `onNewIntent` silently drops links that start the app. Also confirm the SDK is set up with the same token that generated the QR code. > **Tapping a push notification does nothing** > > Notification taps travel over this same URL scheme, so they need the intent filter and the `onNewIntent` forwarding described above. Confirm the filter host is exactly `sdk`. > **A link opens a second copy of my Activity** > > Set `android:launchMode="singleTop"` on the Activity. Without it, Android may create a new instance for the incoming link rather than delivering it to the running one through `onNewIntent`. > **Preview works in production but not in staging** > > Each environment has its own token, and the scheme follows the token. Confirm the staging build registers the scheme for the token it initializes the SDK with. [**For any questions or concerns please reach out to support@userpilot.com**](mailto:support@userpilot.com) # Userpilot SDK features Source: https://docs.userpilot.com/developer/installation/mobile/android/features Offline analytics support, supported SDK versions, and local event storage limits. ## Offline Mode Offline Mode is available in SDK version **1.4.0 and later**. The SDK stores these events on the device while offline: * Identify calls, screen views, custom events, and Auto Capture events. * Flow views, dismissals, completions, and step views and completions. * Survey views, dismissals, completions, responses, and step views, skips, and submissions. * NPS views, dismissals, and feedback submissions. * Push-notification opens, excluding test notifications. Stored events survive app restarts and are sent together in a batch ordered by stored timestamps when the connection returns, before pending live events. Identify a user, or call `anonymous()`, before collecting events. Experience interactions and push opens are not persisted without a current user ID. Offline Mode is always enabled and requires no additional configuration. ### Limitations * All supported event types share local storage of up to **3 MB** or **5,000 events**, whichever limit is reached first. * When either limit is reached, new events are not stored until space becomes available. * Switching accounts or logging out clears stored events. *** [**For any questions or concerns, please reach out to support@userpilot.com**](mailto:support@userpilot.com) # Fonts Source: https://docs.userpilot.com/developer/installation/mobile/android/fonts Configure system and custom fonts in the Userpilot Android SDK Configure system and custom fonts used in Userpilot experiences, including resolution order, weights, and multi-module support. The Userpilot Android SDK supports system and custom fonts for rendering experiences, ensuring visual consistency across your app with dynamic fonts, weight mapping, and text scaling. ## Overview When building an experience, you can specify the font family name. The SDK will search and apply the font automatically so the selected font renders consistently across your app. ## Font Sources and Resolution Order The SDK resolves fonts by checking the following sources in order: 1. **System Fonts**: Standard Android typefaces such as `default`, `serif`, and `monospaced`. 2. **Resources**: Fonts defined in the `res/font` folder of the SDK or client apps. 3. **Assets**: Fonts stored in the `assets/fonts/` directory. 4. **System Font Directory**: Custom fonts located in `/system/fonts`. ## Font Weight Handling The SDK converts weight strings (for example, `bold`, `light`) to a `Typeface` style or integer value using the `FontWeight` enum. ## Multi-Module Apps or Custom App IDs If your app supports multiple modules and you want to use a font from one of them, provide the module's package name so the SDK can locate the font. ```kotlin theme={null} userpilot = Userpilot(context, "") { packageNames = listOf("com.example.features") } ``` ## Error Logging If a requested font cannot be found, an error message is logged to help debug issues during SDK development or integration: ```plaintext theme={null} Font "fontName" not found! ``` # Android SDK Source: https://docs.userpilot.com/developer/installation/mobile/android/installation Install and configure the Userpilot Android SDK Install and initialize the Userpilot Android SDK, identify users, track screens and events, and configure optional settings. The Userpilot Android SDK enables you to capture user insights and deliver personalized in-app experiences in real time. With a one-time setup, you can immediately begin leveraging Userpilot's analytics and engagement features to understand user behavior and guide their journeys in-app. *** ## Getting Started Your application's `build.gradle` must have a `compileSdk` of 35+ and `minSdk` of 23+, and use Android Gradle Plugin (AGP) 8.1+. ```kotlin theme={null} android { compileSdk 35 defaultConfig { minSdk 23 } } ``` Due to the SDK's usage of Jetpack Compose, it is required to either: 1. Apply the `kotlin-android` plugin in your app's `build.gradle` file. ```kotlin theme={null} plugins { id 'com.android.application' id 'kotlin-android' } ``` 2. **Or** update to Android Gradle Plugin 8.4.0+. > [*Related Google issue*](https://issuetracker.google.com/issues/328687152) regarding usage of Jetpack Compose dependency versions 1.6+. ## Installing the Library The library is distributed through Maven Central. Add the Userpilot module to your `build.gradle` as a dependency and replace `` with the [latest release version](https://central.sonatype.com/artifact/com.userpilot/userpilot-android). Release notes are available [here](./mobile-android-release-notes). ```kotlin theme={null} repositories { mavenCentral() } dependencies { implementation 'com.userpilot:userpilot-android:' } ``` Once synced, the Userpilot SDK is available to import throughout your application. *** ## Initialize the SDK Initialize Userpilot once in your Application class to ensure the SDK is ready as soon as your app starts. Replace `` with your Application Token from the [Environments Page](https://run.userpilot.io/environment). ```kotlin Kotlin theme={null} class YourApplication : Application() { lateinit var userpilot: Userpilot override fun onCreate() { super.onCreate() userpilot = Userpilot(this, "") { loggingEnabled = true } } } ``` ```java Java theme={null} public class YourApplication extends Application { private Userpilot userpilot; @Override public void onCreate() { super.onCreate(); userpilot = UserpilotKt.Userpilot(application, "", config -> { config.setLoggingEnabled(true); return Unit.INSTANCE; }); } } ``` #### Note for Apps Using AndroidX Startup If your application also uses `androidx.startup.InitializationProvider`, you **should not** set `tools:node="ignore"` on this provider to disable it. Doing so will **prevent the Userpilot SDK from being initialized correctly.** Instead, make sure to **merge** the provider declarations using: `tools:node="merge"` This ensures that your custom initializers and the Userpilot initializer both get registered properly. ## Identify Users (Required) Identify unique users and companies (groups of users) alongside their properties. Once identified, all subsequent tracked events and screens will be attributed to that user. > [!WARNING] > > **Important** > > It’s crucial to call the Userpilot identify function; without it, Userpilot won’t be able to recognize your users, and mobile content won’t be displayed to them. **Recommended Usage:** * **On user authentication (login):** Immediately call `identify` when a user signs in to establish their identity for all future events. * **On app launch for authenticated users:** If the user has a valid authenticated session, call `identify` at app launch. * **Upon property updates:** Whenever user or company properties change. ```kotlin Kotlin theme={null} userpilot.identify( userId = "", properties = mapOf("name" to "John Doe", "email" to "user@example.com", "created_at" to "2019-10-17", "role" to "Admin"), company = mapOf("id" to "", "name" to "Acme Labs", "created_at" to "2019-10-17", "plan" to "Free") ) ``` ```java Java theme={null} Map properties = new HashMap<>(); properties.put("name", "John Doe"); properties.put("email", "user@example.com"); properties.put("created_at", "2019-10-17"); properties.put("role", "Admin"); Map company = new HashMap<>(); company.put("id", ""); company.put("name", "Acme Labs"); company.put("created_at", "2019-10-17"); company.put("plan", "Free"); userpilot.identify("", properties, company); ``` **Properties Guidelines** * The `id` key is required in company properties to identify a unique company. * Userpilot supports String, Numeric, and Date types. * Send date values in ISO8601 format. * If you plan to use Userpilot's localization features, pass the user property `locale_code` with a value that adheres to ISO 639-1 format. * Userpilot's reserved properties have pre-determined types and improve the profiles interface in the dashboard: * Use key `email` to pass the user's email. * Use key `name` to pass the user's or company's name. * Use key `created_at` to pass the user's or company's signup date. > [!TIP] > > **Notes** > > * Make sure your User ID source is consistent across all platform installations (Web, Android, and iOS). > * While properties are optional, they are essential for Userpilot's segmentation capabilities. We encourage you to define properties with the people responsible for Userpilot integration. ## Track Screens (Required) Tracking screens is crucial for unlocking Userpilot’s core engagement and analytics capabilities. Screen views are used to trigger eligible in-app experiences, improve targeting, and provide context for analytics by associating subsequent events with the currently active screen. **Auto Capture** Userpilot SDK supports automatic screen tracking for Android applications, allowing screen views to be captured without manually sending screen events. When Auto Capture is enabled, the SDK automatically detects and tracks screens across your app lifecycle. ```kotlin Kotlin theme={null} val userpilot = Userpilot(context, "") { enableScreenAutoCapture = true } ``` > [!TIP] > > **Note** > > Auto Capture automatically tracks screen views and ignores manually sent `screen` events while enabled. > > If you are migrating from manual screen tracking, please review the migration guide [here](https://docs.userpilot.com/data-events/mobile-screen-tracking/mobile-screen-auto-capture#important). **Manual Tracking** If Auto Capture is disabled, you can manually track screens by calling the `screen` API whenever a user navigates to a new screen. ```kotlin Kotlin theme={null} userpilot.screen("Profile") ``` ```java Java theme={null} userpilot.screen("Profile"); ``` ## Track Events Log any meaningful action the user performs. Events can be button clicks, form submissions, or any custom activity you want to analyze. Optionally, you can pass metadata with the event to provide specific context. ```kotlin Kotlin theme={null} userpilot.track("Added to Cart", mapOf("itemId" to "sku_456", "price" to 29.99)) ``` ```java Java theme={null} Map eventProperties = new HashMap<>(); eventProperties.put("itemId", "sku_456"); eventProperties.put("price", 29.99); userpilot.track("Added to Cart", eventProperties); ``` ## Logout When a user logs out, call `logout()` to clear the current user context. This ensures subsequent events are no longer associated with the previous user. ```kotlin Kotlin theme={null} userpilot.logout() ``` ```java Java theme={null} userpilot.logout(); ``` ## Anonymous Users If a user is not authenticated, call `anonymous()` to track events without a user ID. This is useful for pre-signup flows or guest user sessions. ```kotlin Kotlin theme={null} userpilot.anonymous() ``` ```java Java theme={null} userpilot.anonymous(); ``` > [!WARNING] > > Anonymous users are counted towards your Monthly Active Users usage. You should take your account’s MAU limit into consideration before applying this API. ## Experiences Trigger a specific experience programmatically using its ID. This API allows you to manually initiate an experience within your application. ```kotlin Kotlin theme={null} userpilot.triggerExperience("") ``` ```java Java theme={null} userpilot.triggerExperience(""); ``` End the current active experience: ```kotlin Kotlin theme={null} userpilot.endExperience() ``` ```java Java theme={null} userpilot.endExperience(); ``` ## Configuration (Optional) | **Parameter** | **Type** | **Description** | | - | - | - | | loggingEnabled | Boolean | Enable or disable logs for the SDK. **Default: false** | | enableScreenAutoCapture | Boolean | Enables automatic screen tracking. **Default: false** | | enableInteractionAutoCapture | Boolean | Enables automatic interaction capture. **Default: false** | | packageNames | List\ | Custom package list for fonts, mostly used when fonts are stored in a different package than your main app (multi-module apps). | | disableRequestPushNotificationsPermission | Boolean | Disable push notifications permission request by the SDK. **Default: false** | | useInAppBrowser | Boolean | Determines whether to open URLs inside CustomTabsIntent or use the system browser. **Default: false** | | navigationHandler | UserpilotNavigationHandler | Sets the handler for link navigation. Navigation will be handled by the client app. | | analyticsListener | UserpilotAnalyticsListener | Sets the listener to be notified about analytics events. | | experienceListener | UserpilotExperienceListener | Sets the listener to be notified about experience display and lifecycle events. | **Example Usage** ```kotlin Kotlin theme={null} val userpilot = Userpilot(context, "") { loggingEnabled = true enableScreenAutoCapture = true enableInteractionAutoCapture = true useInAppBrowser = true disableRequestPushNotificationsPermission = true navigationHandler = object : UserpilotNavigationHandler { override fun navigateTo(uri: Uri) { } } analyticsListener = object : UserpilotAnalyticsListener { override fun didTrack( type: UserpilotAnalytic, value: String, properties: Payload, ) { } } experienceListener = object : UserpilotExperienceListener { override fun onExperienceStateChanged( experienceType: UserpilotExperienceType, experienceId: Int?, experienceState: UserpilotExperienceState ) { } override fun onExperienceStepStateChanged( experienceType: UserpilotExperienceType, experienceId: Int, stepId: Int, stepState: UserpilotExperienceState, step: Int?, totalSteps: Int?, ) { } } } ``` ```java Java theme={null} Userpilot userpilot = UserpilotKt.Userpilot(application, "", config -> { config.setLoggingEnabled(true); config.setEnableScreenAutoCapture(true); config.setEnableInteractionAutoCapture(true); config.setUseInAppBrowser(true); config.setDisableRequestPushNotificationsPermission(true); Map sdkMessages = new HashMap<>(); sdkMessages.put(UserpilotMessageKeys.PUSH_NOTIFICATION_MESSAGE, "Custom app message"); config.setSdkMessages(sdkMessages); config.setNavigationHandler(new UserpilotNavigationHandler() { @Override public void navigateTo(@NonNull Uri uri) { } }); config.setAnalyticsListener(new UserpilotAnalyticsListener() { @Override public void didTrack(@NonNull UserpilotAnalytic type, @NonNull String value, @Nullable Map properties) { } }); config.setExperienceListener(new UserpilotExperienceListener() { @Override public void onExperienceStepStateChanged(@NonNull UserpilotExperienceType experienceType, int experienceId, int stepId, @NonNull UserpilotExperienceState stepState, @Nullable Integer step, @Nullable Integer totalSteps) { } @Override public void onExperienceStateChanged(@NonNull UserpilotExperienceType experienceType, @Nullable Integer experienceId, @NonNull UserpilotExperienceState experienceState) { } }); return Unit.INSTANCE; }); ``` # Userpilot Android SDK Release Notes Source: https://docs.userpilot.com/developer/installation/mobile/android/mobile-android-release-notes Release history for the Userpilot Android mobile SDK. ## 1.4.1 – Oct 2, 2026 **🚀 Description**\ This release moves the filtering of repeated identify calls from the SDK to Userpilot's servers. 🛠 Improvements * Sent every identify call to Userpilot, including calls that repeat the current user's data. Userpilot now removes duplicate identity updates on the server, so no code changes are required. *** ## 1.4.0 – Sep 27, 2026 **🚀 Description**\ This release adds persistent offline analytics and experience-event support and improves event delivery when the connection returns, along with identity and deep-link handling improvements. 🐛 Bug Fixes * Improved queued event processing after reconnecting and when switching users. * Prevented content and theme requests from stalling after queued analytics finished processing, and sent pending experience interactions before screen refreshes to avoid immediately showing dismissed content again. * Preserved experience eligibility across offline navigation and prevented duplicate screen views after offline experience dismissal. * Corrected custom URL scheme matching, held startup deep links until navigation could be routed, and fixed an initialization failure in link handling. Enabled push-tap navigation without waiting for the analytics connection. * Fixed session tracking for the first screen and restored content evaluation when identification finished before the first screen event. * Prevented system Back and predictive Back from dismissing dialogs and bottom sheets without completing the experience dismissal flow. * Limited NPS to once per screen visit and restored Android 6.0 compatibility in event-processing utilities. ✨ Features * Added local storage for offline identify calls, screen views, custom events, Auto Capture events, flow/survey/NPS interactions, and push-notification opens. * Added automatic batch sending of stored events in timestamp order when the connection returns, including events saved before an app restart. * Added an opt-in setting, disabled by default, to refresh user identity before screen events. * Reduced redundant identity updates and automatically re-synced push tokens after identifying a user. See [Offline Mode](/developer/installation/mobile/android/features#offline-mode) for supported behavior and storage limits. *** ## 1.3.0 – Sep 2, 2026 **🚀 Description**\ This release adds QR-code preview experiences and test push notification support, makes in-app experiences more reliable in preview, rotation, and landscape, along with minor improvements and enhancements. 🐛 Bug Fixes * Kept dialogs and bottom sheets through rotation, and improved experience layout in landscape and on carousel screens. ✨ Features * Added preview experiences launched from a QR code. * Added support for test push notifications. *** ## 1.2.3 – Aug 6, 2026 **🚀 Description**\ This release enhances Auto Capture reliability, improves privacy controls for captured data, and includes stability and documentation improvements. 🐛 Bug Fixes * Improved the visibility of the NPS close button. * Fixed Auto Capture event ordering by ensuring pending interaction events are processed before auto-captured screen events. * Added an additional storage layer for Activity references to improve stability. *** ## 1.2.2 – Jul 13, 2026 🚀 Description This release extends support to more Userpilot SDK wrappers, along with minor improvements and enhancements. *** ## 1.2.1 – Jul 8, 2026 🚀 Description This release introduces Auto Capture support for wrapper SDKs, along with minor updates and enhancements. *** ## 1.2.0 – Jun 24, 2026 🚀 Description This release introduces multi-instance support for the Userpilot SDK, along with stability improvements to enhance overall reliability and maintainability. ✨ Features * Added support for multiple Userpilot SDK instances. 🛠 Improvements * Expanded test coverage and added automated tests to improve stability and ensure long-term maintainability. *** ## 1.1.3 – Jun 7, 2026 **🚀 Description**\ This release introduces the full auto capture solution for Android applications. ✨ Features * Added automatic screen and interaction tracking for Android View and Jetpack Compose applications. *** ## 1.1.2 – May 21, 2026 **🚀 Description**\ This release includes compatibility updates between the mobile SDK and the Userpilot analytics/dashboard platform. 🛠 Improvements * Added compatibility support between the mobile SDK and Userpilot analytics/dashboard. * Enhanced screen targeting and improved migration flow from manual screen events to auto-captured screen events. *** ## 1.1.1 – May 10, 2026 **🚀 Description**\ This release includes compatibility updates between the mobile SDK and the Userpilot analytics/dashboard platform. 🛠 Improvements * Added compatibility support between the mobile SDK and Userpilot analytics/dashboard. * Enhanced screen targeting and improved migration flow from manual screen events to auto-captured screen events. *** ## 1.1.0 – Apr 8, 2026 **🚀 Description**\ This release introduces the auto capture solution for Android View-based apps, enabling automatic screen event tracking and improved analytics. 🛠 Improvements * Introduced automatic screen event capture for Android Views. * Captured events include screen views, user interactions, and hierarchy context. *** ## 1.0.16 – Feb 10, 2026 **🚀 Description**\ This is a patch release that focuses on improving stability and preventing lifecycle-related crashes. 🛠 Improvements * Fixed WindowLeaked and BadTokenException crashes by ensuring dialogs are only shown when the hosting Activity is in a valid state. *** ## 1.0.15 – Jan 20, 2026 **🚀 Description**\ Fixed a bug where SDK UI contents could be handled while the activity was in an invalid state, improving stability. 🛠 Improvements * Prevented SDK UI handling when the activity state is invalid. *** ## 1.0.14 – Dec 15, 2025 **🚀 Description**\ Improved stability and performance. 🛠 Improvements * Added submissionId parameter to all Survey events. *** ## 1.0.13 – Nov 10, 2025 **🚀 Description**\ Improved stability and performance. 🐛 Bug Fixes * Fixed potential crashes related to missing [Koin](https://insert-koin.io/) scope on restore SDK state from background. 🛠 Improvements * Added submissionId parameter to Survey List View and Step View to improve exported answers reports. *** ## 1.0.12 – Nov 2, 2025 **🚀 Description**\ Enhancements for smoother dependency management. 🛠 Improvements * Improved lazy dependency initializer issue. *** ## 1.0.11 – Oct 28, 2025 **🚀 Description**\ Improved user experience with better keyboard handling. 🛠 Improvements * Enhanced keyboard visibility detection. *** ## 1.0.10 – Sep 22, 2025 **🚀 Description**\ Add a configuration option to disable automatic push notification permission requests by the Userpilot SDK. *** ## 1.0.9 – Sep 21, 2025 **🚀 Description**\ Enhanced permission request handling for more reliable integration across different Android versions and wrappers. **📄 Motivation and Context** * Improved permission request behavior with clearer dialogs. *** ## 1.0.8 – Sep 18, 2025 **🚀 Description**\ Focused on enhancing SDK stability, maintainability, and developer experience through major refactoring, improved event handling, and fixes for critical race conditions. Introduced better permission handling, refined UI updates, and extended test coverage. **📄 Motivation and Context** * Refactored `ExperiencesPublisher`, `AnalyticsPublisher`, `Boot-up managers`, and `MulticastDelegate`. * Added Permissions handler. * Improved push notification request handling. * Enhanced keyboard detection and handling across screens. * Updated business logic for fake reload after updating user properties. * Improved pending content processing and next-experience handling. * Enhanced delay logic for content triggering and cancellation. * Updated flush queue logic for more reliable logout behavior. * Corrected diagnosis identify user change issue. * Implemented comprehensive unit tests for Userpilot. * Added markdown docs for `UserpilotAnalyticsListener` & `UserpilotExperienceListener`. * Updated sample app UI and screens. *** ## 1.0.7 – Aug 14, 2025 **🚀 Description**\ Enhanced anonymous user tracking for more accurate identification and improved documentation. **📄 Motivation and Context** * Refined anonymous user tracking logic to consistently track the same anonymous user per device. * Fixed a bug causing exceptions when converting string values to integers. * Updated the README with the latest usage instructions and feature details. *** ## 1.0.6 – Jul 16, 2025 **🚀 Description**\ Improved session handling and code quality, enhancing stability and test reliability. Introduced better logout behavior and fixed start session status in edge cases. **📄 Motivation and Context** * Improved logout flow to ensure proper session cleanup. * Fixed a bug related to start session status in specific edge cases. *** ## 1.0.4 – Jul 13, 2025 **🚀 Description**\ Enhanced experience logging of step indices. *** ## 1.0.3 – Jul 10, 2025 **🚀 Description**\ This release brings essential stability improvements and prepares the SDK for better integration in production environments. **📄 Motivation and Context** * Fixed issues related to screen event triggering to ensure accurate experience delivery. * Added ProGuard rules for smoother integration with apps using code shrinkers. * Enhanced experience logging for clearer visibility into step and state transitions. * Applied minor linting and code formatting enhancements for cleaner, maintainable code. *** ## 1.0.2 – Jun 29, 2025 **🚀 Description**\ Focused on code cleanup, enhancements, and improved reliability. **📄 Motivation and Context** * Refined the Userpilot initializer. * Improved push notification handling for consistent behavior. * Updated the README file with latest usage guidelines and improvements. *** ## 1.0.1 – Jun 17, 2025 **🚀 Description**\ Removed unnecessary sanitize payload properties. *** ## 1.0.0 – Jun 16, 2025 **🚀 Description**\ Updates the SDK with improved handling of event properties. **📄 Motivation and Context** * Enhanced processing of event properties for more accurate and consistent tracking. * Minor improvements to maintain SDK robustness and forward compatibility. # Multi-Instance Source: https://docs.userpilot.com/developer/installation/mobile/android/multi-instance Run multiple independent Userpilot instances in the same Android app Run multiple independent Userpilot instances in the same process — for example when an embedded vendor SDK and its host app both initialize Userpilot, each with their own account token. Each instance keeps its own analytics, autocapture routing, storage, and experience UI without interfering with the others. > [!TIP] > > **Note** > > If you only have one Userpilot instance in your app, nothing changes. `Userpilot(context, token)` and all existing autocapture APIs work as before. The rest of this guide is for SDK authors and host apps that need two or more Userpilot instances concurrently. *** ## Quick Reference | **Scenario** | **Use** | | - | - | | Single instance (host app only) | `Userpilot(context, token)` — `isDefault` defaults to `true`, so the host claims default automatically | | Vendor SDK that embeds Userpilot | `Userpilot(context, token) { isDefault = false … }` inside the vendor facade | | Re-initialize idempotently | `Userpilot(context, token)` again — returns the existing instance for the same token | Manual API calls (`identify`, `track`, `screen`, `triggerExperience`) route to whichever instance you call them on — no extra wiring is needed. `UserpilotConfig.isDefault` defaults to **`true`**. The host application does not need to set `isDefault = true` explicitly. Embedded vendor SDKs **must** set `isDefault = false`. *** ## Event Routing Every event — autocapture interaction, autocapture screen, or a manual `screen(_:)` / `track(_:)` — routes to one Userpilot instance using a single three-tier rule: 1. **Explicit `userpilot:` argument**, when the API exposes one (for example `UserpilotComposeNavigationTracker(..., userpilot = ...)`). 2. **Anchored owner**, when something in the UI hierarchy claims the originating UI (`attachToActivity(...)`, or `UserpilotOwner { … }` / `LocalUserpilotOwner` in Compose). 3. **Default fallback** — the instance that claimed `isDefault` (defaults to `true`). If none of those resolve to a live instance, the event is a silent no-op. There is no fan-out. In the single-instance case the only registered instance claims the default automatically (`isDefault` defaults to `true`), so nothing in your integration changes. *** ## Idempotent Factory `Userpilot(context, token)` is a get-or-create factory. `UserpilotConfig.isDefault` defaults to **`true`**, so the first live instance that does not opt out claims the default role automatically. Subsequent calls with the **same** token return the existing instance: ```kotlin theme={null} val a = Userpilot(context, "HOST") // claims default (isDefault defaults true) val b = Userpilot(context, "HOST") // a === b — same storage, socket, and autocapture coordinator. The DSL block on the second call is ignored. ``` A second instance with a **different** token that also leaves `isDefault` at its default and tries to claim the role while another instance already holds it will **not** displace the existing claimant — the SDK logs a warning and un-anchored events keep routing to whoever already claimed the role. Different tokens coexist as independent instances — each with its own socket, autocapture coordinator, and lifecycle callbacks. *** ## The Default Instance The SDK tracks the live instance that claimed the default role via `isDefault = true`. This fallback is used for un-anchored autocapture and screen-tracker call sites. ```kotlin theme={null} val hostUserpilot = Userpilot(context, "HOST") hostUserpilot.track("Added to Cart") ``` There is intentionally **no public default-instance getter**. For manual calls, keep and pass the `Userpilot` instance returned by the factory. In the single-instance case this is your only instance (`isDefault` defaults to `true`). In the multi-instance case the default role is typically held by the host app, which does not need to set `isDefault = true` explicitly. Resolution is **claim-based, not order-based**, so init order between correctly configured tenants does not matter. Embedded vendor SDKs **must opt out**: ```kotlin theme={null} val acmeUserpilot = Userpilot(context, "ACME") { isDefault = false enableScreenAutoCapture = true enableInteractionAutoCapture = true // anchor vendor UI — e.g. attachToActivity in each vendor Activity } ``` Only **one** instance can hold the default role at a time. An instance with `isDefault = true` (the default) claims that role on registration **when the role is unclaimed**. If the role is already held, the new claim is **rejected** (a warning is logged) and the **existing claimant keeps the role**. This is **claim-based**, not registration-order-based: registering first does not make an instance the default unless it also opts in with `isDefault = true`, and there is no first-registered fallback when every instance opts out. **Correct setup:** the host leaves `isDefault` at its default (`true`); every embedded vendor sets `isDefault = false`. The host then holds the default role **regardless of init order** — a vendor that opts out never competes for the slot even if it initializes first. **Misconfiguration:** if two instances both leave `isDefault = true`, whichever registers **while the role is still unclaimed** becomes the default; any later conflicting claim is rejected. Do not rely on init order — always set `isDefault = false` on embedded instances. If **no** instance claims the default role (every instance set `isDefault = false`), there is no SDK default fallback and un-anchored events are dropped. Keep `isDefault` at its default (`true`) on the host app. ### Host app + embedded vendor Once a vendor SDK opts out and anchors its own UI, the host app does nothing special for default resolution: ```kotlin theme={null} // Application.onCreate (host app) val hostUserpilot = Userpilot(context, "HOST") { enableScreenAutoCapture = true enableInteractionAutoCapture = true // no isDefault = true needed — defaults to true } // AcmeSDK (vendor SDK facade) val acmeUserpilot = Userpilot(context, "ACME") { isDefault = false enableScreenAutoCapture = true enableInteractionAutoCapture = true } ``` Both instances are now live and independent. *** ## Compose Routing Wrap each Compose subtree in `UserpilotOwner(yourUserpilot) { … }`. Nested wrappers shadow outer ones, so an embedded SDK can take ownership of its own UI even when the host declares a different owner at the root. ```kotlin theme={null} @Composable fun AcmeSdkUi(content: @Composable () -> Unit) { UserpilotOwner(userpilot = acmeUserpilot) { content() } } setContent { UserpilotOwner(userpilot = hostUserpilot) { NavHost(navController = navController, startDestination = "home") { composable("home") { HomeScreen() } composable("acmeSdkSurface") { AcmeSdkUi { AcmeScreens() } } } } } ``` `UserpilotComposeNavigationTracker` also accepts an explicit `userpilot:` argument: ```kotlin theme={null} UserpilotComposeNavigationTracker( navController = navController, userpilot = acmeUserpilot, ) ``` Resolution priority: 1. The `userpilot:` argument if non-null. 2. `LocalUserpilotOwner.current` if a `UserpilotOwner` is in scope. 3. The SDK default fallback. `Modifier.userpilotScreen(...)` resolves via `LocalUserpilotOwner` → SDK default fallback. *** ## View Routing For Android Views, ownership is anchored on the hosting Activity's decor view. Call `attachToActivity` from each owning Activity's `onCreate`: ```kotlin theme={null} class AcmeLoginActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) acmeUserpilot.attachToActivity(this) setContentView(R.layout.activity_login) } } ``` ### Interaction events Click, value-change, and text-input handlers resolve the owner via: 1. Parent-chain walk looking for the `userpilot_owner_tag` set by `attachToActivity`. 2. Context fallback — unwrap `View.context` to the hosting Activity and read the tag from its decor view (for example Dialog clicks). 3. Default fallback — the SDK default fallback handles the event. Dialogs and bottom-sheet dialogs are auto-tagged on first `Dialog.show()`, so subsequent inner clicks resolve cheaply via the parent chain. ### Screen events Each instance installs its own lifecycle callbacks, but a per-tracker gate ensures **only one tenant publishes a screen event** per Activity or Fragment: | **Anchored?** | **Behaviour** | | - | - | | Decor view tagged by `attachToActivity` | Only the anchored tenant fires. | | Not anchored | Only the SDK default fallback fires. | With multiple tenants registered, an un-anchored Activity emits exactly one screen event on the default — not one copy per tenant. *** ## Forwarding Events to the Host By default, autocapture events publish only on the instance that owns the originating UI. A vendor-owned screen or click is reported to the **vendor** tenant only. If the host app wants autocapture events that originate inside embedded vendor SDKs, opt in on the **host (default) instance**: ```kotlin theme={null} val hostUserpilot = Userpilot(context, "HOST") { enableScreenAutoCapture = true enableInteractionAutoCapture = true allowReceiveEventsFromExternalSource = true } ``` When enabled: * Autocapture events that resolve to a **non-default** instance are published to that instance **and** forwarded to the default instance. * The flag is read **only on the resolved default instance**. Setting it on a vendor instance has no effect. * Forwarded events are delivered unchanged through the host's publisher and `UserpilotAnalyticsListener`. There is no extra tagging. * The host's own events are never duplicated to itself, and forwarding never re-routes, so there is no fan-out loop. Routing still decides the owning tenant; forwarding only adds a copy to the default when the default opted in. *** ## Gradle Plugin Scope The Userpilot Gradle plugin instruments bytecode at build time. By default it uses `InstrumentationScope.ALL`, which transforms your module plus every dependency JAR/AAR — correct for end-user apps. If you ship an SDK embedded in someone else's app, restrict instrumentation to your module: ```kotlin theme={null} plugins { id("com.userpilot.plugin") version "" } userpilot { enabled.set(true) composeEnabled.set(true) viewEnabled.set(true) instrumentationScope.set("PROJECT") } ``` `PROJECT` (case-insensitive) restricts class-visitor transformation to the current module's compiled classes. The embedding app can apply its own plugin configuration independently. Any other value keeps the default `ALL` behavior. *** ## Routing Matrix | **Surface** | **Routing** | | - | - | | Manual API (`identify`, `track`, `screen`, `triggerExperience`, `endExperience`) | Routes to whichever instance you called the method on. | | Activity / Fragment screen autocapture | Anchored tenant if decor view is tagged, otherwise the SDK default fallback. One tenant per screen. | | Compose interaction autocapture | `UserpilotOwner` (`LocalUserpilotOwner`) → SDK default fallback. | | Compose navigation tracker | `userpilot:` arg → `LocalUserpilotOwner` → SDK default fallback. | | Compose `Modifier.userpilotScreen(...)` | `LocalUserpilotOwner` → SDK default fallback. | | View interaction autocapture | Parent-chain walk → context walk → SDK default fallback. | | View-less callbacks (TabHost, Material date/time pickers, popup menus) | SDK default fallback. | Each instance owns its own capture config (`enableInteractionAutoCapture`, `enableInteractionTextCapture`, `enableInteractionAccessibilityLabelCapture`, `enableInteractionValueCapture`), so two tenants can hold different autocapture preferences in the same process. *** ## Limitations * **View-less callbacks** cannot resolve an anchored owner. They always route to the SDK default fallback (the `isDefault` claimant), or are dropped when no instance holds that role. * **Same-process re-init with the same token** returns the existing instance and discards the DSL block on the second call. * **Activity screen events publish once** per Activity or Fragment — to whichever tenant owns the anchor, or to the default when un-anchored. If both tenants need the full session, re-emit the screen manually with `instance.screen("…")`. *** ## Complete Example ```kotlin theme={null} // Host Application.onCreate val hostUserpilot = Userpilot(this, "HOST") { enableScreenAutoCapture = true enableInteractionAutoCapture = true // isDefault defaults to true — claims default automatically } // AcmeSDK (vendor SDK facade) class AcmeSDK(context: Context) { val acmeUserpilot = Userpilot(context, "ACME") { isDefault = false enableScreenAutoCapture = true enableInteractionAutoCapture = true } fun identifyAcmeUser(id: String) = acmeUserpilot.identify(id) } hostUserpilot.identify("host-42") AcmeSDK(this).identifyAcmeUser("acme-99") hostUserpilot.track("Added to Cart") ``` # Push Notifications Source: https://docs.userpilot.com/developer/installation/mobile/android/push-notifications Set up and customize push notifications with the Userpilot Android SDK Configure Firebase and deep linking to receive, display, and handle Userpilot push notifications on Android. The Userpilot SDK supports push notifications to help you deliver targeted messages and enhance user engagement. ## Notification taps while offline In SDK 1.4.0 and later, a valid notification tap for the current user routes its deep link without waiting for an open analytics connection. The SDK records the open at tap time and persists it when the device is offline. Test notifications navigate without recording a push-open analytics event. The destination may still need a network connection to load its content. See [Navigation callbacks](./callbacks#navigation-handler) for startup routing and [Offline Mode](./features#offline-mode) for storage and delivery limits. ## Prerequisites Configure your Android push settings in the [Userpilot Settings Studio](https://run.userpilot.io/settings/mobile) before setting up push notifications in your app. To obtain the required keys and configuration details, refer to the [Android Push Notification Guide](/in-app-engagement/mobile-content/preparing-to-send-push-notifications#android-push-notifications-setup). ## Setup This guide assumes this is the first time you're adding push notification code to your project using Google services (Firebase Cloud Messaging). If your project is already configured for push, proceed to Step 2. **Step 1. Add Firebase** Follow the steps in the official Google documentation on [How to add Firebase to your project](https://firebase.google.com/docs/android/setup). **Step 2. Request Notifications Permission** Starting from Android 13 (API level 33), apps must explicitly request the `POST_NOTIFICATIONS` permission to display push notifications. The Userpilot SDK **automatically** requests push notifications permission on its own. **Step 3. Add the Userpilot Firebase Messaging Service** Firebase connects to your app through a ``. Add the following to your `AndroidManifest.xml`: ```xml theme={null} ``` If your project already has a child of `FirebaseMessagingService`, you can leave it as is and instead plug in `UserpilotFirebaseMessagingService` to your service class. ```kotlin theme={null} import com.userpilot.pushNotifications.UserpilotFirebaseMessagingService import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage class CustomFirebaseMessageService : FirebaseMessagingService() { override fun onMessageReceived(message: RemoteMessage) { if (UserpilotFirebaseMessagingService.handleMessage(baseContext, message)) { // handled as Userpilot message return } // not Userpilot message super.onMessageReceived(message) } override fun onNewToken(token: String) { // sets new token from the callback UserpilotFirebaseMessagingService.setToken(token) super.onNewToken(token) } } ``` **Step 4. Deep Linking** To ensure your app **opens automatically when a Userpilot push notification is clicked**, you need to configure an `intent-filter` in your app's \*\*entry \*\*`Activity` in `Manifest.xml` . This configuration enables deep linking based on a custom scheme defined in your app. **Define the deep-link scheme.** Use `userpilot-` followed by the lowercased token passed to the SDK, including `stg-` for staging tokens. For example, `NX-12345678` uses `userpilot-nx-12345678`. Leave `userpilot_custom_scheme` empty to use this default. If you set a custom value, register the same value in the manifest. See [Custom URL scheme](./custom_scheme) for the complete setup. **Update Manifest** file to include the intent-filter as below. Make sure to set `host="sdk"` . ```xml theme={null} ``` **Step 5. Handling Notification Intent** Inside your entry point activity, pass the intent to the Userpilot SDK to handle it. ```kotlin theme={null} override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) handleIntentUserpilotIntent(intent) } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) if (handleIntentUserpilotIntent(intent)) return } /** If not handled by Userpilot, then let app handle it. */ private fun handleIntentUserpilotIntent(intent: Intent): Boolean { return userpilot.onNewIntent(intent) } ``` **Step 6. Customization** The Userpilot SDK allows customization for how messages are delivered. These properties are set as `` values. To change them, create a file under `res/values` and set the desired values. ```xml theme={null} @drawable/ic_notification #6765E8 true true true com.userpilot.general.channel General Default channel used for app messages 3 ``` # Auto Capture Source: https://docs.userpilot.com/developer/installation/mobile/capacitor/auto-capture Configure auto capture for the Userpilot Capacitor plugin Configure automatic screen and interaction capture for Ionic Capacitor applications using the Userpilot Capacitor plugin. The Userpilot Capacitor plugin can automatically capture screens and interactions in your Ionic app and forward them to the native Userpilot SDKs, enabling analytics and engagement without manual screen and event tracking. Because Ionic renders inside a single native WebView, auto capture runs entirely in the DOM at runtime — there is **no build-time instrumentation** to configure. The engine detects route changes and interactions and bridges them to the native SDK, so the resulting events are indistinguishable from native auto capture on the backend. *** ## SDK Configuration Auto capture is **off by default**. Auto capture behavior is configured through the `config` options passed to `Userpilot.initialize`. ```ts theme={null} import { Userpilot } from '@userpilot/capacitor'; await Userpilot.initialize({ token: '', config: { logging: true, enableScreenAutoCapture: true, enableInteractionAutoCapture: true, enableInteractionTextCapture: true, enableInteractionAccessibilityLabelCapture: true, enableInteractionValueCapture: false, }, }); ``` | **Option** | **Type** | **Default** | **Description** | | - | - | - | - | | `enableScreenAutoCapture` | `boolean` | `false` | Enables automatic screen tracking. Route/page changes are detected via the History API, `popstate`/`hashchange`, and Ionic route events. | | `enableInteractionAutoCapture` | `boolean` | `false` | Enables automatic interaction capture. Clicks and other interactions are captured as events. | | `enableInteractionTextCapture` | `boolean` | `true` | If `false`, user-visible text/labels and text-field metadata are omitted from captured interaction payloads. When enabled, typed input values are never captured verbatim. | | `enableInteractionAccessibilityLabelCapture` | `boolean` | `true` | If `false`, accessibility labels (`aria-label` / `title`) are not captured. | | `enableInteractionValueCapture` | `boolean` | `false` | Enables value payload capture: `is_checked` (checkbox/switch/toggle), selected date/time, slider/range values, and `selected_index`/`selected_value` on selects and menus. List/select selections always include `selected_index`; `selected_value` is only included when this flag is `true`. Per-element overrides have higher priority. | | `maxHierarchyDepth` | `number` | `30` | Maximum number of ancestry nodes included in the hierarchy path. | When either `enableScreenAutoCapture` or `enableInteractionAutoCapture` is enabled, the DOM auto capture engine starts automatically after `initialize`. > [!TIP] > > **Note** > > Auto Capture automatically tracks screen views and ignores manually sent `screen` events while enabled. > > If you are migrating from manual screen tracking, please review the migration guide [here](https://docs.userpilot.com/data-events/mobile-screen-tracking/mobile-screen-auto-capture#important). *** ## Screen Capture When `enableScreenAutoCapture` is `true`, the engine reports a screen whenever the route changes. The screen name is `location.pathname + location.search`, sent as-is — including the root path `/` — for example `/post-details?id=10`. Consecutive identical names are de-duplicated. > [!TIP] > > **Manual screens** > > Manual `screen()` calls are ignored whenever `enableScreenAutoCapture` is configured — manual and automatic screen tracking remain mutually exclusive, including while auto capture is stopped. To send screens manually, leave `enableScreenAutoCapture: false`. *** ## Interaction Capture When `enableInteractionAutoCapture` is `true`, the engine captures the following DOM interactions through a small set of delegated, capture-phase listeners on `document`: | **Interaction type** | **Emitted for** | | - | - | | `tap` | Buttons, links, `ion-button`, `ion-back-button`, `ion-menu-button` (hamburger), `ion-fab-button`, list items, cards, chips, and elements with `[role="button"]`. | | `tab_selected` | `ion-tab-button`, `[role="tab"]`. | | `text_field_changed` | `input`, `textarea`, `ion-input`, `ion-textarea`, `ion-searchbar`, `contenteditable` (debounced; typed text is never captured — only `has_text` / `text_length`). | | `checkbox_selected`, `radio_button_selected`, `switch_changed` | Checkbox, radio, and switch/toggle changes. | | `slider_changed` | Range/slider changes. | | `selection_change` | Select/dropdown and segment selection. | | `date_picker_changed`, `time_picker_changed` | Date and time input changes. | | `view_presented` | Modal, alert, popover, action-sheet, and dialog presentation. | Each interaction maps to a high-level analytics category (`tap`, `text_change`, `selection_change`, `value_change`, `view_presented`) — that is the event name that reaches your analytics backend. ### Component Value Gating By default, user-input values are stripped from event payloads — `is_checked`, selected dates and times, slider/range values, and dropdown selections do not leave the device. To include them, pass `enableInteractionValueCapture: true` in `config`. Position fields such as `selected_index` are always captured because they don't reveal user input; `selected_value` is only included when value capture is enabled. Per-element overrides have higher priority. *** ## Privacy Controls Global flags govern what is captured: * **Text capture** — `enableInteractionTextCapture` * **Accessibility label capture** — `enableInteractionAccessibilityLabelCapture` * **Value payload capture** — `enableInteractionValueCapture` Disabling text capture removes text-derived properties from the payload. Redaction replaces an existing text/title with `****`; when no text exists, the property is omitted. Redaction is scoped to `target_text` (and the `dialog_title` of a presented surface). It does **not** mask `accessibility_label` or the hierarchy's `attr__desc` — accessibility labels are developer-authored, so they are controlled only by `enableInteractionAccessibilityLabelCapture`. ### Event Metadata Keys Common fields on interaction events: | **Key** | **Meaning** | | - | - | | `target_view_class` | Simple component name, e.g. `IonButton` (Ionic components) or the tag name (`button`) for plain HTML elements. | | `target_class` | Package-qualified name, e.g. `@ionic/react.IonButton`. The framework package (`@ionic/react` / `@ionic/vue` / `@ionic/angular`) is detected at runtime; plain HTML elements report their tag name. | | `target_text` | Primary visible label / title for the control (redacted when required). | | `accessibility_label` | Flattened accessibility metadata (only when accessibility-label capture is enabled). | | `target_resource_id` | The target element `id` (Ionic auto-generated ids like `ion-r-0` are filtered out). | | `view_tag` | Element tag metadata. | | `class_name` | Developer-authored element classes (not part of the hierarchy). | | `hierarchy` | Semicolon-separated leaf-to-root path, with the screen name appended. | | `raw_interaction_type` | Low-level interaction name, e.g. `tap`, `checkbox_selected`. | | `is_checked`, `selected_value`, `selected_index`, `values`, `has_text`, `text_length`, `selected_date`, `selected_time` | Interaction-specific source properties. Value properties are gated by `enableInteractionValueCapture`. | ### `UserpilotRedactText` Redaction replaces the captured `target_text` (and the `dialog_title` of a presented surface) with `****`. The interaction event still fires — the element is still identified by type, position, and structure — only the human-readable text is masked. Accessibility labels are not masked by redaction; they are controlled only by `enableInteractionAccessibilityLabelCapture`. The programmatic API accepts either an `Element` or a CSS selector and applies to the element and its subtree: ```ts theme={null} Userpilot.UserpilotRedactText('#credit-card-form'); Userpilot.UserpilotRedactText(document.querySelector('.profile')!); ``` A declarative HTML attribute is also supported: ```html theme={null}
...
``` ### `UserpilotIgnoreInteractions` Ignoring skips **interaction** events whose hit-tested element lies inside the subtree (taps, value changes, text-field updates, etc.). Route-driven **screen** events are **not** suppressed — they come from route detection, not from element hit testing. The host app's own handlers still run — only auto capture ignores those interactions. The programmatic API accepts either an `Element` or a CSS selector and applies to the element and its subtree: ```ts theme={null} Userpilot.UserpilotIgnoreInteractions('.admin-panel'); ``` A declarative HTML attribute is also supported: ```html theme={null}
...
``` *** ## Runtime Controls Auto capture starts automatically from `initialize` (based on config). To stop or resume collection at runtime without re-passing the config — for example a "private mode" — use: ```ts theme={null} // Pause screen + interaction capture. await Userpilot.stopAutoCapture(); // Resume with the same config supplied to initialize. await Userpilot.resumeAutoCapture(); ``` While stopped, no screen or interaction events are recorded regardless of other configuration. Pausing auto capture does not enable manual `screen()` calls: when `enableScreenAutoCapture` is configured, manual and automatic screen tracking remain mutually exclusive, including while auto capture is stopped. # Callbacks Source: https://docs.userpilot.com/developer/installation/mobile/capacitor/callbacks Configure navigation, analytics, and experience callbacks Configure how your app handles navigation, analytics events, and experience lifecycle callbacks. ## Navigation Listener Called when a deep link is triggered from a Userpilot experience or notification. Use this to handle in-app routing. ```js theme={null} url: String — The deep link URL to be handled by your app. ``` ## Analytics Listener Called when an analytics event is triggered by the Userpilot SDK. Use this to mirror or log SDK-level events into your analytics system. Parameters: ```js theme={null} analytic: "Identify" | "Screen" | "Event" value: String — Event value, if any. properties: Map — Additional metadata for the event. ``` ## Experience Listener Called when an experience or its step changes state. Includes two callback types: **onExperienceStateChanged** Triggered when the overall state of an experience changes. ```js theme={null} experienceId?: Int — Unique ID of the experience (optional) experienceType: "Flow" | "Survey" | "NPS" experienceState: "Started" | "Completed" | "Dismissed" | "Skipped" | "Submitted" ``` **onExperienceStepStateChanged** Triggered when the state of a specific step within an experience changes. ```js theme={null} stepId: Int — Unique identifier of the step experienceId: Int — ID of the parent experience experienceType: "Flow" | "Survey" | "NPS" stepState: "Started" | "Completed" | "Dismissed" | "Skipped" | "Submitted" step?: Int — Step index (optional) totalSteps?: Int — Total number of steps in the experience (optional) ``` **Note:** Both callbacks are sent under the same event name: UserpilotExperienceEvent ```js theme={null} useEffect(() => { if (Capacitor.isNativePlatform()) { let analyticsListener: PluginListenerHandle let experienceListener: PluginListenerHandle let navigationListener: PluginListenerHandle const setupListeners = async () => { analyticsListener = await UserpilotPlugin.addListener( 'UserpilotAnalyticsEvent', (event: any) => console.log('Analytics Event:', JSON.stringify(event, null, 2)) ) experienceListener = await UserpilotPlugin.addListener( 'UserpilotExperienceEvent', (event: any) => console.log('Experience Event:', JSON.stringify(event, null, 2)) ) navigationListener = await UserpilotPlugin.addListener( 'UserpilotNavigationEvent', (event: any) => { console.log('Navigation Event', JSON.stringify(event, null, 2)) if (event?.url) handleDeepLink(event.url) } ) } setupListeners() return () => { if (analyticsListener) analyticsListener.remove() if (experienceListener) experienceListener.remove() if (navigationListener) navigationListener.remove() } } }, []) ``` # Custom URL Scheme Source: https://docs.userpilot.com/developer/installation/mobile/capacitor/custom_scheme Register and handle the Userpilot custom URL scheme in Capacitor to preview draft experiences and route push notification taps. The Userpilot Capacitor plugin supports a custom URL scheme that lets you preview experiences on a real device before publishing them, and that carries push notification taps back into your app. Setting it up is two steps: register the scheme on each platform so the OS routes matching links to your app, then forward the incoming URL to the Userpilot SDK from your web layer. *** ## Prerequisites Your app must be configured to handle [deep links](https://capacitorjs.com/docs/guides/deep-links) on both Android and iOS. *** ## Register the custom URL scheme Replace `APP_TOKEN` with your app's Userpilot token, which you can obtain from your [Environments Page](https://run.userpilot.io/environment). For example, if your token is `NX-12345678`, your scheme value is `userpilot-nx-12345678`. For iOS, `uri-scheme` writes the `Info.plist` entry for you: ```bash theme={null} npx uri-scheme add userpilot-APP_TOKEN --ios ``` For Android, add the intent filter to `android/app/src/main/AndroidManifest.xml` on the main Activity. Set `android:launchMode="singleTop"` so an incoming link is delivered to the running Activity rather than creating a second instance of it: ```xml theme={null} ``` Keep `android:host="sdk"` in the filter. The native Userpilot SDK validates the URL host before handling a preview or notification link. Run `npx cap sync` after editing native configuration. *** ## Handle the custom URL scheme Listen for the `appUrlOpen` event from `@capacitor/app` and pass each URL to `Userpilot.didHandleURL`. > [!WARNING] > > **`didHandleURL` is asynchronous and resolves to an object** > > The signature is `didHandleURL({ url }): Promise<{ handled: boolean }>`. You must `await` it and read the `handled` property. Testing the returned value directly always looks truthy, because an unawaited promise is an object, so your own link handling never runs. ```tsx theme={null} import React, { useEffect } from 'react'; import { App, URLOpenListenerEvent } from '@capacitor/app'; import { Userpilot } from '@userpilot/capacitor'; const AppUrlListener: React.FC = () => { useEffect(() => { const listener = App.addListener('appUrlOpen', async (event: URLOpenListenerEvent) => { const { handled } = await Userpilot.didHandleURL({ url: event.url }); if (!handled) { // Handle a non-Userpilot URL } }); return () => { listener.then((handle) => handle.remove()); }; }, []); return null; }; export default AppUrlListener; ``` Mount the listener once, near the root of your app, so it's active before any link arrives: ```tsx theme={null} ... ``` For more on link handling in Ionic apps, see the Capacitor documentation on [deep links](https://capacitorjs.com/docs/guides/deep-links). *** ## Supported links The SDK only claims a URL when the scheme matches your token and the host is `sdk`. Every other URL is passed back to your app. | **Link** | **Purpose** | | - | - | | `userpilot-{token}://sdk/experience_preview/{experience_id}` | Previews a draft Flow or Survey. This is the link behind the Builder's QR code. | | `userpilot-{token}://sdk/notification` | Routes an Android push notification tap to the SDK. Query parameters carry the notification payload. | > [!NOTE] > > On iOS, notification taps are handled natively by the SDK and never reach the `appUrlOpen` event, so follow [Push Notifications](./push-notifications) for that setup. *** ## Preview an experience with a QR code Userpilot can render a draft experience on your device before you publish it, so you can check layout, copy, and behavior on a real screen. Preview is supported for **Flows** and **Surveys**. The preview is delivered over the custom URL scheme. It works once your app is installed on the device and the scheme is registered and handled as described above. **1. Open the experience** Open a Flow or Survey in the Experience Builder and enter **Edit Mode**. **2. Start the preview** Select **Preview** in the top-right corner of the builder. A popup with a **QR code** appears. **3. Scan the QR code** Scan the code with your device camera. Your app opens and the experience appears. Points to keep in mind: * You don't need to apply or save your changes before previewing them. * The experience doesn't need to be live. * A preview bypasses the experience's targeting and frequency settings, so it appears even when the current user wouldn't normally qualify for it. * Once you close the experience, the app resumes normal behavior. *** ## Test push notifications You can send a test push to a specific user from the Userpilot dashboard and verify delivery and tap handling on a real device without sending anything to your wider audience. The notification doesn't need to be live, so you can test it while it's still a draft. * Test pushes work in both the staging and production environments. * Test pushes are **not** counted as real sends, and they're excluded from analytics. * Test pushes bypass the notification's targeting and frequency settings. For the dashboard steps, see [Mobile push notifications](/in-app-engagement/mobile-content/mobile-push-notifications). Because a notification tap arrives as a deep link on Android, a test push also exercises your `appUrlOpen` handling there. If the notification appears but tapping it does nothing, the scheme registration is the first thing to check. *** ## Test a deep link from the command line Open a link directly to confirm your scheme is registered and your listener runs, without going through the Builder: ```bash theme={null} # Android adb shell am start -a android.intent.action.VIEW \ -d "userpilot-nx-12345678://sdk/experience_preview/12345" # iOS Simulator xcrun simctl openurl booted "userpilot-nx-12345678://sdk/experience_preview/12345" ``` Replace the scheme with your own token and `12345` with a real experience ID. *** ## Troubleshooting > **Nothing happens when I scan the QR code** > > Confirm the registered scheme matches your token exactly, lowercased. Then confirm you ran `npx cap sync` and rebuilt, since a scheme is native configuration and won't appear in an older build. Verify with the commands above. > **My own link handling never runs** > > You're most likely not awaiting `didHandleURL`. It returns a promise that resolves to `{ handled: boolean }`, so `if (!result)` is always false against the promise object. Destructure `handled` from an awaited call, as shown above. > **The app opens but the experience doesn't appear** > > The OS routed the link, so registration is correct. Check that `AppUrlListener` is mounted at the root of your app rather than inside a page that may not be rendered when the link arrives, and confirm the SDK is initialized with the same token that generated the QR code. > **Tapping an Android push notification does nothing** > > Notification taps travel over this same URL scheme on Android, so they need the intent filter and the `appUrlOpen` forwarding described above. Confirm the host in your filter is exactly `sdk`. > **Preview works in production but not in staging** > > Each environment has its own token, and the scheme follows the token. Confirm the staging build registers the scheme for the token it initializes the SDK with. [**For any questions or concerns please reach out to support@userpilot.com**](mailto:support@userpilot.com) # Userpilot SDK features Source: https://docs.userpilot.com/developer/installation/mobile/capacitor/features Offline analytics support, supported SDK versions, and local event storage limits. ## Offline Mode Offline Mode is available in SDK version **1.3.0 and later**. The SDK stores these events on the device while offline: * Identify calls, screen views, custom events, and Auto Capture events. * Flow views, dismissals, completions, and step views and completions. * Survey views, dismissals, completions, responses, and step views, skips, and submissions. * NPS views, dismissals, and feedback submissions. * Push-notification opens, excluding test notifications. Stored events survive app restarts and are sent together in a batch ordered by stored timestamps when the connection returns, before pending live events. Identify a user, or call `anonymous()`, before collecting events. Experience interactions and push opens are not persisted without a current user ID. Offline Mode is always enabled and requires no additional configuration. ### Limitations * All supported event types share local storage of up to **3 MB** or **5,000 events**, whichever limit is reached first. * When either limit is reached, new events are not stored until space becomes available. * Switching accounts or logging out clears stored events. *** [**For any questions or concerns, please reach out to support@userpilot.com**](mailto:support@userpilot.com) # Fonts Source: https://docs.userpilot.com/developer/installation/mobile/capacitor/fonts Configure custom fonts in Ionic-Capacitor for the Userpilot plugin To use custom fonts in Userpilot Experiences that are rendering inside of Ionic applications, those fonts must be registered with the native iOS and Android projects for your app. Please follow the instructions in each section below to register the fonts. ## Naming Font Files The file name for a font must match the PostScript name of the font for your flows to properly load the custom font on both Android and iOS. This is because in native Android apps, fonts are referenced by their resource name which is the filename[^1], while in native iOS apps, fonts are referenced by their PostScript name[^2]. On macOS, you can find the PostScript name of a font by opening it with the Font Book app and selecting the Font Info tab. ## iOS 1. Open your Ionic application's iOS project workspace in Xcode - for example ./ios/App/App.xcworkspace 2. Under the root of the `App` project, create a `Fonts` group, and add all of the font files into this group. ![Fonts are added to the Xcode project for the app](https://mintcdn.com/userpilot/HDl9JwKjKVpcPaA7/developer/installation/mobile/assets/images/xcode-add-fonts.png?fit=max\&auto=format\&n=HDl9JwKjKVpcPaA7\&q=85\&s=d27af0f7fd307feb2de3b6353800755d) 3. Ensure that the font files are included in the target membership of the app project. ![Font files are included in the app's build target](https://mintcdn.com/userpilot/HDl9JwKjKVpcPaA7/developer/installation/mobile/assets/images/xcode-font-target-membership.png?fit=max\&auto=format\&n=HDl9JwKjKVpcPaA7\&q=85\&s=28ee7fff9482011a1a6efbcdde78b110) 4. Open the `Info.plist` in the project and add a section for "Fonts provided by application". Add a row for each font and specify the matching font file name. ![Font files are included in the Info.plist for the app](https://mintcdn.com/userpilot/HDl9JwKjKVpcPaA7/developer/installation/mobile/assets/images/xcode-font-plist.png?fit=max\&auto=format\&n=HDl9JwKjKVpcPaA7\&q=85\&s=2196164e7b082a50a00b08ee4e843098) In source code view (xml) this section will look like: ```xml theme={null} UIAppFonts Merriweather-Bold.ttf Merriweather-Regular.ttf Merriweather-BoldItalic.ttf ``` For more information, please refer to [iOS Reference Documentation](https://developer.apple.com/documentation/uikit/text_display_and_fonts/adding_a_custom_font_to_your_app) ## Android 1. Open your Ionic application's Android project in Android Studio - for example ./android 2. Under the `app` module, navigate to the `res` (resources) folder, an add a `font` subdirectory, if not already there. 3. Add the font files into the `font` resource directory. Note, Android resource naming is all lower case, and any `-` should be replaced with `_`. ![Font files are included in the Android app's resources](https://mintcdn.com/userpilot/-xylCyJcypEDYMlN/developer/installation/mobile/assets/images/android-font-resource.png?fit=max\&auto=format\&n=-xylCyJcypEDYMlN\&q=85\&s=15301be73e95cc32b065b5a67e6deded) For more information, please refer to [Android Reference Documentation](https://developer.android.com/develop/ui/views/text-and-emoji/fonts-in-xml) ## Build and Test After the above steps are completed, rebuild your application. Create an Experience in the Userpilot Mobile Builder that specifies a custom font, and try it out by using either the builder preview or publishing. [^1]: "The resource name, which is either the filename excluding the extension..." [https://developer.android.com/guide/topics/resources/providing-resources](https://developer.android.com/guide/topics/resources/providing-resources) [^2]: "When retrieving the font with `custom(_:size:)`, match the name of the font with the font’s PostScript name." [https://developer.apple.com/documentation/swiftui/applying-custom-fonts-to-text/#Apply-a-font-supporting-dynamic-sizing](https://developer.apple.com/documentation/swiftui/applying-custom-fonts-to-text/#Apply-a-font-supporting-dynamic-sizing) # Ionic Capacitor plugin Source: https://docs.userpilot.com/developer/installation/mobile/capacitor/installation Install Userpilot on Ionic-Capacitor Application Userpilot Capacitor Plugin enables you to capture user insights and deliver personalized in-app experiences in real time. With just a one-time setup, you can immediately begin leveraging Userpilot’s analytics and engagement features to understand user behaviors and guide their journeys in-app. This document provides a step-by-step walkthrough of the installation and initialization process, as well as instructions on using the SDK’s public APIs. *** ## Prerequisites #### Android Your application's `build.gradle` must have a `compileSdk` of 35+ and `minSdk` of 23+. Your project should use the Android Gradle Plugin (AGP) version 8.6+. ```kotlin theme={null} android { compileSdk 35 defaultConfig { minSdk 23 } } ``` #### iOS Your application must target iOS 13+ to install the SDK, Update the iOS project xcodeproj to set the deployment target, if needed. In the application's Podfile, include at least this minimum version. ```bash theme={null} # Podfile platform :ios, '14.0' ``` *** ## Installation Add the Userpilot Capacitor Plugin dependency to your application. 1. In your app's root directory, install the Userpilot Capacitor Plugin. You can fetch the latest version from [here](https://www.npmjs.com/package/@userpilot/capacitor). Release notes are available [here](./mobile-capacitor-release-notes). ```bash theme={null} npm install @userpilot/capacitor npx cap sync ``` Userpilot supports CocoaPods and Swift Package Manager (SPM). Capacitor installs the native Userpilot dependency through the plugin, so you do not need to add it separately. To migrate an existing CocoaPods app or set up a new SPM project, follow [Updating to Swift Package Manager](./updating-to-spm). *** ## Initialization To use Userpilot, initialize it once in your Application class. This ensures the SDK is ready as soon as your app starts. Update your Application class. Replace `` with your Application Token, which can be fetched from your [Environments Page](https://run.userpilot.io/environment). ```javascript theme={null} import { Userpilot } from '@userpilot/capacitor'; Userpilot.initialize({ token: 'APP_TOKEN', config: { logging: true } }) ``` *** ## Using the SDK Once initialized, the SDK provides straightforward APIs for identifying users, tracking events, and screen views. ### Identifying Users This API is used to identify unique users and companies (groups of users) and set their properties. Once identified, all subsequent tracked events and screens will be attributed to that user. > [!WARNING] > > **Important** > > It’s crucial to call the Userpilot identify function; without it, Userpilot won’t be able to recognize your users, and mobile content won’t be displayed to them. **Recommended Usage:** * **On user authentication (login):** Immediately call `identify` when a user signs in to establish their identity for all future events. * **On app launch for authenticated users:** If the user has a valid authenticated session, call `identify` at app launch. * **Upon property updates:** Whenever user or company properties change. ```javascript theme={null} // API identify(options: IdentifyOptions) // Parameters export interface IdentifyOptions { userId: string; properties?: object; company?: object; } // Example userpilot.identify( userId: '', properties: { 'name' : 'John Doe', 'email' : 'user@example.com', 'created_at' : '2019-10-17', 'role' : 'Admin' }, company: { 'id' : '', 'name': 'Acme Labs', 'created_at': '2019-10-17', 'plan' : 'Free' } ); ``` **Properties Guidelines** * Key `id` is required in company properties, to identify a unique company. * Userpilot supports String, Numeric, and Date types. * Make sure you’re sending date values in ISO8601 format. * If you are planning to use Userpilot’s localization features, make sure you are passing user property `locale_code` with a value that adheres to ISO 639-1 format. * Userpilot’s reserved properties’ have pre-determined types and improve profiles interface in the dashboard: * Use key `email` to pass the user’s email. * Use key `name` to pass the user’s or company’s name. * Use key `created_at` to pass the user’s or company’s signed up date. > [!TIP] > > Notes > > * Ensure the User ID source is consistent across Web, Android, and iOS. > * While properties are optional, setting them enhances Userpilot’s segmentation capabilities. ### Tracking Screens (Required) Calling screen is crucial for unlocking Userpilot’s core engagement and analytics capabilities. When a user navigates to a particular screen, invoking screen records that view and triggers any eligible in-app experiences. Subsequent events are also attributed to the most recently tracked screen, providing context for richer analytical insights. For these reasons, we strongly recommend tracking all of your app’s screen views. ```javascript theme={null} // API screen(options: ScreenOptions) // Parameters export interface ScreenOptions { title: string; } // Example Userpilot.screen({ title: 'Profile' }) ``` ### Tracking Events Log any meaningful action the user performs. Events can be button clicks, form submissions, or any custom activity you want to analyze. Optionally, you can pass metadata with the event to provide specific context. ```javascript theme={null} // API track(options: TrackOptions) // Parameters export interface TrackOptions { name: string; properties?: object; } // Example Userpilot.track({ name: 'Added to Cart', properties: {'itemId' : 'sku_456', 'price' : 29.99}}) ``` ### Logging Out When a user logs out, call `logout()` to clear the current user context. This ensures subsequent events are no longer associated with the previous user. ```javascript theme={null} userpilot.logout() ``` ### Anonymous Users If a user is not authenticated, call `anonymous()` to track events without a user ID. This is useful for pre-signup flows or guest user sessions. ```javascript theme={null} userpilot.anonymous() ``` > [!WARNING] > > Anonymous users count towards your Monthly Active Users usage. Consider your MAU limits before using this API. *** ## Experience Triggers a specific experience programmatically using it's ID. This API allows you to manually initiate an experience within your application. ```javascript theme={null} // API triggerExperience(options: ShowOptions) // Parameters export interface ShowOptions { experienceId: string; } // Example userpilot.triggerExperience({ experienceId: '' }) ``` To end current active experience ```javascript theme={null} userpilot.endExperience() ``` ## Configuration (Optional) | **Parameter** | **Type** | **Description** | | - | - | - | | logging | Boolean | Enable or disable logs for the SDK. **Default: false** | | disableRequestPushNotificationsPermission | Boolean | Disable push notifications permission request by the SDK. **Default: false** | | useInAppBrowser | Boolean | Determines whether to open URLs inside CustomTabsIntent/SFSafariViewController or use the system browser. **Default: false** | ## Privacy Manifest This plugin uses the native Userpilot iOS SDK. When you ship to the App Store, follow the iOS [Privacy Manifest](../ios/installation#privacy-manifest) guidance. # Userpilot Capacitor SDK Release Notes Source: https://docs.userpilot.com/developer/installation/mobile/capacitor/mobile-capacitor-release-notes Release history for the Userpilot Capacitor mobile SDK. ## 1.3.0 – Sep 29, 2026 🚀 Overview This release adds persistent offline analytics, preserves screens reported during startup, and improves Android build compatibility. 🐛 Bug Fixes * Preserved the latest manual screen reported before initialization completed and sent it after successful initialization, without duplicating screen Auto Capture. * Fixed Android build configuration failures with Android Gradle Plugin 9. ✨ Features * Added persistent offline storage for identify calls, screen views, custom events, and Auto Capture events. * Added automatic batch sending of stored events when the connection returns, including events saved before an app restart. Offline analytics requires no additional configuration. 📦 Dependency Updates * Upgraded the Userpilot native SDK dependencies to iOS 1.4.0 and Android 1.4.0. See [Offline Mode](/developer/installation/mobile/capacitor/features#offline-mode) for supported behavior and storage limits. *** ## 1.2.0 – Sep 29, 2026 🚀 Overview This release adds deep-link handling for experience previews and push notifications, supports nested Auto Capture privacy rules, and improves initialization and logout reliability. ✨ Features * Added `Userpilot.didHandleURL({ url })` to forward custom URL scheme links to Userpilot. * Queued deep links received before or during initialization and returned unhandled links to the app for routing. * Added an optional boolean to the privacy helpers, allowing a child element to override a parent rule. Existing one-argument calls remain supported. 🐛 Bug Fixes * Fixed iOS compilation against the supported native SDK and prevented a crash when logging out. * Rejected Android SDK calls made before initialization instead of crashing the app. * Corrected the web fallback to report URLs as unhandled so the app can route them. 📦 Dependency Updates * Upgraded the Userpilot native SDK dependencies to iOS 1.3.0 and Android 1.3.0, including experience previews and test push notifications. 🛠 Migration * Run `npx cap sync` after upgrading to refresh the native dependencies. * Configure the [Custom URL Scheme](/developer/installation/mobile/capacitor/custom_scheme) for preview and push-notification links. * Use `Userpilot.UserpilotRedactText(target, false)` or `Userpilot.UserpilotIgnoreInteractions(target, false)` to override an inherited privacy rule. *** ## 1.1.0 – Aug 3, 2026 🚀 Overview Added support for the Auto Capture feature, enabling automatic tracking of user interactions and screens with minimal integration effort. ✨ New Features * Added support for Auto Capture. * Automatically captures supported user interactions and screen views without requiring manual tracking. *** ## 1.0.8 – Jan 6, 2026 🚀 Overview Resolved build failure issues. 🐛 Bug Fixes * Fixed Android build configuration issues. *** ## 1.0.7 – Dec 15, 2025 🚀 Overview Improve reporting for surveys. 🛠 Improvements * Added submissionId parameter to all Survey events. 📦 Dependency Updates * Upgraded Userpilot native SDK dependencies - iOS 1.0.15, Android 1.0.14. *** ## 1.0.6 – Dec 7, 2025 🚀 Overview Update Capacitor plugin to support Capacitor v7. 📦 Dependency Updates * Upgraded @capacitor/core, @capacitor/ios, and @capacitor/android to v7. *** ## 1.0.5 – Nov 12, 2025 🚀 Overview Improve reporting for surveys. 🛠 Improvements * Added submissionId parameter to Survey List View and Step View to improve exported answers reports. 📦 Dependency Updates * Upgraded Userpilot native SDK dependencies - iOS 1.0.14, Android 1.0.13. *** ## 1.0.4 – Nov 3, 2025 🚀 Overview This release introduces support for the latest Android platform requirements, improves media handling, and enhances stability across the SDK. 🐛 Bug Fixes * Fix Gif image in mobile content. * Resolved issue where lazy initializer dependencies not intialized. 📦 Dependency Updates * Upgraded Userpilot Android SDK to v1.0.12. *** ## 1.0.3 – Sep 25, 2025 **🚀 Description**\ Updated native SDK dependencies, introduced new configuration options, refined core logic, and resolved multiple issues to improve stability, consistency, and scalability of the Userpilot SDK. 📄 Motivation and Context * Upgrade native SDK dependencies, iOS 1.0.12, Android 1.0.10 * Added a configuration option to disable automatic push notification permission requests. * Enhanced user property update handling with smoother fake reload support. * Fixed processing of pending content to ensure consistent delivery. * Ensured screen events are requested after returning from background when active content is visible. * Refined logout flush queue logic for more reliable event cleanup. * Improved state management during late SDK initialization. * Enhanced fetching of active experiences and logout handling for pending content. * Updated handling of application state transitions. * Minor enhancements on Experiences UI. * Refactored ExperiencesPublisher and AnalyticsPublisher to improve maintainability and scalability. *** ## 1.0.2 – Aug 14, 2025 **🚀 Description**\ Update native SDK dependencies - version 1.0.7 📄 Motivation and Context * Refined anonymous user tracking logic to consistently track the same anonymous user per device. * Updated the README file. *** ## 1.0.1 – Jul 16, 2025 **🚀 Description**\ This update upgrades to the latest native SDK versions and focuses on improving session handling and code quality. It enhances logout behavior, fixes session issues in edge cases, improves experience logging for better debugging and developer insight, and ensures a more stable and reliable SDK experience overall. 📄 Motivation and Context * Upgraded to the latest native SDKs for improved stability and compatibility. * Enhanced experience logging to provide clearer visibility into step and state transitions during runtime. * Improved logout flow to ensure proper session cleanup. * Fixed a bug affecting session start status in specific H-case scenarios. *** ## 1.0.0 – Jul 2, 2025 🚀 Initial release of the Userpilot Capacitor Plugin # Push Notifications Source: https://docs.userpilot.com/developer/installation/mobile/capacitor/push-notifications Set up and customize push notifications with the Userpilot Capacitor Plugin Userpilot SDK supports handling push notifications to help you deliver targeted messages and enhance user engagement. ### **Android Setup:** **Prerequisites** It is recommended to configure your Android push settings in the [Userpilot Settings Studio](https://run.userpilot.io/settings/mobile) before setting up push notifications in your app. To obtain the required keys and configuration details, please refer to the [Android Push Notification Guide](/in-app-engagement/mobile-content/preparing-to-send-push-notifications#android-push-notifications-setup). **Setting up** This guide assumes this is the first time code is being added to your project related to setting up push notifications using Google services (Firebase Cloud Messaging). In the case your project is already configured for push, proceed to Step 2. **Step 1. Add Firebase** Follow the steps in the official Google documentation on [How to add Firebase to your project](https://firebase.google.com/docs/android/setup). **Step 2. Request Notifications Permission** Starting from **Android 13 (API level 33)**, apps \*\*must explicitly request the \*\*`POST_NOTIFICATIONS` **permission** in order to display push notifications. The Userpilot Capacitor plugin **automatically** requests push notifications permission on its own. **Step 3. Add the Userpilot Firebase Messaging Service** Firebase connects to your app through a `` . Go to your Manifest file under `example-app -> android -> app -> manifest` and add: ```xml theme={null} ``` If your project has already added a child of FirebaseMessagingService, you can leave it as is, and instead plug in `UserpilotFirebaseMessagingService` to your service class. ```kotlin theme={null} import com.userpilot.pushNotifications.UserpilotFirebaseMessagingService import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage class CustomFirebaseMessageService : FirebaseMessagingService() { override fun onMessageReceived(message: RemoteMessage) { if (UserpilotFirebaseMessagingService.handleMessage(baseContext, message)) { // handled as Userpilot message return } // not Userpilot message super.onMessageReceived(message) } override fun onNewToken(token: String) { // sets new token from the callback UserpilotFirebaseMessagingService.setToken(token) super.onNewToken(token) } } ``` **Step 4. Deep Linking** To ensure your app **opens automatically when a Userpilot push notification is clicked**, you need to configure an `intent-filter` in your app's \*\*entry \*\*`Activity` under `example -> android -> app -> manifest` . This configuration enables deep linking based on a custom scheme defined in your app. **Define the Deep Link Scheme** In your `userpilot.xml` configuration file, you should have defined a value for `userpilot_push_notification` , check Step 5. **Update Manifest** file to include the intent-filter as below. Make sure to set `host="sdk"` . ```xml theme={null} ``` **Step 5. Customizing** The Userpilot SDK allows for some customizations that will change how your messages will be delivered to your end users. These properties are set as `` properties. In order change those properties create a file under `res/values` under `example-app -> android -> app` and set it to desired values. ```xml theme={null} @drawable/ic_notification #6765E8 true true true com.userpilot.general.channel General Default channel used for app messages 3  ``` ### **iOS Setup** **Prerequisites** It is recommended to configure your iOS push settings in the [Userpilot Settings Studio](https://run.userpilot.io/settings/mobile) before setting up push notifications in your app. To obtain the required keys and configuration details, please refer to the [iOS Push Notification Guide](/in-app-engagement/mobile-content/preparing-to-send-push-notifications#ios-push-notifications-setup). **Enabling Push Notification Capabilities** In Xcode, navigate to the Signing & Capabilities section of your main app target and add the Push Notifications capability. **Configuring Push Notifications** The Userpilot iOS SDK supports receiving push notification so you can reach your users whenever the moment is right. There are two options for configuring push notification: automatic or manual. Automatic configuration is the quickest and simplest way to configure push notifications and is recommended for most customers. **Automatic App Configuration** Automatic configuration takes advantage of swizzling to automatically provide the necessary implementations of the required `UIApplicationDelegate` and `UNUserNotificationCenterDelegate` methods. To enable automatic configuration, call `Userpilot.enableAutomaticPushConfig()` from `UIApplicationDelegate.application(_:didFinishLaunchingWithOptions:)` inside` example-app -> ios -> AppDelegate.swift` . ```swift theme={null} func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { // Automatically configure for push notifications Userpilot.enableAutomaticPushConfig() // Override point for customization after application launch. } ``` Automatic configuration seamlessly integrates with your app's existing push notification handling. It processes only Userpilot notifications, while ensuring that all other notifications continue to be handled by your app’s original logic. **Manually Configuring Push Notifications** Step 1. Enable push capabilities\ Step 2. Register for push notifications\ Step 3. Set push token for Userpilot\ Step 4. Enable push response handling\ Step 5. Configure foreground handling Full source code: ```swift theme={null} // AppDelegate+PushNotification.swift import Foundation import UserNotifications import UserpilotCapacitor extension AppDelegate: UNUserNotificationCenterDelegate { /// Call from `UIApplicationDelegate.application(_:didFinishLaunchingWithOptions:)` func setupPush(application: UIApplication) { requestPushNotificationPermission() // 1: Register to get a device token application.registerForRemoteNotifications() UNUserNotificationCenter.current().delegate = self } // MARK: - Push Notification Permission Request, In case you didn't request it from your Capacitor app. private func requestPushNotificationPermission() { let options: UNAuthorizationOptions = [.alert, .sound, .badge] // Check if the app has already been authorized UNUserNotificationCenter.current().getNotificationSettings { (settings) in if settings.authorizationStatus == .authorized { // App is already authorized for push notifications // Register for remote notifications again to get the device token DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() } } else { // If not authorized, request permission UNUserNotificationCenter.current().requestAuthorization(options: options) { (granted, error) in if granted { DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() } } else { if let errorDescription = error?.localizedDescription { print("Permission denied or failed to request: \(errorDescription)") } else { print("Permission denied or failed to request: Unknown error") } } } } } } // 2: Pass device token to Userpilot func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { UserpilotPlugin.setPushToken(deviceToken) } // 3: Pass the user's response to a delivered notification to Userpilot func userNotificationCenter( _ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void ) { if UserpilotPlugin.didReceiveNotification(response: response, completionHandler: completionHandler) { return } completionHandler() } // 4: Configure handling for notifications that arrive while the app is in the foreground func userNotificationCenter( _ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void ) { if #available(iOS 14.0, *) { completionHandler([.banner, .list]) } else { completionHandler(.alert) } } } ``` # Updating to Swift Package Manager Source: https://docs.userpilot.com/developer/installation/mobile/capacitor/updating-to-spm Migrate your Userpilot Capacitor app from CocoaPods to Swift Package Manager, complete Xcode setup, and verify native SDK dependencies. ### Overview Migrate your Userpilot Capacitor iOS app from CocoaPods to Swift Package Manager (SPM). Follow these steps for apps that still use CocoaPods, including older copies of the Userpilot Capacitor example. Capacitor 8 uses SPM by default for new iOS projects and continues to support existing CocoaPods projects. Upgrading Capacitor or running `npx cap sync ios` does not migrate an existing CocoaPods project. See [Capacitor's version 8 upgrade guide](https://capacitorjs.com/docs/updating/8-0). `@userpilot/capacitor` supports both dependency managers on Capacitor 6, 7, and 8. The steps below target a Capacitor 8 app. Run commands from your app directory (`example-app/` in the Userpilot Capacitor repository). ### Use cases * Migrate an existing CocoaPods app while preserving its native configuration. * Set up a new Capacitor 8 iOS project with SPM and the Userpilot plugin. ## Check your current setup For the standard Capacitor iOS directory layout: | App file | Dependency manager | How Userpilot is installed | | - | - | - | | `ios/App/Podfile`, with no `ios/App/CapApp-SPM/` directory | CocoaPods | The generated plugin entry loads `UserpilotCapacitor.podspec`, which depends on the native Userpilot iOS SDK. | | `ios/App/CapApp-SPM/Package.swift` | SPM | The generated package references the plugin's `Package.swift`, which depends on the native Userpilot iOS SDK. | Capacitor detects SPM from the `CapApp-SPM` directory. There is no Userpilot configuration flag to switch dependency managers. If your app already uses SPM, skip the migration and follow [Verify the integration](#verify-the-integration). ## Before migrating * Save a restorable copy of your iOS project, including uncommitted changes, signing settings, capabilities, URL schemes, custom native code, and extension targets. * Ensure your Capacitor 8 environment meets its requirements: Node.js 22+, Xcode 26+, and an app deployment target of iOS 15+. Keep `@capacitor/core`, `@capacitor/cli`, and `@capacitor/ios` on compatible versions. See the [Capacitor 8 upgrade guide](https://capacitorjs.com/docs/updating/8-0). * Install your app's npm dependencies and [install Userpilot](./installation#installation) if you have not already. The repository's example already declares Userpilot as a local dependency. * Check SPM support for every installed Capacitor plugin. Userpilot supports SPM, but another plugin may require an update or replacement. Capacitor does not automatically fall back to CocoaPods for incompatible plugins. Cordova plugins also need to be checked individually. * Review custom pods, `Podfile` hooks, build settings, and dependencies belonging to app extensions. Arrange their SPM equivalents and preserve any custom setup before the assistant removes CocoaPods integration. Keep CocoaPods available to run `pod deintegrate` during migration. ## Migrate an existing CocoaPods app Build your web assets, then run Capacitor's migration assistant: ```sh theme={null} npm run build npx cap spm-migration-assistant ``` The assistant runs `pod deintegrate`, removes `ios/App/Podfile`, `Podfile.lock`, and `App.xcworkspace`, and creates `ios/App/CapApp-SPM/` and `ios/debug.xcconfig`. It generates package dependencies from your installed plugins and reports incompatible plugins. Resolve those warnings before continuing; custom pod dependencies and hooks need your own migration work. ### Complete the Xcode setup ```sh theme={null} npx cap open ios ``` After migration, use `App.xcodeproj`; the CocoaPods workspace has been removed. 1. Select the **App** project, then **Package Dependencies**, and click **+**. 2. Choose **Add Local...**, select `ios/App/CapApp-SPM`, and add the package. 3. Add its **CapApp-SPM** library product to your **App** target. Confirm it appears in the target's **Frameworks, Libraries, and Embedded Content**. 4. Under the project's **Info > Configurations**, add `ios/debug.xcconfig` and use it for the app's Debug configuration. If you already use a custom Debug xcconfig, retain it and include the generated file using the correct relative path. 5. Restore any custom build settings or scripts previously supplied by your `Podfile`, and add any separately managed native or extension dependencies to their intended targets. See [Capacitor's illustrated migration steps](https://capacitorjs.com/docs/ios/spm#using-our-migration-tool) for the package and configuration dialogs. ## Use SPM in a new iOS project For a Capacitor 8 app that does not yet have an iOS project: ```sh theme={null} npm install @capacitor/ios@8 @userpilot/capacitor npm run build npx cap add ios --packagemanager SPM npx cap sync ios npx cap open ios ``` The SPM template already includes the package and Debug configuration. Use the migration steps above for an existing iOS project so you preserve its native configuration. ## Verify the integration From the app directory: ```sh theme={null} npm run build npx cap sync ios npx cap open ios ``` Confirm that: * `ios/App/CapApp-SPM/Package.swift` includes the `UserpilotCapacitor` package and product. * Xcode resolves the native `Userpilot` dependency through the plugin's package. * The app builds and launches on an iOS simulator or device, and your existing Userpilot initialization and tracking calls work. The dependency chain is: ```text theme={null} ios/App/CapApp-SPM/Package.swift -> @userpilot/capacitor/Package.swift -> Userpilot/ios-sdk ``` Do not add the native Userpilot SDK separately to the app target. The plugin's [Package.swift](https://unpkg.com/@userpilot/capacitor@latest/Package.swift) selects its version. Capacitor regenerates `CapApp-SPM/Package.swift` during sync, so avoid editing that generated file manually. Your Userpilot JavaScript initialization and API calls remain the same. ### Troubleshooting * **A plugin is missing from the generated package:** check the sync output for SPM compatibility warnings and install a compatible plugin version before syncing again. * **Xcode cannot resolve a package:** confirm that `CapApp-SPM` is linked to the app target and run `npx cap sync ios`. If resolution remains stale, use **File > Packages > Reset Package Caches**, then resolve the packages again. * **Duplicate Userpilot dependencies or symbols:** remove any extra manual Userpilot installation left in the app and keep the dependency supplied by the Capacitor plugin. * **A custom native dependency is missing:** migrate the dependency and its build settings from your saved `Podfile`, including any separate extension targets. ## Going back to CocoaPods Restore the migration-related iOS project changes from your saved copy, including the original `Podfile`, `Podfile.lock`, workspace, Xcode project, and custom configuration. Preserve unrelated work. Remove the generated `CapApp-SPM` directory and `ios/debug.xcconfig` if they did not exist before migration; leaving `CapApp-SPM` in place makes Capacitor continue selecting SPM. Then reinstall the CocoaPods dependencies and open the restored workspace: ```sh theme={null} npx cap sync ios npx cap open ios ``` For a new iOS project, you can explicitly choose CocoaPods with `npx cap add ios --packagemanager CocoaPods`. This command does not convert an existing SPM project. ## References * [Swift Package Manager in Capacitor](https://capacitorjs.com/docs/ios/spm) * [Updating to Capacitor 8](https://capacitorjs.com/docs/updating/8-0) * [Userpilot Capacitor package manifest](https://unpkg.com/@userpilot/capacitor@latest/Package.swift) * [Userpilot Capacitor CocoaPods specification](https://unpkg.com/@userpilot/capacitor@latest/UserpilotCapacitor.podspec) ### FAQs > **Do I need to change my Userpilot JavaScript calls?** > > Your initialization and tracking calls remain the same. SPM changes how > your iOS app installs the native dependencies. > **Can I install the native Userpilot package separately?** > > Keep the native SDK dependency supplied by the Capacitor plugin. Adding > another copy to the app target can cause duplicate dependencies or symbols. [Contact **support@userpilot.com** for help with your integration.](mailto:support@userpilot.com) # Auto Capture Source: https://docs.userpilot.com/developer/installation/mobile/cordova/auto-capture Configure auto capture for the Userpilot Cordova plugin Configure automatic screen and interaction capture for Cordova applications using the Userpilot Cordova plugin. The Userpilot Cordova plugin can automatically capture screens and interactions in your Cordova app and forward them to the native Userpilot SDKs, enabling analytics and engagement without manual screen and event tracking. Because Cordova renders inside a WebView, auto capture runs entirely in the DOM at runtime — there is **no build-time instrumentation** and no per-element wiring. The engine detects route changes and interactions and bridges them to the native SDK, so the resulting events appear alongside native auto capture on the backend. *** ## SDK Configuration Auto capture is **off by default**. Auto capture behavior is configured through the `options` object passed to `userpilot.setup`. ```javascript theme={null} document.addEventListener('deviceready', function () { userpilot.setup( '', { logging: true, enableScreenAutoCapture: true, enableInteractionAutoCapture: true, enableInteractionTextCapture: true, enableInteractionAccessibilityLabelCapture: true, enableInteractionValueCapture: false, }, function onSuccess() {}, function onFail(error) {} ); }); ``` | **Option** | **Type** | **Default** | **Description** | | - | - | - | - | | `enableScreenAutoCapture` | `boolean` | `false` | Enables automatic screen tracking from route/history changes. | | `enableInteractionAutoCapture` | `boolean` | `false` | Enables automatic interaction capture (taps, changes, text input, submits, dialogs). | | `enableInteractionTextCapture` | `boolean` | `true` | If `false`, `target_text` is omitted from captured interaction payloads. Typed input values are never captured verbatim regardless of this flag. | | `enableInteractionAccessibilityLabelCapture` | `boolean` | `true` | If `false`, accessibility labels (`aria-label` / `title`) are not captured, and `attr__desc` is omitted from hierarchy segments. | | `enableInteractionValueCapture` | `boolean` | `false` | Enables value payload capture: `is_checked`, `selected_value`, and selected date/time. Slider `min`/`max` and `selected_index` for selects/lists are always included; `selected_value` only when this flag is `true`. | | `maxHierarchyDepth` | `number` | `30` | Maximum number of ancestry nodes included in the hierarchy path. | When either `enableScreenAutoCapture` or `enableInteractionAutoCapture` is enabled, the DOM auto capture engine starts automatically after `setup`. The configuration is read once from `setup`; use [Runtime Controls](#runtime-controls) to pause or resume afterwards. > [!TIP] > > **Note** > > Auto Capture automatically tracks screen views and ignores manually sent `screen` events while enabled. > > If you are migrating from manual screen tracking, please review the migration guide [here](https://docs.userpilot.com/data-events/mobile-screen-tracking/mobile-screen-auto-capture#important). *** ## Screen Capture When `enableScreenAutoCapture` is `true`, the plugin patches `history.pushState` / `history.replaceState` and listens for `popstate` and `hashchange`. The screen name is resolved with the following priority: 1. **Hash route** (for example `#/settings` → `/settings`). File-based Cordova apps keep a constant, noisy `pathname`, so a meaningful hash route is preferred. This covers hash-based SPA routers. 2. **`location.pathname + location.search`** (for history-routed apps), reported as-is — a bare `/` is a valid screen name and is kept. The name always comes from the URL. There is no fallback to `document.title` or any other substitute. If the URL resolves to an empty string, no screen event is emitted. Consecutive identical names are de-duplicated. Debounced `text_change` events are flushed **before** a screen transition is published — on both automatic route changes and manual `userpilot.screen(...)` calls — so a field edited just before navigating is still attributed to the screen it was typed on. > [!TIP] > > **Manual screens** > > Manual `userpilot.screen()` calls are ignored whenever `enableScreenAutoCapture` is configured — manual and automatic screen tracking remain mutually exclusive, including while auto capture is stopped. To send screens manually, leave `enableScreenAutoCapture: false`. *** ## Interaction Capture When `enableInteractionAutoCapture` is `true`, the plugin captures DOM interactions through delegated capture-phase listeners on `document` for `click`, `change`, `input`, and `submit`, plus a `MutationObserver` for dialog presentation. Events that originate inside a web component's shadow DOM are resolved to the real host element. Each interaction is classified into a `raw_interaction_type` based on the element's tag, `type`, and ARIA `role`: | **Interaction type** | **Emitted for** | | - | - | | `tap` | `button`, `a[href]`, `[role="button"]`, `[role="link"]`, `[role="menuitem"]`, `[onclick]` (climbs to the nearest interactive ancestor) | | `tab_selected` | `[role="tab"]` | | `list_item_selected` | A clickable element that also matches `[role="option"]` or `[role="listitem"]` | | `menu_item_selected` | `[role="menuitem"]`, or any clickable element inside a `[role="menu"]` container | | `checkbox_selected` | `input[type="checkbox"]` without `[role="switch"]` | | `radio_button_selected` | `input[type="radio"]` | | `switch_changed` | `[role="switch"]` (including `` and ` ``` | **Attribute** | **Effect** | | - | - | | `data-userpilot-ignore` | Drops auto capture for the element and its subtree. | | `data-userpilot-redact` | Masks `target_text` for the element and its subtree. | *** ## Runtime Controls Auto capture starts automatically from `setup` (based on options). To stop or resume collection at runtime without re-passing the config — for example a "private mode" — use: ```javascript theme={null} userpilot.stopAutoCapture(onSuccess, onFail); // detaches all listeners userpilot.resumeAutoCapture(onSuccess, onFail); // restarts with the same config ``` While stopped, no screen or interaction events are recorded regardless of other configuration. Pausing auto capture does not enable manual `screen()` calls: when `enableScreenAutoCapture` is configured, manual and automatic screen tracking remain mutually exclusive, including while auto capture is stopped. *** ## Limitations * **WebView only.** Auto capture observes the DOM rendered in the Cordova WebView. Anything rendered outside the WebView (fully native screens or plugins with their own UI) is not captured by this engine. * **Custom widgets need semantic markup.** Elements that handle interactions without using standard tags or ARIA roles (for example a clickable `
` with no `role` and no handler attribute) are not classified. Use the supported building blocks in the [interaction table](#interaction-capture) — or add the appropriate `role` — to be captured. * **Typed input values are never captured.** For text fields, `target_text` is the field's label and only `has_text` / `text_length` describe the content; the value itself is never sent. * **Text fields need a label to report one.** `target_text` is resolved from `aria-labelledby`, an associated/wrapping `