Quickstart
Two paths, three steps. Install arcy.js with NPM or a single HTML snippet, identify your users, and verify the installation from the dashboard.
ARCY is installed by adding arcy.js to your product. There are two paths and three required steps, and the whole thing is designed so a developer can finish without reading anything beyond this page.
Your environment's install snippets, with the real token already filled in, live in the dashboard under Settings > Installation. This page explains the same steps in full.
Everything ARCY does is configured from the dashboard, not from code. There is no config file, no build step, and nothing to redeploy when you change settings.
Required steps
Choose your installation method
- NPM, for browser apps using a module bundler (Webpack, Vite, Rollup). The recommended path.
- HTML, for everything else, including tag managers. One snippet pasted before the
ending
</body>tag.
Pick one. Never install both on the same page.
Install
First, run this in your terminal:
npm install arcy.jsThen import and use the arcy object where your app boots:
import arcy from "arcy.js"
arcy.init("YOUR_ENVIRONMENT_TOKEN") // Copy the real token from Settings > Installation
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",
})Only do this if you're not using NPM. Copy the snippet from
Settings > Installation in your dashboard and paste it into your HTML document
before the ending </body> tag.
The snippet has two parts:
- ARCY's loader script. Minified, self-contained, and already carrying your environment's token. It loads arcy.js asynchronously, so your page load speed is unaffected, and it queues any calls made before the script finishes loading. Paste it exactly as is. You never need to read, edit, version, or host it.
- Your identify script. A short, readable script containing the
arcy.identify()call. This is the only part you edit.
<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>The token in init() identifies one environment (Production, Staging, and so on). It
is public by design and appears in your page source, so it is safe to ship. Each
environment has its own token; use the environment switcher in the dashboard sidebar
to get another environment's snippet.
Replace the placeholders
Replace the placeholders with real, dynamic values from your auth or session layer:
| 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 |
When you are done, click Verify installation on the dashboard's Installation page. It confirms traffic is arriving from your environment and diagnoses the common failure cases specifically: no traffic yet, an unverified origin, or a snippet carrying another environment's token.
Optional steps
Add custom attributes
The attributes object in identify() is technically optional, but the attributes you
pass decide which intelligence ARCY can compute. Passing nothing produces a working
widget and a nearly useless dashboard.
- Pass
plan_valueandplan_cycleto enable revenue-at-risk insights. Without them, ARCY has no revenue figure to attach to a struggling account, and the insight is not degraded, it is impossible. - Pass
organization_idto get account-level rollups instead of isolated users. - Add any custom attribute your product knows about the user.
Custom attributes must be declared before ARCY stores them: define each one under Agent > Attributes in the dashboard, then send it. A key that arrives without a declaration is dropped, counted, and named on Verify installation, never silently stored. This keeps a typo from permanently entering your schema.
Enforce identity verification
Strongly recommended for production. Without it, anyone can open your site, type
arcy.identify("someone-elses-id") into the browser console, and read that person's
conversation history back out of the widget. Identity verification closes that by having
your own server sign each user id with a Secret the browser never sees.
It takes one line on your backend and one extra argument on the front end.
1. Get your Secret
Your environment's Secret is in the dashboard under Agent > Environments. It is shown once when the environment is created and once again each time you rotate it, so store it wherever you keep your other server-side credentials.
The Secret is a server-side credential. Never put it in front-end code, a build
environment variable that reaches the browser, a mobile app binary, or a repository.
Anyone holding it can sign any user id. The Token you pass to arcy.init() is the
public one and is safe in the browser. The Secret is not.
2. Sign the user id on your server
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 same recipe Intercom and
Segment use, so an existing implementation usually ports directly.
Compute it wherever you already render the page or serve the session, and pass the result to the front end alongside the user id.
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) $user->id, getenv('ARCY_SECRET'));Sign the exact string you pass to identify(). If your user ids are integers and
you call arcy.identify(String(user.id)), sign String(user.id) too. A signature over
42 will not verify a call identifying "42".
3. Pass it to identify()
The hash rides in a third argument, an options object:
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. The first asserts no user id, and
the second inherits the verification state of the identify() call that came before it.
4. Turn enforcement on
Until you switch it on, verification runs as a dry run: signatures are checked and the result is recorded, but nothing is turned away. That is deliberate, so you can confirm your signing works before it can lock anyone out.
Watch the results, and when they are clean, switch Enforce identity verification on for the environment. From then on, a call whose signature does not verify is not trusted: the session continues as anonymous rather than being attributed to a user it could not prove.
Turn enforcement on only after you see verified sessions arriving. With it on and the signing broken, every user is treated as anonymous until you deploy a fix.
Rotating the Secret
Rotating breaks your backend, not your page, so ARCY accepts signatures made with the previous Secret for 24 hours after a rotation. That window is there so a deploy can follow a rotation without sending your users anonymous in between.
If you rotated because the Secret leaked, do not wait for the window: revoke the previous Secret immediately from the same screen. That takes effect at once, and any signature still made with the old value stops verifying.
Install for unauthenticated users
For public pages with no signed-in user, swap identify() for:
arcy.identifyAnonymous()A unique id is generated and stored in localStorage, then reused on later visits, so
a returning anonymous visitor is the same user across sessions.
Anonymous activity consumes credits on the same meter as identified activity. If you put ARCY on a high-traffic public site, understand the cost before you do it, and set the separate anonymous usage cap under Settings > Limits. It bounds anonymous spend without throttling your signed-in users, and setting it to 0 disables anonymous serving entirely.
Installing through a tag manager
The HTML path works unchanged inside a tag manager such as Google Tag Manager:
- Create a new Custom HTML tag.
- Paste the full HTML snippet from Settings > Installation, both scripts included.
- Set the trigger to All Pages, and publish the container.
The loader guards against double-initialization, so a snippet that fires twice (common
with tag managers) does not break anything. Tag manager sandboxes that strip <script>
tags must allow custom HTML for the snippet to run.
Electron apps and self-hosting the loader are not supported in the beta. An Electron renderer can usually use the NPM path unmodified.