# Install Escendo

Instructions for a coding agent (Claude Code, Cursor, Codex, or similar) or a person. Escendo identifies the companies and, for United States visitors, the people visiting a website. Human-readable version: https://escendo.co/agents

You need a **site key** (`esc_` followed by 32 hex characters). If the person gave you one, go to step 0. If they have no Escendo account, sign them up first (below): it takes one API call, and the only thing they do themselves is enter a card.

## Sign-up (no account yet)

Ask the person for their **work email** and the site's **domain** (e.g. `acme.com`). Then:

```bash
curl -sS -X POST https://app.escendo.co/api/agent/signup \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"email":"jane@acme.com","domain":"acme.com","name":"Acme","vertical":"saas"}'
```

`vertical` is optional: `saas`, `law`, `clinic`, `agency`, `home_services` or `other`. If the link or instructions that brought the person here carried a `utm_campaign` or `ref` (for example `escendo.co/agents?utm_source=reddit&utm_campaign=r2`), add `"source": "reddit/r2"` (or the bare `ref` value, which takes precedence). Keep the Idempotency-Key: if the call fails on the network, send the exact same request with the same key and you get the same answer.

The response gives you:

- `site_key`: the public key for the tag. Continue with step 0 using it.
- `agent_token`: shown **once**. Keep it for this session only, send it only as `Authorization: Bearer <token>`, and never write it into the site's code.
- `checkout_url`: give it to the person to enter a card. New accounts get 7 days free.

After paying, the person gets an email from Escendo. **They must open it and confirm the setup.** Until they do, nothing you configure below (Slack, CRMs, webhooks) receives a lead. Tell them so. If it is lost: `POST https://app.escendo.co/api/agent/resend-confirmation` with the Bearer token.

With the token you can then:

- `GET https://app.escendo.co/api/agent/status`: what is done, and a `next` list of what is left. `ready: true` means finished.
- `POST https://app.escendo.co/api/agent/config` with any of `slack_webhook_url`, `alert_email`, `hot_pages`, `vertical`.
- `POST https://app.escendo.co/api/agent/crm` with `{"provider": "hubspot", "key": "…"}` (also `pipedrive`, `attio`, `close`, `gohighlevel`, `clio`, `freshsales`, `copper`, `insightly`, `notion`; some take an `extra` field).

If sign-up answers **409 `account_exists`**, the person already has an account: ask them to create a token at https://app.escendo.co/settings#agent and give it to you, and their site key from the dashboard. The full reference, with every response and error, is https://escendo.co/agent-api.md.

## 0. Check the key first

```bash
curl https://connect.escendo.co/v/YOUR_SITE_KEY
```

- `{"found":true, ...}`: the key is valid. Check that `domains` includes this site's domain; if it lists only other domains, or `receivingBeacons` is already `true` before you have deployed anything, the key may belong to a different site, so confirm it with the user. Otherwise carry on. `receivingBeacons` will be `false` until the tag is live; that is expected.
- `{"found":false}` (HTTP 404): the key does not exist, usually a copy-paste error. A key that does not match `^esc_[0-9a-f]{32}$` was truncated or mangled in the paste. Stop and ask the user to copy it again from the dashboard, and tell them what you will change once you have it. Do not edit any files with a key that is not found.

## 1. Add the tag

```html
<script async src="https://connect.escendo.co/s.js?k=YOUR_SITE_KEY"></script>
```

Make it the last element inside `<body>`, on every page. It is async and wrapped in try/catch, so it cannot block rendering or throw into the page. Add it once, in the shared layout, not per page. Use the plain tag even if the codebase loads other scripts another way.

| Stack | Where |
|---|---|
| Next.js, app router | `app/layout.tsx`, inside `<body>`. Use a plain `<script async>` tag, not `next/script`, so it is in the server-rendered HTML. |
| Next.js, pages router | `pages/_document.tsx`, inside `<body>` |
| Astro, Remix, SvelteKit, Nuxt | the root layout's `<body>` |
| Plain HTML | before `</body>` in every page, or the shared include |
| WordPress | a header-and-footer scripts plugin (e.g. WPCode) → Footer, or the theme's `footer.php` before `</body>` |
| Webflow | Site settings → Custom code → Footer code, then publish |
| Wix | Settings → Custom Code → Body – end, All pages (premium plan) |
| Squarespace | Settings → Advanced → Code Injection → Footer |
| Shopify | `theme.liquid`, before `</body>` |
| Google Tag Manager | Custom HTML tag, trigger All Pages. Also needs the DNS record in step 3. |

