Quickstart
Install arcy.js in your framework, tell ARCY who the user is, verify the install, sign user ids, follow sign-out and report key moments.
Your environment's snippets, with the real token already filled in, are in the dashboard under Settings > Installation. That page also carries the go-live steps for the environment you have selected; this page is the code. No account yet? Start with Create account.
Everything else ARCY does is configured in the dashboard, not in code. There is no config file and nothing to redeploy when you change a setting.
A coding agent can write all of this page in your repository for you: run Install arcy.js from ARCY MCP. It follows the steps below and asks only what your code does not answer.
Required steps
Add arcy.js to your Product
Pick your framework. Every path does the same two things: init() starts ARCY with your
environment token, and identify() tells ARCY who the current user is.
Install the package:
npm install arcy.jsCall ARCY once, where your Product starts. In a Vite or Create React App project that is your root component:
import { useEffect } from "react"
import { supabase } from "./supabase"
import arcy from "arcy.js"
export default function App() {
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
const { data } = supabase.auth.onAuthStateChange((_event, session) => {
const user = session?.user
if (!user) return
arcy.identify(user.id, {
user_first_name: user.user_metadata.first_name,
user_last_name: user.user_metadata.last_name,
user_email: user.email,
user_signed_up_at: user.created_at,
})
})
return () => data.subscription.unsubscribe()
}, [])
return <YourApp />
}The name keys come from user_metadata, so use whatever keys you store there.
import { useEffect } from "react"
import { useUser } from "@clerk/clerk-react"
import arcy from "arcy.js"
export default function App() {
const { isLoaded, isSignedIn, user } = useUser()
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
}, [])
useEffect(() => {
if (!isLoaded || !isSignedIn) return
arcy.identify(user.id, {
user_first_name: user.firstName,
user_last_name: user.lastName,
user_email: user.primaryEmailAddress?.emailAddress,
user_signed_up_at: user.createdAt?.toISOString(),
})
}, [isLoaded, isSignedIn, user])
return <YourApp />
}import { useEffect } from "react"
import { getAuth, onAuthStateChanged } from "firebase/auth"
import arcy from "arcy.js"
export default function App() {
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
return onAuthStateChanged(getAuth(), (user) => {
if (!user) return
arcy.identify(user.uid, {
user_first_name: user.displayName,
user_email: user.email,
user_signed_up_at: user.metadata.creationTime
? new Date(user.metadata.creationTime).toISOString()
: undefined,
})
})
}, [])
return <YourApp />
}Firebase keeps one display name, so there is no separate last name to send.
import { useEffect } from "react"
import arcy from "arcy.js"
export default function App() {
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
arcy.identify("USER_ID", {
user_first_name: "USER_FIRST_NAME",
user_last_name: "USER_LAST_NAME",
user_email: "USER_EMAIL",
user_signed_up_at: "USER_SIGNED_UP_AT",
})
}, [])
return <YourApp />
}Install the package:
npm install arcy.jsarcy.js runs in the browser only, so keep the call in a client component.
Add one client component and render it in your root layout:
"use client"
import { useEffect } from "react"
import { supabase } from "@/lib/supabase"
import arcy from "arcy.js"
export function ARCY() {
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
const { data } = supabase.auth.onAuthStateChange((_event, session) => {
const user = session?.user
if (!user) return
arcy.identify(user.id, {
user_first_name: user.user_metadata.first_name,
user_last_name: user.user_metadata.last_name,
user_email: user.email,
user_signed_up_at: user.created_at,
})
})
return () => data.subscription.unsubscribe()
}, [])
return null
}The name keys come from user_metadata, so use whatever keys you store there.
"use client"
import { useEffect } from "react"
import { useUser } from "@clerk/nextjs"
import arcy from "arcy.js"
export function ARCY() {
const { isLoaded, isSignedIn, user } = useUser()
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
}, [])
useEffect(() => {
if (!isLoaded || !isSignedIn) return
arcy.identify(user.id, {
user_first_name: user.firstName,
user_last_name: user.lastName,
user_email: user.primaryEmailAddress?.emailAddress,
user_signed_up_at: user.createdAt?.toISOString(),
})
}, [isLoaded, isSignedIn, user])
return null
}"use client"
import { useEffect } from "react"
import { useSession } from "next-auth/react"
import arcy from "arcy.js"
export function ARCY() {
const { data: session, status } = useSession()
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
}, [])
useEffect(() => {
if (status !== "authenticated" || !session.user.id) return
arcy.identify(session.user.id, {
user_first_name: session.user.name,
user_email: session.user.email,
})
}, [status, session])
return null
}session.user.id is there only if your session callback puts it there.
"use client"
import { useEffect } from "react"
import { getAuth, onAuthStateChanged } from "firebase/auth"
import arcy from "arcy.js"
export function ARCY() {
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
return onAuthStateChanged(getAuth(), (user) => {
if (!user) return
arcy.identify(user.uid, {
user_first_name: user.displayName,
user_email: user.email,
user_signed_up_at: user.metadata.creationTime
? new Date(user.metadata.creationTime).toISOString()
: undefined,
})
})
}, [])
return null
}Firebase keeps one display name, so there is no separate last name to send.
"use client"
import { useEffect } from "react"
import arcy from "arcy.js"
export function ARCY() {
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
arcy.identify("USER_ID", {
user_first_name: "USER_FIRST_NAME",
user_last_name: "USER_LAST_NAME",
user_email: "USER_EMAIL",
user_signed_up_at: "USER_SIGNED_UP_AT",
})
}, [])
return null
}import { ARCY } from "./arcy"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<ARCY />
</body>
</html>
)
}Call ARCY once in _app.tsx, the component that wraps every page:
import { useEffect } from "react"
import { supabase } from "@/lib/supabase"
import arcy from "arcy.js"
import type { AppProps } from "next/app"
export default function App({ Component, pageProps }: AppProps) {
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
const { data } = supabase.auth.onAuthStateChange((_event, session) => {
const user = session?.user
if (!user) return
arcy.identify(user.id, {
user_first_name: user.user_metadata.first_name,
user_last_name: user.user_metadata.last_name,
user_email: user.email,
user_signed_up_at: user.created_at,
})
})
return () => data.subscription.unsubscribe()
}, [])
return <Component {...pageProps} />
}The name keys come from user_metadata, so use whatever keys you store there.
import { useEffect } from "react"
import { useUser } from "@clerk/nextjs"
import arcy from "arcy.js"
import type { AppProps } from "next/app"
export default function App({ Component, pageProps }: AppProps) {
const { isLoaded, isSignedIn, user } = useUser()
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
}, [])
useEffect(() => {
if (!isLoaded || !isSignedIn) return
arcy.identify(user.id, {
user_first_name: user.firstName,
user_last_name: user.lastName,
user_email: user.primaryEmailAddress?.emailAddress,
user_signed_up_at: user.createdAt?.toISOString(),
})
}, [isLoaded, isSignedIn, user])
return <Component {...pageProps} />
}import { useEffect } from "react"
import { useSession } from "next-auth/react"
import arcy from "arcy.js"
import type { AppProps } from "next/app"
export default function App({ Component, pageProps }: AppProps) {
const { data: session, status } = useSession()
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
}, [])
useEffect(() => {
if (status !== "authenticated" || !session.user.id) return
arcy.identify(session.user.id, {
user_first_name: session.user.name,
user_email: session.user.email,
})
}, [status, session])
return <Component {...pageProps} />
}session.user.id is there only if your session callback puts it there.
import { useEffect } from "react"
import { getAuth, onAuthStateChanged } from "firebase/auth"
import arcy from "arcy.js"
import type { AppProps } from "next/app"
export default function App({ Component, pageProps }: AppProps) {
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
return onAuthStateChanged(getAuth(), (user) => {
if (!user) return
arcy.identify(user.uid, {
user_first_name: user.displayName,
user_email: user.email,
user_signed_up_at: user.metadata.creationTime
? new Date(user.metadata.creationTime).toISOString()
: undefined,
})
})
}, [])
return <Component {...pageProps} />
}Firebase keeps one display name, so there is no separate last name to send.
import { useEffect } from "react"
import arcy from "arcy.js"
import type { AppProps } from "next/app"
export default function App({ Component, pageProps }: AppProps) {
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
arcy.identify("USER_ID", {
user_first_name: "USER_FIRST_NAME",
user_last_name: "USER_LAST_NAME",
user_email: "USER_EMAIL",
user_signed_up_at: "USER_SIGNED_UP_AT",
})
}, [])
return <Component {...pageProps} />
}Install the package:
npm install arcy.jsCall ARCY where you create the Vue application:
import { createApp } from "vue"
import { supabase } from "./supabase"
import arcy from "arcy.js"
import App from "./App.vue"
createApp(App).mount("#app")
arcy.init("YOUR_ENVIRONMENT_TOKEN")
supabase.auth.getUser().then(({ data: { user } }) => {
if (!user) return
arcy.identify(user.id, {
user_first_name: user.user_metadata.first_name,
user_last_name: user.user_metadata.last_name,
user_email: user.email,
user_signed_up_at: user.created_at,
})
})The name keys come from user_metadata, so use whatever keys you store there.
import { createApp } from "vue"
import { getAuth, onAuthStateChanged } from "firebase/auth"
import arcy from "arcy.js"
import App from "./App.vue"
createApp(App).mount("#app")
arcy.init("YOUR_ENVIRONMENT_TOKEN")
onAuthStateChanged(getAuth(), (user) => {
if (!user) return
arcy.identify(user.uid, {
user_first_name: user.displayName,
user_email: user.email,
user_signed_up_at: user.metadata.creationTime
? new Date(user.metadata.creationTime).toISOString()
: undefined,
})
})Firebase keeps one display name, so there is no separate last name to send.
import { createApp } from "vue"
import arcy from "arcy.js"
import App from "./App.vue"
createApp(App).mount("#app")
arcy.init("YOUR_ENVIRONMENT_TOKEN")
arcy.identify("USER_ID", {
user_first_name: "USER_FIRST_NAME",
user_last_name: "USER_LAST_NAME",
user_email: "USER_EMAIL",
user_signed_up_at: "USER_SIGNED_UP_AT",
})Install the package:
npm install arcy.jsAdd a client plugin. The .client in the file name keeps it out of the server render:
import { supabase } from "~/lib/supabase"
import arcy from "arcy.js"
export default defineNuxtPlugin(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
supabase.auth.getUser().then(({ data: { user } }) => {
if (!user) return
arcy.identify(user.id, {
user_first_name: user.user_metadata.first_name,
user_last_name: user.user_metadata.last_name,
user_email: user.email,
user_signed_up_at: user.created_at,
})
})
})The name keys come from user_metadata, so use whatever keys you store there.
import { getAuth, onAuthStateChanged } from "firebase/auth"
import arcy from "arcy.js"
export default defineNuxtPlugin(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
onAuthStateChanged(getAuth(), (user) => {
if (!user) return
arcy.identify(user.uid, {
user_first_name: user.displayName,
user_email: user.email,
user_signed_up_at: user.metadata.creationTime
? new Date(user.metadata.creationTime).toISOString()
: undefined,
})
})
})Firebase keeps one display name, so there is no separate last name to send.
import arcy from "arcy.js"
export default defineNuxtPlugin(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
arcy.identify("USER_ID", {
user_first_name: "USER_FIRST_NAME",
user_last_name: "USER_LAST_NAME",
user_email: "USER_EMAIL",
user_signed_up_at: "USER_SIGNED_UP_AT",
})
})Install the package:
npm install arcy.jsCall ARCY from your root layout, inside onMount so it runs in the browser only:
<script>
import { onMount } from "svelte"
import { supabase } from "./supabase"
import arcy from "arcy.js"
onMount(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
supabase.auth.getUser().then(({ data: { user } }) => {
if (!user) return
arcy.identify(user.id, {
user_first_name: user.user_metadata.first_name,
user_last_name: user.user_metadata.last_name,
user_email: user.email,
user_signed_up_at: user.created_at,
})
})
})
</script>
<slot />The name keys come from user_metadata, so use whatever keys you store there.
<script>
import { onMount } from "svelte"
import { getAuth, onAuthStateChanged } from "firebase/auth"
import arcy from "arcy.js"
onMount(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
onAuthStateChanged(getAuth(), (user) => {
if (!user) return
arcy.identify(user.uid, {
user_first_name: user.displayName,
user_email: user.email,
user_signed_up_at: user.metadata.creationTime
? new Date(user.metadata.creationTime).toISOString()
: undefined,
})
})
})
</script>
<slot />Firebase keeps one display name, so there is no separate last name to send.
<script>
import { onMount } from "svelte"
import arcy from "arcy.js"
onMount(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
arcy.identify("USER_ID", {
user_first_name: "USER_FIRST_NAME",
user_last_name: "USER_LAST_NAME",
user_email: "USER_EMAIL",
user_signed_up_at: "USER_SIGNED_UP_AT",
})
})
</script>
<slot />Install the package:
npm install arcy.jsCall ARCY from your root component:
import { Component, OnInit } from "@angular/core"
import { supabase } from "./supabase"
import arcy from "arcy.js"
@Component({ selector: "app-root", templateUrl: "./app.component.html" })
export class AppComponent implements OnInit {
ngOnInit() {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
supabase.auth.getUser().then(({ data: { user } }) => {
if (!user) return
arcy.identify(user.id, {
user_first_name: user.user_metadata.first_name,
user_last_name: user.user_metadata.last_name,
user_email: user.email,
user_signed_up_at: user.created_at,
})
})
}
}The name keys come from user_metadata, so use whatever keys you store there.
import { Component, OnInit } from "@angular/core"
import { getAuth, onAuthStateChanged } from "firebase/auth"
import arcy from "arcy.js"
@Component({ selector: "app-root", templateUrl: "./app.component.html" })
export class AppComponent implements OnInit {
ngOnInit() {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
onAuthStateChanged(getAuth(), (user) => {
if (!user) return
arcy.identify(user.uid, {
user_first_name: user.displayName,
user_email: user.email,
user_signed_up_at: user.metadata.creationTime
? new Date(user.metadata.creationTime).toISOString()
: undefined,
})
})
}
}Firebase keeps one display name, so there is no separate last name to send.
import { Component, OnInit } from "@angular/core"
import arcy from "arcy.js"
@Component({ selector: "app-root", templateUrl: "./app.component.html" })
export class AppComponent implements OnInit {
ngOnInit() {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
arcy.identify("USER_ID", {
user_first_name: "USER_FIRST_NAME",
user_last_name: "USER_LAST_NAME",
user_email: "USER_EMAIL",
user_signed_up_at: "USER_SIGNED_UP_AT",
})
}
}Install the package:
npm install arcy.jsAdd a script to the layout every page uses. Astro bundles it and runs it in the browser:
<slot />
<script>
import { supabase } from "../lib/supabase"
import arcy from "arcy.js"
arcy.init("YOUR_ENVIRONMENT_TOKEN")
supabase.auth.getUser().then(({ data: { user } }) => {
if (!user) return
arcy.identify(user.id, {
user_first_name: user.user_metadata.first_name,
user_last_name: user.user_metadata.last_name,
user_email: user.email,
user_signed_up_at: user.created_at,
})
})
</script>The name keys come from user_metadata, so use whatever keys you store there.
<slot />
<script>
import { getAuth, onAuthStateChanged } from "firebase/auth"
import arcy from "arcy.js"
arcy.init("YOUR_ENVIRONMENT_TOKEN")
onAuthStateChanged(getAuth(), (user) => {
if (!user) return
arcy.identify(user.uid, {
user_first_name: user.displayName,
user_email: user.email,
user_signed_up_at: user.metadata.creationTime
? new Date(user.metadata.creationTime).toISOString()
: undefined,
})
})
</script>Firebase keeps one display name, so there is no separate last name to send.
<slot />
<script>
import arcy from "arcy.js"
arcy.init("YOUR_ENVIRONMENT_TOKEN")
arcy.identify("USER_ID", {
user_first_name: "USER_FIRST_NAME",
user_last_name: "USER_LAST_NAME",
user_email: "USER_EMAIL",
user_signed_up_at: "USER_SIGNED_UP_AT",
})
</script>Install the package:
npm install arcy.jsCall ARCY from your root route, inside an effect so it runs after hydration:
import { useEffect } from "react"
import { supabase } from "./supabase"
import arcy from "arcy.js"
export default function App() {
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
const { data } = supabase.auth.onAuthStateChange((_event, session) => {
const user = session?.user
if (!user) return
arcy.identify(user.id, {
user_first_name: user.user_metadata.first_name,
user_last_name: user.user_metadata.last_name,
user_email: user.email,
user_signed_up_at: user.created_at,
})
})
return () => data.subscription.unsubscribe()
}, [])
return <Outlet />
}The name keys come from user_metadata, so use whatever keys you store there.
import { useEffect } from "react"
import { useUser } from "@clerk/react-router"
import arcy from "arcy.js"
export default function App() {
const { isLoaded, isSignedIn, user } = useUser()
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
}, [])
useEffect(() => {
if (!isLoaded || !isSignedIn) return
arcy.identify(user.id, {
user_first_name: user.firstName,
user_last_name: user.lastName,
user_email: user.primaryEmailAddress?.emailAddress,
user_signed_up_at: user.createdAt?.toISOString(),
})
}, [isLoaded, isSignedIn, user])
return <Outlet />
}import { useEffect } from "react"
import { getAuth, onAuthStateChanged } from "firebase/auth"
import arcy from "arcy.js"
export default function App() {
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
return onAuthStateChanged(getAuth(), (user) => {
if (!user) return
arcy.identify(user.uid, {
user_first_name: user.displayName,
user_email: user.email,
user_signed_up_at: user.metadata.creationTime
? new Date(user.metadata.creationTime).toISOString()
: undefined,
})
})
}, [])
return <Outlet />
}Firebase keeps one display name, so there is no separate last name to send.
import { useEffect } from "react"
import arcy from "arcy.js"
export default function App() {
useEffect(() => {
arcy.init("YOUR_ENVIRONMENT_TOKEN")
arcy.identify("USER_ID", {
user_first_name: "USER_FIRST_NAME",
user_last_name: "USER_LAST_NAME",
user_email: "USER_EMAIL",
user_signed_up_at: "USER_SIGNED_UP_AT",
})
}, [])
return <Outlet />
}No package to install. Use this path when you have no bundler, or when you install through a tag manager. Paste the block from the Installation page into your HTML just before the closing body tag. The first script is ARCY's loader: it already carries your environment's token, loads asynchronously, queues calls made before it finishes, and never needs editing. The second script is yours.
<script>
supabase.auth.getUser().then(({ data: { user } }) => {
if (!user) return
arcy.identify(user.id, {
user_first_name: user.user_metadata.first_name,
user_last_name: user.user_metadata.last_name,
user_email: user.email,
user_signed_up_at: user.created_at,
})
})
</script>This uses the Supabase client your page already creates, so keep that script above it.
<script>
firebase.auth().onAuthStateChanged((user) => {
if (!user) return
arcy.identify(user.uid, {
user_first_name: user.displayName,
user_email: user.email,
user_signed_up_at: user.metadata.creationTime
? new Date(user.metadata.creationTime).toISOString()
: undefined,
})
})
</script>This is the Firebase SDK you get from a script tag, so keep your Firebase scripts above it.
<script>
arcy.identify("USER_ID", {
user_first_name: "USER_FIRST_NAME",
user_last_name: "USER_LAST_NAME",
user_email: "USER_EMAIL",
user_signed_up_at: "USER_SIGNED_UP_AT",
})
</script>Never put the HTML snippet and the package on the same page. Pick one.
The token in init() belongs to one environment. It is public and appears in your page
source, so it is safe to ship. Each environment has its own token; switch environments in the
dashboard sidebar to get another one.
Replace the placeholders
Only the Your own auth tab leaves placeholders behind. Clerk, Auth.js, Supabase and Firebase read the user out of their own session, so if you picked one of those go straight to the next step.
Swap each placeholder for a real, dynamic value from your session code, or every visitor arrives as the same person:
| Placeholder | Meaning |
|---|---|
USER_ID | The signed-in user's ID in your own database |
USER_FIRST_NAME | The user's first name, as a dynamic value |
USER_LAST_NAME | The user's last name, as a dynamic value |
USER_EMAIL | The user's real email, as a dynamic value |
USER_SIGNED_UP_AT | When the user signed up. ISO 8601, e.g. 2019-12-11T12:34:56Z |
Verify the installation
Open the real page in a browser. Settings > Installation watches for traffic from the selected environment on its own, confirms it when it arrives, and names the failure when none does. Each verdict has a fix on Troubleshooting.
Optional steps
Add custom attributes
The attributes you pass to identify() decide what ARCY can work out. Pass plan_value and
plan_cycle for revenue at risk and organization_id for per-account totals. Every key must
be declared under Configure > Attributes before you send it; an
undeclared key is dropped, never stored.
Sign user ids for identity verification
Without it, anyone can type arcy.identify("someone-elses-id") into a browser console and read
that person's conversations. Your server signs each user id with the environment's Secret, and
the browser passes the signature along. Where the Secret is shown and what replacing it does is
on Environments.
The Secret is a server-side credential. Never put it in front-end code, a build variable that reaches the browser, a mobile binary, or a repository. Anyone holding it can sign any user id.
The signature is HMAC-SHA256 of the user id, keyed with the Secret, hex encoded. It covers the
user id only, not the attributes, and it is the recipe Intercom and Segment use. Sign the exact
string you pass to identify(): if your ids are integers and you call
arcy.identify(String(user.id)), sign String(user.id).
Sign with the Secret of the environment whose token the page runs on: ARCY_SECRET beside
ARCY_TOKEN, ARCY_DEV_SECRET beside ARCY_DEV_TOKEN. When the Secret is not set yet, send no
userHash; the call still works while Require identity verification is off.
import { createHmac } from "node:crypto"
const userHash = createHmac("sha256", process.env.ARCY_SECRET)
.update(String(user.id))
.digest("hex")import hashlib, hmac, os
user_hash = hmac.new(
os.environ["ARCY_SECRET"].encode("utf-8"),
str(user.id).encode("utf-8"),
hashlib.sha256,
).hexdigest()user_hash = OpenSSL::HMAC.hexdigest("SHA256", ENV["ARCY_SECRET"], user.id.to_s)$userHash = hash_hmac('sha256', (string) $userId, getenv('ARCY_SECRET'));Pass the result in the third argument of identify():
arcy.identify("USER_ID", {
user_first_name: "USER_FIRST_NAME",
user_email: "USER_EMAIL",
}, {
userHash: "USER_HASH", // computed on your server, never in the browser
})identifyAnonymous() and updateUser() take no hash. Until you switch
Require identity verification on, signatures are checked and recorded but nothing is turned
away. Watch the checks on Identity verification and switch
it on once they pass.
Follow sign-in, profile changes and sign-out
Call ARCY from one place that watches your current user, never from each sign-out button or profile form, so a path added later is not missed:
| Your current user | Call |
|---|---|
| A user id you have not sent yet | identify(id, attributes, { userHash }) |
| The same id, with a changed plan or role | updateUser(changedAttributes) |
| No user, after there was one | reset() |
| No user from the start | identifyAnonymous() |
Without reset(), the next person on a shared browser inherits the last user's identity and
conversation. On a plain HTML site, call identify() or identifyAnonymous() on each page load
and put reset() on your sign-out link, before it navigates.
Report key moments
Call arcy.track(name) where your Product knows a milestone succeeded:
signup finished, first project created, invite sent, checkout paid. Call it after the success,
not on the click that starts it. A flow's completion event, a Target and a Checklist item can
end on the name, so spell it exactly as the dashboard shows it.
arcy.track("project_created")Serve visitors who are not signed in
For public pages, call arcy.identifyAnonymous() instead of identify(). The visitor gets an
id in a first-party cookie on your domain, so a returning visitor is the same person across
sessions and subdomains. An anonymous visitor's chat answers spend the same AI credits as an
identified user's; cap them under Settings > Limits.
Installing through a tag manager
The HTML path works unchanged in Google Tag Manager and similar tools:
- Create a Custom HTML tag.
- Paste the full HTML snippet from the Installation page, both scripts included.
- Trigger it on all pages and publish the container.
The loader ignores a second initialization, so a tag that fires twice does no harm. Electron and self-hosting the loader are not supported; an Electron renderer can use the npm path.