SaaSFunnelsDocs
Integrations and Sources

Web SDK Installation

Install the SaaSFunnels Web SDK for supplemental browser events.

The Web SDK is a supplemental event source for browser-observable product moments such as activation, pricing intent, checkout starts, downgrade or cancel intent, team invites, and limit friction. Keep backend systems, Stripe, PostHog, Segment, or Direct API events as the primary source when they already provide cleaner account-level data.

Create a client

In SaaSFunnels, open Settings → Integrations → Web SDK, create a client, and add the exact origins where the customer app runs. The client key is publishable and origin-limited, but it is shown only once so the install snippet can be copied safely.

If you are following the Setup checklist, Connect Your App explains where the browser key and Client ID go, how the separate Funnel server key is stored, and how Setup verifies the connection. For a Next.js app, the browser key can use NEXT_PUBLIC_SAASFUNNELS_WEB_SDK_CLIENT_KEY; other frameworks use their own public configuration mechanism.

Never place Direct API, PostHog, Segment, Stripe, or server ingest secrets in browser code.

Use SaaSFunnels MCP Server when a coding agent needs source-boundary context. The SaaSFunnels CLI validates Direct API, PostHog, and Segment payloads; the Web SDK remains the browser-safe path for visible intent and must not receive server ingest keys.

Install the script

Add the hosted script to the customer application and initialize it with the Web SDK client key.

<script>
  window.saasfunnels = window.saasfunnels || [];
  window.saasfunnels.push(["group", "acct_123"]);
  window.saasfunnels.push([
    "init",
    {
      clientKey: "<web_sdk_client_key>",
      host: "https://app.saasfunnels.ai",
      autoPage: false,
    },
  ]);
  window.saasfunnels.push([
    "track",
    "upgrade_clicked",
    {
      plan: "growth",
      source: "pricing",
    },
  ]);
</script>
<script async src="https://app.saasfunnels.ai/sdk/saasfunnels.js"></script>

If the website uses Content Security Policy, allow this SaaSFunnels host for the stable script and its immutable module release. Preserve the host nonce when using nonce-based CSP. Set Funnel consent to granted only after the site's consent owner permits this purpose; traffic entries do not evaluate while consent is pending or denied.

Install website Funnel entries

Create the website behavior in Funnel Studio → Targeting, then register its exact placement, named event, and typed detector policy in the customer website.

saasfunnels.funnels.setConsent("granted");

const unregister = saasfunnels.funnels.registerPlacement({
  key: "pricing_recovery",
  trafficEntries: [
    {
      eventKey: "website.desktop_exit_intent",
      kind: "desktop_exit_intent",
    },
    {
      eventKey: "website.time_on_page",
      kind: "time_on_page",
      seconds: 15,
    },
    {
      eventKey: "website.scroll_depth",
      kind: "scroll_depth",
      percent: 60,
    },
    {
      eventKey: "website.inactivity",
      kind: "inactivity",
      seconds: 30,
    },
    { eventKey: "website.route", kind: "route" },
  ],
});

For an existing named website interaction that should be eligible by page or campaign context, opt in on its placement:

saasfunnels.funnels.registerPlacement({
  key: "pricing_recovery",
  triggers: ["pricing_cta_clicked"],
  websiteContext: true,
});

The opt-in is explicit so existing host-interaction payloads remain unchanged by default.

Desktop exit intent runs only for a fine pointer leaving through the top of the viewport and is bounded to once per first-party session. It never runs for touch or coarse-pointer devices. Choose separately named Scroll depth or Inactivity entries for mobile; SaaSFunnels does not relabel those as exit intent.

The runtime sends only normalized path or route key, referrer origin and path, five allowlisted UTM fields, coarse device class, lawful new/returning session class, a first-party session identifier, exact placement, and the trigger name/value. Raw URLs, raw query strings, fragments, credentials, form values, and arbitrary DOM are dropped. Route changes work for initial loads and SPAs, and unregister or runtime teardown removes every listener and timer.

Return to Settings → Integrations → Web SDK to see the exact placements and triggers detected by runtime decisions. Funnel Validation and Test distinguish a missing installation, missing placement, unseen trigger, stale binding, and verified current draft.