Client-side navigation in single-page apps is tracked automatically.

## 2. Content-Security-Policy

Search the codebase for `Content-Security-Policy` (headers config, middleware, meta tags, hosting config such as `vercel.json` or `_headers`). If there is none, skip this step. If there is one, add these sources to the existing directives:

```
script-src  https://connect.escendo.co https://cdn.sitelytics.tech https://pxdrop.lijit.com
connect-src https://connect.escendo.co https://api.sitelytics.tech
img-src     https:
```

`connect.escendo.co` is Escendo. `cdn.sitelytics.tech` and `api.sitelytics.tech` are the identity provider's script and API, which the tag loads; `pxdrop.lijit.com` is a script that one loads. The identity provider also requests images from hosts chosen at runtime, which is why `img-src` needs `https:`; if the site owner will not allow that, identification still works but matches fewer visitors. Tell the user which directives you changed.

## 3. Domains

Escendo accepts page views only from domains registered against the key, in the dashboard. Webflow serves from two addresses (`site.webflow.io` and the custom domain): both must be added.

People are only sent for verified domains. Verification looks for the key in the homepage HTML. If the tag is injected by Google Tag Manager, it is not in the HTML, so add a DNS TXT record on the domain:

```
escendo-verify=YOUR_SITE_KEY
```

## 4. Deploy, then verify

The tag only works once the change is deployed to the live site. If you can deploy, do, then open any page of the live site once in a browser (a plain `curl` of the page does not run the tag) and run the check below.

If you cannot deploy, stop here and tell the user, in one message: what you changed, that they need to deploy it, open their site once in a browser, and then either run the check below or ask you to. That is a complete install; do not report it as a failure.

```bash
curl https://connect.escendo.co/v/YOUR_SITE_KEY
```

```json
{ "found": true, "receivingBeacons": true, "lastBeaconAt": "2026-10-16T15:04:11Z",
  "domains": [{ "domain": "example.com", "verified": true }] }
```

| Result | Meaning | Do |
|---|---|---|
| `found: false` (404) | The key does not exist | Ask the user to re-copy it from the dashboard |
| `receivingBeacons: true` | Installed and working | Done |
| `receivingBeacons: false` | Nothing has arrived yet | Check, in order: the change is deployed; the tag is in the served HTML (`curl -s https://example.com \| grep s.js`); the domain is registered against the key; the CSP |
| `verified: false` on a domain | People will not be sent for it yet | `curl -X POST https://connect.escendo.co/verify/YOUR_SITE_KEY` once the tag or DNS record is live |

## 5. Propose hot pages

Hot pages are the paths that signal buying intent; visits to them are flagged in every alert. Read the site's routes and propose a list from what actually exists: pricing, demo, booking, contact, quote, consultation and high-intent service pages. Matching is by prefix at a path segment: `/pricing` covers `/pricing/enterprise` but not `/pricing-archive`. The user saves them in the dashboard.

## 6. Optional: mark calls to action

Only if the user asks. Obvious buying actions are detected automatically; marking one makes its label in alerts exact:

```html
<button data-escendo-cta="Book demo">Book a demo</button>
```

## 7. Optional: connect an MCP client

Read-only access to the site's visitors for an agent. The user creates a token in the dashboard under Settings → MCP.

```bash
claude mcp add --transport http escendo https://connect.escendo.co/mcp --header "Authorization: Bearer YOUR_MCP_TOKEN"
```

Tools: `recent_viewers` (recent people and companies with pages read) and `traffic_stats` (daily counts, no personal data).

## What Escendo does not collect

No session replay, keystrokes, heatmaps or form field values. The raw IP address is never stored. Person-level identification runs for United States visitors only. Browsers sending Global Privacy Control or Do Not Track are never identified. The site owner must add a notice to their privacy policy; the dashboard provides the wording.
