# Web SDK quickstart

> Source: https://cross-deck.com/docs/quickstart-web/ · Crossdeck developer docs (generated from the published page).

Quickstart
10 min
·
For React, Next.js, Vue, Svelte, vanilla JS, and plain HTML

From a fresh Crossdeck account to your first event landing in the dashboard in under ten minutes. Two values to paste, four lines of code, one verification screen.

You can also do this from the onboarding wizard.

If you just signed up, the dashboard's **Onboarding** flow generates the same snippet you'll build manually here — same code, same heartbeat, same green tick. This page exists for developers who closed the wizard, came back later, or want to read the steps before they paste anything into their codebase.

## Before you start

You'll need:

- A Crossdeck account with a project created (if you don't have one, [start free](https://cross-deck.com/start-free/) — no credit card).

- Your app's frontend codebase, in any of the supported frameworks (React, Next.js, Vue 3, Svelte / SvelteKit, vanilla JS with a bundler, or plain HTML with a CDN script tag).

- About 10 minutes.

You don't need to connect a payment rail (Stripe, Apple, Google Play) to see your first event — telemetry flows the moment the SDK initialises. Rail connections are a separate guide.

## Step 1 · Find your appId and publishable key

Crossdeck identifies your app with two values:

Value What it looks like What it does

appId
app_web_92b2d6a5728a4d
Identifies which app inside your project the events belong to. Stable, never rotates.

publicKey
cd_pub_test_… or cd_pub_live_…
Authenticates the SDK to the ingest endpoint. Safe to ship in your client bundle.

Test keys for development, live keys for production.

A `cd_pub_test_…` key routes into the sandbox environment — events here never affect production analytics, billing, or your customer-facing dashboards. Switch to `cd_pub_live_…` only when you're ready to ship. The SDK's `environment` argument must match the key's prefix (we error loudly if it doesn't).

### Where to find them