Install an acquisition Funnel

The governed acquisition starters use the same runtime and Studio as in-product Funnels:

StarterDefault placement and entry
Lead magnetguides.activation_benchmark.inline; scroll depth at 60%
Demo requestpricing.demo_request.inline; pricing route
Waitlistproduct.early_access.inline; early-access route
Pricing-page recoverypricing.exit.modal; desktop exit intent

For Inline capture, reserve an exact host-owned mount and pass the element itself. Your page owns its width, spacing, and reserved height; the Studio sample container is illustrative and does not set your site's layout. Use the placement key shown in Studio's Set up embed view.

Registration reserves a delivery location. Request a decision explicitly with check, an installed trigger, or React's opt-in checkOnMount; mounting alone does not request one. Keep the existing SDK client/provider and consent setup above.

<div id="activation-guide-funnel" style="min-height: 12rem"></div>
<script>
  const mount = document.getElementById("activation-guide-funnel");
  const unregister = saasfunnels.funnels.registerPlacement({
    autoCheckOnRoute: true,
    key: "guides.activation_benchmark.inline",
    mount,
    trafficEntries: [
      {
        eventKey: "website.scroll_depth",
        kind: "scroll_depth",
        percent: 60,
      },
    ],
  });

  void saasfunnels.funnels.check("guides.activation_benchmark.inline");
</script>

React applications use the same runtime and a visible Inline mount:

import {
  FunnelInlinePlacement,
  SaaSFunnelsProvider,
} from "@saasfunnels/funnels-react";

<SaaSFunnelsProvider
  config={{
    clientKey: "<web_sdk_client_key>",
    consent: consentGranted ? "granted" : "pending",
    host: "https://app.saasfunnels.ai",
  }}
>
  <FunnelInlinePlacement
    autoCheckOnRoute
    checkOnMount
    className="min-h-48"
    placementKey="product.early_access.inline"
  />
</SaaSFunnelsProvider>;

Before publishing:

  1. Add every production and preview origin to the Web SDK client and allow the SaaSFunnels script, module, API, and optional renderer stylesheet under the site's CSP. unsafe-eval is not required.
  2. Keep consent pending until the site's consent owner grants the configured capture and delivery purpose. Withdrawal closes active UI and prevents new decisions.
  3. Match the Studio placement key exactly and configure page/campaign rules as eligibility. Do not put raw URLs, query strings, form answers, or arbitrary DOM values into website context.
  4. Connect an approved lead_handoff or lead_magnet_delivery destination. The first release references an external asset or provider template; SaaSFunnels does not host uploaded lead-magnet files.
  5. Use Inline for forms. Banner supports a compact announcement or native CTA and deliberately does not accept capture fields.
  6. Keep desktop exit intent desktop-only. Configure a separately named route, scroll-depth, time-on-page, or inactivity entry for mobile traffic.

After publishing, verify the current draft in Studio Validation, submit one consented test Lead, and reconcile Leads → Responses, Delivery, and Outcomes with Funnel Results and provider Integration Activity. If the Funnel does not render, inspect Web SDK diagnostics for the exact origin, consent state, placement, route, trigger, current definition version, runtime version, CSP block, cap, cooldown, conflict, or operational-control reason. Operators should follow the Acquisition Funnel release runbook for rollback and recovery.

Identify accounts

Every useful revenue event needs a stable account identity. Call saasfunnels.group("acct_123", traits) once the account, workspace, company, or tenant is known. You can also pass account_id on individual track calls.

Accountless events are stored as setup diagnostics instead of becoming revenue signals.

Send account traits

Use saasfunnels.group(accountId, traits) to send the ordinary SaaS account facts SaaSFunnels needs for account tracking and signal quality. Refresh these traits when the account signs up, changes plan, enters or exits trial, changes team size, or loads the authenticated app shell.

window.saasfunnels.group("acct_123", {
  account_created_at: "2026-06-01T12:00:00.000Z",
  plan: "starter",
  is_in_trial: true,
  trial_ends_at: "2026-06-28T12:00:00.000Z",
  user_count: 7,
  active_user_count: 6,
  seat_count: 10,
});

