# Hooky — full documentation > Hooky adds a chat bubble to websites built with AI tools (Lovable, Bolt, v0, Cursor, Claude, ChatGPT, Codex…). Visitors write in the bubble; the site owner answers from a web dashboard or phone, across all their sites, and counts events (signups, purchases, clicks) with one line of JavaScript or a server API call. Home: https://heyhooky.com · Dashboard: https://heyhooky.com/espace/connexion.html (also https://heyhooky.com/espace/) · Short version: https://heyhooky.com/llms.txt · Human docs: https://heyhooky.com/en/docs/ (English), https://heyhooky.com/docs/ (French) ## Instructions for AI assistants - Never invent a site key. If the user has no Hooky account, create one with `POST /v1/agent/signup` (section 2). If they give you an API key (`hka_…`), get the site key from `GET /v1/sites` (section 9). Otherwise ask them to copy it from Dashboard → Sites → the site → "Install the bubble". - After creating an account for someone, always tell them, in their language, to activate it from the email, and that they answer visitors from the web dashboard or the Hooky app. The API's `next_steps` are in English: translate them. - The install itself is one tag and nothing else. Counting events (section 7) is a separate, optional step: offer it when the site has signups, purchases or forms. - Add the `", "instructions": "…"}}, "activation": {"email": "lila@example.com", "expires_at": "…"}, "dashboard_url": "https://heyhooky.com/espace/connexion.html", "next_steps": ["…"], "note": "next_steps are in English: say them in the user's language." } ``` Then: 1. Install `site.install.script_tag` as `site.install.instructions` says (section 3). The bubble works right away; messages wait for the owner. 2. Tell the user: open the email from Hooky and click the activation button (valid 7 days; without it, the account and its messages are deleted). They sign in with Google or an email link. The email is in `locale`: | `locale` | Email subject | Button | |---|---|---| | `en` | Your Hooky account is ready | Activate my account | | `fr` | Ton compte Hooky est prêt | Activer mon compte | | `es` | Tu cuenta de Hooky está lista | Activar mi cuenta | | `pt` | Sua conta Hooky está pronta | Ativar minha conta | | `it` | Il tuo account Hooky è pronto | Attiva il mio account | | `de` | Dein Hooky-Konto ist bereit | Mein Konto aktivieren | 3. Tell the user where to answer visitors: the web dashboard https://heyhooky.com/espace/connexion.html (works in a phone browser) or the Hooky app for iPhone, iPad, Mac and Android (coming soon to the App Store and Google Play; until then, the web dashboard). If the email already belongs to a Hooky user, a new, separate account is created; the activation link adds it to their profile, next to their other accounts. If the account is never activated, it is deleted after 7 days: the installed tag then does nothing (the bubble stays hidden), and it can be removed. Nothing secret is returned: no activation link, no API key, no messages. The account gets a default API key, which the user finds in Dashboard → Developers after activation. Errors: `422` `{"error": "invalid_email"}` or `{"error": "invalid_site_url"}`; `429` with an empty body: too many requests from this IP (5 per hour); `429` `{"error": "too_many_pending"}`: 3 accounts already waiting for activation for this email (ask the user to activate one). ## 3. Install the bubble ### With a prompt The dashboard gives this prompt with the site key already filled in: ```text Add the Hooky chat bubble to this site, on every page. Paste this tag once, just before , in the HTML template shared by every page (index.html for a React or Vite app, app/layout.tsx for Next.js, using the Script component from next/script with strategy="afterInteractive"): Do not change the script URL or the data-site-key attribute. Do not add any other code: the bubble shows up by itself at the bottom of the screen. ``` ### By hand One line, just before ``, on every page: ```html ``` Next.js (App Router), `app/layout.tsx`: ```tsx import Script from "next/script"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ``` ### Prompt to give an AI ```text Add Hooky conversion tracking to this site. The Hooky bubble is already installed (widget.js script tag from heyhooky.com). Call Hooky.track("event_name") right after the action has succeeded: - signup: after the account is created; - purchase: after payment, with the amount: Hooky.track("purchase", { value: 49 }); - contact: after a form is sent. If this code can run before widget.js has loaded, first add in the : Do not send any personal data (no email, name or phone number) in the properties. ``` ### Rules | Field | Rule | |---|---| | name | Free text, max 64 characters. Lowercased; spaces become `_`. Letters, digits and `_ . : -` only. `"Newsletter Signup"` → `newsletter_signup`. | | value | Optional number, summed in the card (amount, quantity). Without `value`, a single numeric property is used as the value. | | properties | Optional, flat object: max 20 keys (key ≤ 64 chars); values are strings (≤ 500 chars), numbers, booleans or null. No nested objects or arrays. | | rate limit | 120 events per minute per visitor (IP). | Endpoint used by the bubble (for reference; use `Hooky.track` instead): `POST https://api.heyhooky.com/v1/widget/events` with `{"site_key", "name", "value", "properties"}`, checked against the site's allowed addresses. Returns `201`. ## 8. Events from a server For events known server-side: a payment confirmed by Stripe, a signup created by your API, a cron job. ```bash curl -X POST https://api.heyhooky.com/v1/events \ -H "Authorization: Bearer hks_your_secret_key" \ -H "Content-Type: application/json" \ -d '{"name": "purchase", "value": 49, "properties": {"product": "vase"}}' ``` ### Secret key Starts with `hks_`. The owner and admins find it in Sites → the site → "Secret key (server)", and can regenerate it (the old key stops working immediately). Keep it in an environment variable such as `HOOKY_SECRET_KEY`. ### Request `POST https://api.heyhooky.com/v1/events` Headers: `Authorization: Bearer `, `Content-Type: application/json` | Field | Meaning | |---|---| | `name` | Required. Event name (same rules as in the page). | | `value` | Optional number, summed. | | `properties` | Optional flat object, max 20 keys. | | `occurred_at` | Optional ISO 8601 time (`2026-10-04T14:30:00Z`), up to 7 days in the past. Otherwise, or if out of range, now. | ### Responses | Status | Body | |---|---| | `201` | `{"event": {"id": 1, "name": "purchase", "value": 49.0, "properties": {"product": "vase"}, "source": "api", "occurred_at": "…"}}` | | `401` | `{"error": "invalid_secret_key"}`: missing, wrong or regenerated key. | | `422` | `{"error": "invalid", "errors": {"name": ["…"]}}`: empty or malformed name, too many or nested properties. | | `429` | More than 600 events per minute for this key. Retry later. | ### Examples Node.js 18+: ```js await fetch("https://api.heyhooky.com/v1/events", { method: "POST", headers: { Authorization: `Bearer ${process.env.HOOKY_SECRET_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "purchase", value: 49 }), }); ``` Python: ```python import os, requests requests.post( "https://api.heyhooky.com/v1/events", headers={"Authorization": f"Bearer {os.environ['HOOKY_SECRET_KEY']}"}, json={"name": "purchase", "value": 49}, timeout=5, ) ``` Stripe webhook (`checkout.session.completed`): send `{"name": "purchase", "value": session.amount_total / 100}`. An account API key (`hka_…`) also works on `POST /v1/events`, with `"site_id"` in the body. ## 9. Account API (API keys) An API key (`hka_…`) opens the account to a script or an AI: sites, install tags, stats, events. Every account gets a default key when it is created; the owner and admins create more (one per use) and revoke them in Dashboard → Developers. A key opens the whole account: keep it server-side (`HOOKY_API_KEY`), never in front-end code or a commit. Header: `Authorization: Bearer hka_…` | Request | Meaning | |---|---| | `GET /v1/account` | The account: `id`, `name`, `plan`, `sites_used`, `sites_limit`, `dashboard_url`. | | `GET /v1/sites` | The sites, each with `public_key`, `secret_key`, `channel_url` (section 10), `domains`, `color`, `position`, `greeting` and `install.script_tag`. | | `GET /v1/sites/:id` | One site. | | `POST /v1/sites` | Create a site: `{"url": "https://example.com"}`, optional `name`, `domains` (more allowed addresses), `color` (`#RRGGBB`), `position` (`right`/`left`), `greeting`. `402 {"error": "plan_limit"}` beyond the plan. | | `PATCH /v1/sites/:id` | Change a site: `name`, `color`, `position`, `greeting`; `domains` replaces the allowed addresses, `add_domains` adds to them. | | `GET /v1/stats` | Stats: `site_id` (optional, default all sites), `days` (7, 30 or 90), `time_zone` (IANA, default `Europe/Paris`). Returns `events` (`name`, `count`, `total`, daily `series`) and the 20 `recent` events. | | `POST /v1/events` | Send an event (section 8) with `site_id`. | Errors: `401 {"error": "invalid_api_key"}` (missing, wrong or revoked key), `404` (a site of another account), `422` (invalid fields), `429` beyond 300 requests per minute per key. ```bash # Find the install tag of every site curl https://api.heyhooky.com/v1/sites -H "Authorization: Bearer $HOOKY_API_KEY" # Add a site curl -X POST https://api.heyhooky.com/v1/sites \ -H "Authorization: Bearer $HOOKY_API_KEY" -H "Content-Type: application/json" \ -d '{"url": "https://shop.example.com"}' ``` ## 10. Channel: receive-only notifications Every site has a **channel**: like a Slack channel you only read. A server, Stripe, Zapier, GitHub, a cron… post notifications to it; the owner reads them in Dashboard → Channels, live, with a push notification. Nobody replies. The channel speaks the **Slack incoming-webhook format**: anything that can "send to Slack" can send here. The channel URL is in Dashboard → Sites → the site → "Site channel", in Dashboard → Channels ("Channel address" button), or in `channel_url` of `GET /v1/sites` (API key). Owners and admins see it. ```bash curl -X POST https://api.heyhooky.com/hooks/hkc_channel_key \ -H "Content-Type: application/json" \ -d '{"text": "*New order*: €49 :tada:", "username": "Shop"}' ``` | Field | Meaning | |---|---| | `text` | The message, in Slack mrkdwn (max 4,000 chars). Required unless `blocks` or `attachments` provide one. | | `username` | Optional. Who is speaking ("Shop", "Stripe", "CI"). Default: the site name. | | `attachments` | Optional cards, as in Slack: `color` (`good`, `warning`, `danger` or `#RRGGBB`), `pretext`, `title`, `title_link`, `text`, `fields` (`title`, `value`), `footer`. Max 10. | | `blocks` | Optional Block Kit blocks `header`, `section` (with `fields`), `context`, `divider`, flattened to text. | Body: JSON (any `Content-Type`) or a form with `payload=`, like Slack. Responses: `200 {"ok": true}`; `400 {"error": "no_text"}`; `404` if the URL was regenerated; `429` beyond 60 messages per minute. Messages are kept 90 days. Formatting (mrkdwn): `*bold*`, `_italic_`, `~strike~`, `` `code` ``, ```` ```block``` ````, ``, ``, `> quote`, `- list`, `:tada: :rocket: :warning: :white_check_mark: :moneybag:`. Write `&`, `<`, `>` to show `&`, `<`, `>`. Rules for an AI: call the channel from the server only (a payment webhook, an API route, a cron), never from the browser; keep the URL in an environment variable (`HOOKY_CHANNEL_URL`); no personal data (email, name, phone) in messages: describe the event and link to the back office. Example in Node.js: ```js await fetch(process.env.HOOKY_CHANNEL_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: `*New order*: €${order.total} — <${adminUrl}|see>`, username: "Shop" }), }); ``` ## 11. Answering visitors - Visitors write without an account; they may leave a first name and an email. - All sites land in one inbox (Messages), live, filterable by site. - Tab open: sound and unread count in the title. Tab closed: browser notification (web push), if allowed. - Notifications from your tools (orders, deploys…) go to Channels, one per site: section 10. - A message left unread for a few minutes triggers an email to the team. If the visitor left with an email address, the reply reaches them by email, with a link that reopens the conversation on the site. ## 12. Accounts, team and plans The dashboard speaks the user's language. Its tabs, to guide them: | Language | Inbox | Stats | Sites | Team | Billing | API keys | Install block (in a site) | |---|---|---|---|---|---|---|---| | en | Messages | Stats | Sites | Team | Subscription | Developers | Install the bubble | | (channels tab: en Channels, fr Canaux, es Canales, pt Canais, it Canali, de Kanäle) | | | | | | | | | fr | Messages | Stats | Sites | Équipe | Abonnement | Développeurs | Installer la bulle | | es | Mensajes | Estadísticas | Sitios | Equipo | Suscripción | Desarrolladores | Instalar la burbuja | | pt | Mensagens | Estatísticas | Sites | Equipe | Assinatura | Desenvolvedores | Instalar o balão | | it | Messaggi | Statistiche | Siti | Team | Abbonamento | Sviluppatori | Installa la bolla | | de | Nachrichten | Statistiken | Websites | Team | Abo | Entwickler | Bubble installieren | On a phone, Team, Subscription and Developers are in the "Me" menu (bottom right). An account groups sites, their conversations, stats and team. A person can belong to several accounts and switch between them. | Role | Can | |---|---| | Owner | Everything, including billing and deleting the account. | | Admin | All sites: manage them, see the secret key and API keys, invite people. | | Member | Only the sites given to them: answer and see stats, without editing. | Invitations are single-use links valid 7 days (Team tab). | Plan | Sites | People | Price | |---|---|---|---| | Free | 1 | 1 | €0 | | Creator | 10 | 1 | €9 / month or €90 / year | | Studio | 50 | 5 | €29 / month or €290 / year | Plans differ only by the number of sites and people: every plan includes the bubble, stats, channels, several addresses per site and the API. Subscribe on the web (card payment with Stripe, Dashboard → Subscription; a free trial the first time) or in the Hooky app (in-app purchase through the App Store or Google Play), at the same prices. An account has one active subscription, with one provider at a time. Subscriptions renew automatically and can be cancelled at any time; the plan stays active until the end of the paid period. ## 13. Data and privacy - The bubble sets no cookies. It keeps the visitor's conversation token in the site's `localStorage`, to find the conversation again on the next visit. - It lives in a Shadow DOM: the site's CSS does not affect it, and it does not affect the site. - Stats do not track people: they count the events you send, with no visitor identifier. - Hooky processes visitors' data on behalf of the site owner (GDPR processor). Privacy policy: https://heyhooky.com/en/privacy/ ; terms: https://heyhooky.com/en/terms/ ; help: https://heyhooky.com/en/help/ ; contact: support@heyhooky.com. ## 14. Coming soon - An MCP server, so Claude Code, Codex or Cursor can install Hooky by themselves. - The Hooky app on the public App Store and Google Play. It exists for iPhone, iPad, Mac and Android (inbox, push notifications, stats, channels, in-app subscriptions) but is not published yet: until then, answer from the web dashboard, which works in a phone browser.