Tell Hamla what happened. It does the marketing that follows.
One script tag sees the browser. One HTTP request tells Hamla what happened inside your own system — a payment, a booking, a cancelled plan. Everything after that is the business owner's job, not yours.
Install Hamla on this site and track signups and purchases
Claude Code, Cursor, Codex — or paste into Lovable, v0, Bolt
The script tag
Copy it from Settings → Website in Hamla, or let the dashboard hand it to you. It carries no secret: the business id in it is public in every page that loads the SDK.
<script src="https://app.hamla.io/s/{businessId}.js" async></script>That one line covers visits, sources, on-site behaviour and anything you call hamla.track() for in the page — no key, because a published bundle cannot hold one.
The secret key stays on your server
For events from your server you need a secret key: Settings → API keys, starting sk_live_. Keep it on your server. It can write customer data, so treat it like a database password.
No account yet? Install anyway.
If you are an agent building someone a site, you do not have their Hamla login and you will not get one mid-task. This gives you a working install immediately — a real business id, a real snippet — plus a single-use link the site's owner uses to take ownership. No authentication, and no key is issued, because none is needed.
Create the install
POST /api/installcurl -X POST https://app.hamla.io/api/install \ -H "Content-Type: application/json" \ -d '{ "website": "raqmi.co", "name": "Raqmi", "locale": "en" }'Five installs per hour per IP address.
Put the snippet in the page
200 OK{ "success": true, "data": { "businessId": "cmf8q1x4k0000", "snippet": "<script src=\"https://app.hamla.io/s/cmf8q1x4k0000.js\" async></script>", "claimUrl": "https://app.hamla.io/claim/9f3c...", "expiresAt": "2026-09-19T08:00:00.000Z", "note": "This install records anonymous traffic only until it is claimed. Print the claim link for the person who owns the site — it is single-use and expires." }, "message": "Install created" }Put snippet in the page. Print claimUrl for the person who owns the site — that is the one thing only you can do, and the install is deleted if nobody follows it.
Print the claim link for the owner
What the owner sees when they open that link: their own traffic — visitors, where they came from, how many reached checkout and left — and one button. Not a signup form.
Until it is claimed it records visits, and nothing more
Page views, sessions, sources, on-site events. An identity carrying an email or a phone number is refused with 422 and a sentence naming the claim as the fix — never silently dropped. platformCustomerId stays allowed: it is your own opaque id and names nobody outside your system.
There is no key to leak
The snippet carries a business id, which is already public in every page that loads the SDK. sk_live_ is issued from Settings → API keys, after a human owns the business.
No campaign launches before the claim
Nothing can message a real person until someone owns the business and can answer for it.
The link works once, then the install is deleted
Long enough to reach an owner who is not the person you are working with, on the next working day. If nobody claims it, the business and everything it collected are removed.
Using Lovable, v0 or Bolt?
Those builders cannot install a skill or connect an MCP server — the only way in is a prompt you paste. This one carries the whole procedure rather than linking to it, because a builder that half-reads an instruction writes a placeholder.
Add Hamla to this site so I get analytics and marketing on it.
1. POST to https://app.hamla.io/api/install with JSON {"website": "<this site's domain>"}.
No API key, no account, no auth header. It returns
{ "data": { "businessId", "snippet", "claimUrl" } }.
2. Put the returned "snippet" string in the <head> of every page, exactly as given.
Do not invent or edit the business id inside it.
3. When you are done, show me the "claimUrl" value. I need it to take ownership —
it works once and expires in 72 hours.
Rules: never send a customer's email or phone to Hamla from this site, and never
put a Hamla secret key in front-end code. Neither is needed for the above.Paste it as-is. If your builder cannot make an HTTP request while it works, run the curl above yourself and give it the snippet that comes back.
Events
The script tag cannot see your database. When someone pays you, books, cancels or finishes a lesson, that happens in your system — one call in the code you already have is what lets Hamla act on it.
curl -X POST https://app.hamla.io/api/sdk/track \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"event": "subscription_started",
"type": "purchase",
"identity": { "email": "omar@raqmi.co" },
"value": 29,
"currency": "JOD",
"idempotencyKey": "invoice_9911"
}'The seven words
Your event name stays yours, in your own language. `type` is how you say what Hamla should DO with it — and it is the difference between a clinic's 100 JOD counting as revenue and counting as nothing.
Sending is free. Reaching people is what costs.
Events are never metered, on any plan — send everything you have, forever. POST /api/sdk/track/batch takes 500 past events at a time so day one is not day zero, and backfilled history never sends a message to anybody.
What one event buys
You write the call once. Everything here is then available to whoever runs the marketing, with no further code from you.
A campaign fires on it
Aftercare an hour after treatment_completed. Onboarding the second somebody pays. Not tomorrow, not in the next batch — right then.
An audience builds from it
Two fields appear per event name: when it last happened, and how many times. “Bought 3+ times, quiet for 60 days” is a win-back list that stays correct on its own.
A sequence ends on it
Anyone who does the thing you wanted stops getting asked, and the campaign's success is measured against it.
On Node
npm install @gethamla/node
Queueing, retries and a flush() for serverless. It never throws and never blocks — a missing key degrades to a logged warning, because marketing data is never worth a customer's checkout.
Drive Hamla from an assistant
https://app.hamla.io/mcp
One OAuth endpoint exposing the same tools Hamla's own agent runs on: read analytics, build campaigns, manage contacts, import a list.
Listed in the MCP registry as io.hamla/hamla, and available as a ChatGPT app.
If you are an agent
Everything above exists in a form you can parse, generated from the same registries the API validates against — so this page cannot describe a field the endpoint would reject.
hamla.io/llms.txt
Every page on this site with a one-line description. docs.hamla.io/llms.txt indexes the full documentation.
hamla.io/developers.md
The same content as plain markdown — no markup to strip, no layout to guess at.
docs.hamla.io/reference/events
Field-by-field, with every error status and what it means. The long form of the table above.
GET /developers.md
# Hamla for developers
> Marketing automation a business installs with one script tag.
> Events are free and unmetered on every plan; contacts and sending are what cost.
## Install
Add before `</head>`. The business id comes from Settings → Website in Hamla
and is public — it appears in every page that loads the SDK.
```html
<script src="https://app.hamla.io/s/{businessId}.js" async></script>
```
This covers visits, sources, on-site behaviour, and `hamla.track()` calls made
in the page. No key: a published bundle cannot hold one.
## No account yet — POST /api/install
If you are an agent building someone a site, you do not have their Hamla login
and you will not get one mid-task. This returns a working install immediately,
with no authentication and no key issued.
```bash
curl -X POST https://app.hamla.io/api/install \
-H "Content-Type: application/json" \
-d '{
"website": "raqmi.co",
"name": "Raqmi",
"locale": "en"
}'
```
```json
{
"success": true,
"data": {
"businessId": "cmf8q1x4k0000",
"snippet": "<script src=\"https://app.hamla.io/s/cmf8q1x4k0000.js\" async></script>",
"claimUrl": "https://app.hamla.io/claim/9f3c...",
"expiresAt": "2026-09-19T08:00:00.000Z",
"note": "This install records anonymous traffic only until it is claimed. Print the claim link for the person who owns the site — it is single-use and expires."
},
"message": "Install created"
}
```
Put `snippet` in the page. **Print `claimUrl` for the person who owns the
site** — that is the one step only you can take, and the install is deleted if
nobody follows it.
What an unclaimed install may do, and what it refuses:
- **Anonymous traffic only.** Page views, sessions, sources, on-site events. An
`identity` carrying an `email` or `phone` is refused with **422** and a
sentence naming the claim as the fix — never silently dropped.
`platformCustomerId` stays allowed: it is your own opaque id and names
nobody outside your system.
- **No credential is issued.** The snippet carries a business id, which is
already public in every page that loads the SDK. `sk_live_` comes from
Settings → API keys, after a human owns the business.
- **No campaign can launch.** Nothing messages a real person until someone owns
the business and can answer for it.
- **Five installs per hour per IP.**
- **The claim link works once and expires in 72 hours.** If nobody claims it,
the business and everything it collected are deleted.
The owner opening that link sees their own traffic — visitors, sources, how
many reached checkout and left — and one button. Not a signup form.
### Lovable, v0, Bolt
They cannot install a skill or connect an MCP server, so paste this in:
```
Add Hamla to this site so I get analytics and marketing on it.
1. POST to https://app.hamla.io/api/install with JSON {"website": "<this site's domain>"}.
No API key, no account, no auth header. It returns
{ "data": { "businessId", "snippet", "claimUrl" } }.
2. Put the returned "snippet" string in the <head> of every page, exactly as given.
Do not invent or edit the business id inside it.
3. When you are done, show me the "claimUrl" value. I need it to take ownership —
it works once and expires in 72 hours.
Rules: never send a customer's email or phone to Hamla from this site, and never
put a Hamla secret key in front-end code. Neither is needed for the above.
```
## Events — POST /api/sdk/track
Server-side. Requires a secret key from Settings → API keys.
```
Authorization: Bearer sk_live_...
```
A publishable key (`pk_live_`) is refused with 403 here: it ships in page
source, so honouring it would let any visitor invent revenue.
```bash
curl -X POST https://app.hamla.io/api/sdk/track \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"event": "subscription_started",
"type": "purchase",
"identity": { "email": "omar@raqmi.co" },
"value": 29,
"currency": "JOD",
"idempotencyKey": "invoice_9911"
}'
```
### Fields
- `identity` **(required)** — Who it happened to — one of email, phone or platformCustomerId. Any one is enough, and it does not have to be the email: a clinic that only ever collects phone numbers sends phone, forever.
- `event` **(required)** — The moment, in your own vocabulary, 1–64 characters. A clinic sends treatment_completed, a school sends lesson_finished. Triggers and audiences match it exactly.
- `type` — What the event MEANS, from the seven below. Leave it out and Hamla infers from the amount and from names it recognises; send it and nothing is ever guessed.
- `value, currency` — The amount and its currency. Counted toward the contact's revenue according to the type — a clinic's money is money.
- `occurredAt` — ISO 8601 or epoch milliseconds. The past is allowed, which is what makes backfilling possible; more than five minutes into the future is refused.
- `properties` — Any JSON worth keeping with the event, forever. Up to 32,768 bytes, stored nested rather than spread into the payload root.
- `traits` — What is true NOW — plan, subscription_status. Creates its own custom field on first sight and newest value wins, so segments can filter on it immediately.
- `tags` — Merge-added to the contact, never replacing tags it already carries.
- `idempotencyKey` — Your invoice or order id. The same key twice is recorded once, which is what makes a retry — or a re-run of your whole backfill — free.
### What `type` means
Your `event` name stays yours, in your own language. `type` says what Hamla
should do with it.
- `purchase` — Money received. Adds to revenue, counts as an order.
- `refund` — Money returned. Subtracts from revenue.
- `booking` — Committed, not yet paid. No revenue.
- `lead` — Identity captured. No revenue.
- `checkout_started` — Started, not finished. No revenue.
- `fulfilled` — They received it. No revenue.
- `cancelled` — It ended. No revenue.
### Backfill — POST /api/sdk/track/batch
1–500 past events per request, same field rules, each with its real
`occurredAt`. Backfilled history **never starts a campaign** and never fires a
segment-entry trigger. Give every row an `idempotencyKey` and the job is safe
to re-run.
### Browser
`hamla.track('download_ebook', { asset: 'guide.pdf' })` — no key needed. These
count as deliberate and can start campaigns. Anything involving money or your
own records should come from your server.
## Node
```bash
npm install @gethamla/node
```
Wraps the same HTTP API with queueing and retries. Never throws, never blocks;
call `flush()` before a serverless runtime freezes.
## MCP
```
https://app.hamla.io/mcp
```
OAuth. Registry name `io.hamla/hamla`. Exposes the same tools Hamla's own
agent runs on — analytics, campaigns, contacts, segments, imports.
## More
- https://hamla.io/llms.txt — this site, indexed
- https://docs.hamla.io/llms.txt — the documentation, indexed
- https://docs.hamla.io/reference/events — every field and error status
- https://github.com/usehamla — agent skills and rules
Handles: `usehamla` on GitHub, `@gethamla` on npm, `hamla.io` on the web.