Minimum useful traits:

FactPreferred field namesNotes
Customer signup dateaccount_created_at, signup_at, or customer_created_atThis is the customer product signup date, not the time SaaSFunnels first saw the account.
Login activityTrack user_logged_in or session_startedInclude a stable user ID with identify or track options when available.
Plan and lifecycleplan, plan_name, current_plan, account_status, or subscription_statusKeep values bounded and stable.
Trial stateis_in_trial, trial_started_at, trial_ends_atUse as a fallback when Stripe is not connected. Stripe is authoritative for billing and trial state when connected.
Team and seatsuser_count, active_user_count, seat_count, or member_countSend counts only, not raw user lists.

Track events

Start with explicit events that map to revenue semantics:

window.saasfunnels.track("user_logged_in", {
  source: "app_shell",
});

window.saasfunnels.track("checkout_started", {
  plan: "growth",
  value: 1,
});

Generic page views are not treated as revenue signals by default. Use explicit names such as pricing_viewed, upgrade_clicked, cancel_flow_started, or team_member_invited when the page or action reflects intent.

Sparse Event KPI events

SaaSFunnels does not need full clickstream telemetry to produce Event KPI analytics. Start with a small allowlist of account-scoped events that describe value, friction, expansion intent, and sentiment.

Event familyExample namesUseful properties
High-value usagefeature_used, api_credits_used, report_exported, workflow_completed, generation_completed, sync_completedfeature_key, feature_area, is_key_feature, is_premium, value, quantity, usage_ratio, allowance, sentiment fields
Activation / first valueaccount_activated, first_value_completed, onboarding_completedplan, feature, value, sentiment fields
Limit frictionlimit_hit, quota_blocked, overage_prompt_viewedusage_ratio, allowance, plan, value, sentiment fields
Product or support frictionsync_failed, setup_failed, support_neededfeature, error_code, severity, sentiment fields
Upgrade intentpricing_viewed, upgrade_clicked, premium_feature_clicked, premium_feature_used, checkout_startedplan, current_plan, target_plan, feature_key, value, sentiment fields
Downgrade or cancel intentdowngrade_flow_started, cancel_flow_startedplan, reason_code, sentiment fields
Team expansionteam_member_invited, seat_added, role_assignedquantity, role, plan, sentiment fields
Integration connectedintegration_connected, oauth_connected, integration_configuredintegration, provider, sentiment fields
Checkout outcomecheckout_started, checkout_completed, checkout_abandonedplan, value, currency, sentiment fields

These same event names and properties work for the Web SDK, Direct API, PostHog, and Segment. The source-specific wrapper can change, but the account identity, event name, timestamp, value fields, semantic intent, and sentiment fields should stay consistent.

Flag key features

Use key-feature fields on high-value usage, premium intent, or limit-friction events when the feature itself explains account health, expansion readiness, or churn risk. Do not create dynamic event names such as generation_feature_used or one event name per feature. Keep the event name stable, then describe the feature with bounded properties.

For Web SDK events, call saasfunnels.group(accountId, traits) before tracking, then send:

  • feature_key: stable machine name for the feature, such as generation, export, workflow, or api.
  • feature_area: stable product area, such as ai, reporting, automation, or integrations.
  • is_key_feature: true only when the feature is part of the customer's core value path.
  • is_premium: true when the feature is paid, gated, or upgrade-relevant.
  • value, quantity, usage_ratio, or allowance when the action has measurable volume or usage pressure.
window.saasfunnels.group("acct_123", { plan: "starter" });

window.saasfunnels.track("feature_used", {
  feature_key: "generation",
  feature_area: "ai",
  is_key_feature: true,
  is_premium: true,
  value: 1,
});

Use the Web SDK for browser-visible feature clicks, premium-feature intent, or UI friction the backend cannot see. Use Direct API instead when the backend can confirm completed usage, billable metering, export completion, workflow completion, quota calculation, or a durable setup milestone.

Sentiment fields

Sentiment is optional and separate from the event family. Missing sentiment is stored as unknown, not neutral; use neutral only when the customer action explicitly carries neutral sentiment.