- Open the [Crossdeck dashboard](https://app.cross-deck.com/) and select your project from the workspace switcher (top-left).

- From the left sidebar, click **Developers**, then **API**. The direct path is [/developers/api](https://app.cross-deck.com/developers/api).

- The page lists your apps. Each app card shows the `appId` at the top and two publishable keys below — one **test**, one **live**. Click the copy icon next to either to copy it to your clipboard.

If you haven't created a web app yet, the dashboard shows a **+ Add app** button. Picking *Web* mints both keys instantly.

## Step 2 · Install the SDK

Pick the install path that matches your stack. Every path ships the **same SDK** — the npm package and the CDN bundle expose the same `Crossdeck` API.

### npm — for bundled apps (React, Next.js, Vue, Svelte, Vite, Webpack)

```
npm install @cross-deck/web
```

Or `pnpm add` / `yarn add` / `bun add` — same package name across every registry. The npm scope is `@cross-deck` with a hyphen, not `@crossdeck`.

### CDN script tag — for plain HTML, Webflow, Framer, no-build sites

```
<script src="https://unpkg.com/@cross-deck/web@^1/dist/crossdeck.umd.min.js"></script>
```

The UMD bundle exposes `window.Crossdeck.Crossdeck`. Pin to the current major (`@^1`) — it tracks every 1.x release automatically but never a breaking 2.0. Avoid `@latest`, which ships every release to your customers, including breaking ones.

## Step 3 · Initialise the SDK

Call `Crossdeck.init(...)` exactly once at boot — top of your provider tree, root of your app, or in ` ` for plain HTML. The call is synchronous and returns immediately; the SDK fires its first heartbeat on the next tick.

### Next.js (App Router)

Create a client component at the top of your provider tree — typically `app/providers.tsx` — and call `init` inside `useEffect`:

```
"use client";
import { useEffect } from "react";
import { Crossdeck } from "@cross-deck/web";

export function Providers({ children }) {
  useEffect(() => {
    Crossdeck.init({
      appId: "app_web_92b2d6a5728a4d",
      publicKey: "cd_pub_test_…",
      environment: "sandbox",
    });
  }, []);
  return children;
}
```

Then wrap your root layout in ` `.

### Vite / Create React App / vanilla JS bundler

```
import { Crossdeck } from "@cross-deck/web";

Crossdeck.init({
  appId: "app_web_92b2d6a5728a4d",
  publicKey: "cd_pub_test_…",
  environment: "sandbox",
});
```

Put it in your entry file (`src/main.tsx`, `src/index.js`) above your app's render call.

### Plain HTML / Webflow / Framer (no bundler)

```
<script src="https://unpkg.com/@cross-deck/web@^1/dist/crossdeck.umd.min.js"></script>
<script>
  window.Crossdeck.Crossdeck.init({
    appId: "app_web_92b2d6a5728a4d",
    publicKey: "cd_pub_test_…",
    environment: "sandbox",
  });
</script>
```

Drop both ` ` blocks into the ` ` of every page that should report telemetry. Webflow and Framer expose a "custom code" panel under site settings — that's where this goes.

The environment string must match the publishable-key prefix.

`cd_pub_test_…` pairs with `"sandbox"`; `cd_pub_live_…` pairs with `"production"`. Mismatched values throw at `init` rather than silently routing events into the wrong warehouse.

### Confirm your domain is on the Allowed origins list

Each Web app in Crossdeck has an **Allowed origins** list. During onboarding it starts empty, and the **first heartbeat** your SDK sends (right after `Crossdeck.init()`) auto-registers the origin it came from *plus* that origin's registrable apex — so the first deploy Just Works without you typing a domain. After that the list is **locked**: any *new* origin (a staging URL, a www variant, a brand-new production domain) gets a `403 origin_not_allowed` until you add it explicitly.

To check or add an origin:

- Open the [API](https://app.cross-deck.com/developers/api) page.

- Click the app's name (or the **Edit** link on the app card). The app's settings panel opens.

- Scroll to **Allowed origins**. You'll see your registered origins (`https://yoursite.com`, `http://localhost:3000`, etc.).

- Click **+ Add origin**, paste the full scheme + host (no trailing slash, no path), and save.

An origin is scheme + host + port. `https://acme.com`, `http://localhost:3000`, and `https://staging.acme.com` are three different origins; subdomains and ports count. Wildcards aren't supported — for environments with many short-lived preview URLs (Vercel previews, Netlify branch deploys), add the base preview domain pattern via `https://*.acme-preview.com` by emailing [support](mailto:support@cross-deck.com) and we'll enable wildcard matching on the app.

## Step 4 · Identify your user

Until you call `identify(...)`, every event carries an anonymous ID minted on first page load and persisted in `localStorage`. The moment you know who the user is — after sign-in, in your auth callback — call `identify` with your app's user ID. The SDK rotates the anonymous events onto the identified person automatically.

```
// Anywhere you have a signed-in user — auth callback, useEffect on a session
Crossdeck.identify("user_847");

// When the user signs out
Crossdeck.reset();
```

The `userId` is whatever your app uses — the database primary key, the Firebase UID, the Auth0 `sub`. It should be stable for the lifetime of the account.

Want the People feed to show real names instead of `anon_xxx`? `identify` also accepts optional `email` and `traits` — see [Identify users](https://cross-deck.com/docs/identify-users/) for the full signature.

### React + auth library (Firebase, Clerk, Auth0, NextAuth)

```
"use client";
import { useEffect } from "react";
import { Crossdeck } from "@cross-deck/web";
import { useUser } from "@your-auth-library";

export function IdentityBridge() {
  const { user } = useUser();
  useEffect(() => {
    if (user) Crossdeck.identify(user.id);
    else Crossdeck.reset();
  }, [user?.id]);
  return null;
}
```

Mount ` ` anywhere inside your provider tree. It's a render-less component — single useEffect that mirrors auth state into the SDK.

## Step 5 · Verify the heartbeat

Open your app in a fresh tab. The SDK fires a boot heartbeat the moment `init` resolves, then a stream of `page.viewed`, `session.started`, and `element.clicked` events as you interact with the page.

To confirm Crossdeck received them, open [Developers → Heartbeat](https://app.cross-deck.com/developers/heartbeat) in the dashboard. The page shows a per-app **SDK signal audit** with three live tiles:

- **Last seen** — timestamp of the most recent event from your app. Should show "a few seconds ago."

- **SDK version** — the version string the SDK stamped on its events. Confirms which build is live.

- **Recent events** — a live tail of the last events received. You should see `session.started` and `page.viewed` within a few seconds of loading your app.

End-to-end latency is 3–8 seconds.

The SDK batches events (default flush at 20 events or 5 seconds) → ingest writes to Firestore → the dashboard's onSnapshot listener fans the update out. If you don't see anything after 15 seconds, jump to [Troubleshooting](#troubleshooting).

### If nothing shows up after 30 seconds

Symptom Most likely cause Fix

Console: Crossdeck.init: invalid key prefix
environment doesn't match the key (test key + production, or live key + sandbox).
Set environment: "sandbox" for cd_pub_test_… keys; "production" for cd_pub_live_… .

Console: POST /v1/events 401
The publishable key is for a different project than the appId.
Re-copy both values from the same app card on the API page.

Console: POST /v1/events 403 origin not allowed
Your domain isn't on the app's allowed-origins list (defaults to first observed origin).
Open the app's settings in the dashboard and add your domain to Allowed origins .

No console errors, but Heartbeat shows nothing
An ad-blocker, tracking-protection extension, or strict CSP is dropping the request before it leaves the browser.
Open DevTools → Network , filter for v1/events . If the request never appears, an extension is blocking; allowlist api.cross-deck.com . If it appears with status 0 (blocked) , your Content-Security-Policy needs connect-src https://api.cross-deck.com .

Heartbeat shows events but no page.viewed
Your app uses a custom router that doesn't fire pushState / popstate .
Call Crossdeck.page() manually inside your router's navigation handler.

## What's next

Heartbeat green? The SDK is live. From here:

- [Identify users](https://cross-deck.com/docs/identify-users/) — pass traits (email, plan, signup date) so the People feed renders real names instead of `anon_xxx`.

- [Track custom events](https://cross-deck.com/docs/track-events/) — `Crossdeck.track("paywall_seen", { variant: "annual" })` for the conversion events that matter to you.

- [Entitlements & gating](https://cross-deck.com/docs/entitlements/) — gate pro features with one line: `if (Crossdeck.isEntitled("pro")) { … }`.

- [Connect Stripe](https://cross-deck.com/docs/connect-stripe/) — start verifying revenue and computing MRR. ([Apple App Store](https://cross-deck.com/docs/connect-apple/) and [Google Play](https://cross-deck.com/docs/#payment-rails) if you're cross-platform.)

- [Full Web SDK reference](https://cross-deck.com/docs/web-sdk/) — every method, every option, every event the SDK fires automatically.