Send sentiment only when the browser action has a bounded, defensible signal. If sentiment is omitted, SaaSFunnels may infer a mapping default from the event name, semantic type, event kind, and bounded property keys. That mapped default is reviewable in the Events page and fills future events only when payload sentiment is missing or unknown.

window.saasfunnels.track("api_credits_used", {
  value: 250,
  usage_ratio: 0.82,
  allowance: 300,
  feature_key: "generation",
  feature_area: "ai",
  is_key_feature: true,
  sentiment: "negative",
  sentiment_score: -0.6,
  sentiment_confidence: 0.8,
  sentiment_source: "explicit",
});

Accepted sentiment fields:

FieldValues
sentimentpositive, neutral, negative, unknown
sentiment_scoreNumber from -1 to 1
sentiment_confidenceNumber from 0 to 1
sentiment_sourceexplicit, inferred, ai, integration, unknown

Do not send free-text feedback, support transcripts, chat messages, form fields, or raw customer comments as sentiment context. Send a bounded label, score, confidence, and source instead.

KPI coverage

Each Event KPI improves with a few specific event families. More volume helps, but variety and account identity matter more than tracking every click.

KPIMinimum useful coverage
Sentiment TrendsAny revenue-relevant event family with sentiment fields across 7, 14, or 30 day windows
Sentiment-Weighted UsageHigh-value usage events with value, quantity, or usage_ratio, plus optional sentiment
Revenue-Sentiment DivergenceUsage, checkout, upgrade, downgrade, or cancel intent events combined with billing or plan context
Friction LoadLimit, support, setup, sync, checkout, or payment-friction events plus value events for comparison
Expansion Surface AreaUpgrade intent, top-ups, team expansion, integration connected, usage pressure, and positive sentiment
Account Momentum QualityA mix of usage, activation, expansion, friction, and sentiment events over trailing windows

As a rule of thumb, one or two isolated events are diagnostic only. Three or more account-scoped events in a 14 day window can become directional. Five or more relevant events in 30 days, especially across two or more event families, can support usable KPI confidence. Low-volume accounts should still show sample size and low-evidence states instead of being forced into a strong classification.

See Event KPI Signals for the canonical KPI definitions and account filter fields.

Source-compatible examples

Direct API payloads can send the normalized field names directly:

{
  "account_id": "acct_123",
  "event_id": "evt_usage_001",
  "event_name": "api_credits_used",
  "timestamp": "2026-06-20T15:00:00.000Z",
  "value": 250,
  "usage_ratio": 0.82,
  "sentiment": "negative",
  "sentiment_score": -0.6,
  "sentiment_confidence": 0.8,
  "sentiment_source": "explicit"
}

PostHog and Segment should forward the same business fields inside properties while keeping a stable account or group identity:

{
  "event": "api_credits_used",
  "properties": {
    "account_id": "acct_123",
    "account_created_at": "2026-06-01T12:00:00.000Z",
    "active_user_count": 6,
    "is_in_trial": true,
    "plan": "starter",
    "seat_count": 10,
    "trial_ends_at": "2026-06-28T12:00:00.000Z",
    "value": 250,
    "usage_ratio": 0.82,
    "sentiment": "negative",
    "sentiment_score": -0.6,
    "sentiment_confidence": 0.8,
    "sentiment_source": "integration"
  },
  "timestamp": "2026-06-20T15:00:00.000Z"
}
{
  "type": "track",
  "event": "team_member_invited",
  "groupId": "acct_123",
  "userId": "user_456",
  "properties": {
    "quantity": 2,
    "sentiment": "positive"
  },
  "timestamp": "2026-06-20T15:00:00.000Z"
}

Data safety

The SDK sends event names, timestamps, normalized URL path/referrer context, account/user identifiers, and the properties you provide. It strips query strings and fragments from URL fields. Do not send form values, emails, session tokens, API keys, cookies, authorization headers, arbitrary route changes, every button click, or free-text support content.

Verify setup

After installation, send one event from an allowed origin and check Settings > Integrations > Web SDK and Events. A valid account-scoped event should appear as source web_sdk. If the event is missing account identity, SaaSFunnels will show a rejected diagnostic row with setup guidance.