> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hamla.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Hamla Events

> Tell Hamla what happens inside your own system — a signup, a payment, a booking — and turn each of those moments into a campaign, an audience, or a goal.

Hamla can only act on what it can see.

It already sees your website. The script tag watches visits, clicks and carts on its own. But it cannot see inside your system. When someone pays you, cancels, books an appointment or finishes a lesson, that happens in **your** database. Hamla has no idea it happened.

A **Hamla event** is you telling Hamla: *this just happened.*

```
"Omar just subscribed."
```

That is the whole idea. Hamla does the rest — it finds Omar, writes it on his timeline, moves him from *trial* to *customer*, and if a campaign was waiting for exactly that moment, it starts.

### Why it is worth ten minutes of your developer's time

| Without events                  | With events                                                                                   |
| ------------------------------- | --------------------------------------------------------------------------------------------- |
| Omar visited your pricing page. | Omar visited your pricing page, subscribed on Tuesday, and has not opened the app in 12 days. |

The first one can send a newsletter. The second one can win him back.

## Send your first event

<Steps>
  <Step title="Get a secret key">
    Dashboard → **Settings → API keys**. You want the **secret** key — it starts with `sk_live_`.

    Keep it on your server. It can write customer data, so treat it like a database password. It must never appear in a web page or a mobile app.
  </Step>

  <Step title="Send the event where it happens">
    In the code you already have — the payment webhook, the booking confirmation, the signup handler — add one call.

    ```bash theme={null}
    curl -X POST https://app.hamla.io/api/sdk/track \
      -H "Authorization: Bearer sk_live_xxx" \
      -H "Content-Type: application/json" \
      -d '{
        "event": "subscription_started",
        "type": "purchase",
        "identity": { "email": "omar@raqmi.co" },
        "value": 29,
        "currency": "JOD"
      }'
    ```

    On Node.js, `npm install @gethamla/node` gives you the same thing with queueing and retries built in:

    ```ts theme={null}
    import { Hamla } from '@gethamla/node';
    const hamla = new Hamla(); // reads HAMLA_API_KEY

    hamla.track({
      event: 'subscription_started',
      type: 'purchase',              // what it MEANS — optional, never guessed
      identity: { email: 'omar@raqmi.co' },
      value: 29,
      currency: 'JOD',
    });
    ```

    Only two things are required: **who** (`identity` — an email, a phone, or your own user id) and **what** (`event` — any name you like).

    `type` is the optional third. Your `event` name stays yours, in your own language; `type` tells Hamla what to *do* with it, from a list of seven (`purchase`, `refund`, `booking`, `lead`, `checkout_started`, `fulfilled`, `cancelled`). Leave it out and Hamla infers — an amount means money, and names it already knows keep working — but sending it means nothing is ever guessed. Full list in the [events reference](/reference/events).
  </Step>

  <Step title="Check it arrived">
    Open that person's contact page in Hamla. The event is on their timeline, with the time it happened.

    That is the loop closed. Everything below is what you can now do with it.
  </Step>
</Steps>

## Who the person is

`identity` answers it, and **any one field is enough**. You never send all three, and you never need an email.

| Send                                   | When that is what you have                                |
| -------------------------------------- | --------------------------------------------------------- |
| `{ "email": "omar@raqmi.co" }`         | accounts, newsletters, invoices                           |
| `{ "phone": "+962790000000" }`         | clinics, restaurants, delivery, anything WhatsApp-first   |
| `{ "platformCustomerId": "user_812" }` | your own user id — often the only thing a webhook carries |

A clinic that has never asked anyone for an email address is a first-class Hamla customer: send the phone, on its own, forever.

Send more than one and you do **not** get two contacts. Hamla merges them into one person and keeps each as a way to find them — so a phone that arrives months later joins the email you sent first, and their whole history stays in one place. That is worth doing whenever you have both.

<Note>
  Identity is not permission. Knowing someone's phone number is what lets Hamla recognize them; it is not consent to send them an SMS or a WhatsApp message. That is a separate opt-in, and Hamla keeps the two apart on purpose.
</Note>

### And what to call them

Identity says *who*. It does not say what to call them — and a contact with no name is why `Hi {{contact.firstName}}` comes out as `Hi Friend`.

Names go through `identify`, the companion door to `track`:

```bash theme={null}
curl -X POST https://app.hamla.io/api/sdk/identify \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "identity": { "phone": "+962790000000" },
    "profile": { "name": "Sara Al Otaibi" },
    "method": "booking"
  }'
```

```ts theme={null}
hamla.identify({
  identity: { email: 'omar@raqmi.co' },
  profile: { name: 'Omar Al Saleh' },
  method: 'signup',
});
```

Send the name in whatever shape you already store it:

| Field                                    | Use it when                                                             |
| ---------------------------------------- | ----------------------------------------------------------------------- |
| `profile.name`                           | you have one name field — a signup form, a Google profile, most systems |
| `profile.firstName` / `profile.lastName` | your database already keeps them apart                                  |

`name` is split on arrival: the first word is the first name, the rest is the family name, so `Sara Al Otaibi` is greeted as **Sara** and `محمد بن عبد الله` as **محمد**. Send both shapes together and the explicit parts win, field by field.

Call it wherever you already know them — signup, checkout, login. Calling it on every login is safe and worth doing: Hamla fills in what is missing and never overwrites a name that is already there, so a correction someone makes in their profile survives.

<Note>
  `profile` also takes `country`, `city`, `language` and `timezone`. `timezone` is what keeps a "one day later" step out of someone's night — the browser SDK fills it in for you; a server should send it when it knows.
</Note>

## Now use it

Sending the event is step one. Here is what it is *for*.

### 1. Start a campaign the second it happens

Open a campaign → **When does the campaign start?** → **App event** → App: **Your app's events** → **Event name**, and type it.

That is it. Every person who fires that event from now on gets your message. Not tomorrow, not in the next batch — right then.

<Note>
  The name must match what your code sends, exactly: `treatment_completed`, not `Treatment Completed`. Once an event has arrived at least once, it appears in the picker so you can select it instead of typing it.
</Note>

Under the name there is **Can enter again later**:

* **Off** — each person is enrolled once, ever. Use it for a welcome sequence.
* **On** — they can be enrolled again in a later period. Use it for renewals, repeat purchases, monthly check-ups.

### 2. Build an audience out of it

The moment your first `purchase` event arrives, two new fields appear in the segment builder under **Events**:

| Field                         | What it means         | Ask it things like                                                          |
| ----------------------------- | --------------------- | --------------------------------------------------------------------------- |
| **purchase — last time**      | when it last happened | "bought more than 60 days ago", "bought in the last 7 days", "never bought" |
| **purchase — how many times** | the running count     | "bought 3 or more times", "bought exactly once"                             |

Nobody creates these fields. They exist because you sent the event.

Combine the two and you have your best customers going quiet: *bought 3+ times, last time more than 60 days ago.* That is a win-back list, and it stays correct forever, because your server keeps sending events.

<Note>
  The **Events** group appears in the builder only after the first event of any kind has arrived. Before that there is nothing to filter on.
</Note>

### 3. Make it the campaign's finish line

A campaign should stop bothering someone the moment they do the thing you wanted.

In the campaign → **How the sequence ends** → **When an event happens** → pick your event.

Now anyone who fires `subscription_started` stops receiving the rest of the sequence. It is checked before every single send. The same event also becomes what the campaign's success is measured against in your reports.

### 4. Or just ask

You do not have to build anything. Type it:

> "Everyone who finished a treatment last month but has not booked again — send them a reminder."

Hamla reads the same events and does it.

### And two things that happen on their own

* **The person moves through their journey.** Some names carry meaning: `trial_started`, `subscription_started` and `purchase` move someone into *Decision*; `subscription_renewed` and `repeat_purchase` move them into *Retention*. Use those names when they fit and the journey stays honest with no extra work. Your own invented names are still recorded — they just do not move the stage.
* **Everything feeds the numbers.** Engagement, scoring and attribution all read your events, so reporting reflects what really happened in your business, not only what happened in a browser.

## Events and traits are different things

This is the one distinction worth getting right.

* Something that **happened at a moment** is an **event**. Kept forever, never overwritten. *"Upgraded to Pro on Tuesday."*
* Something that **is true right now** and can change is a **trait**. Newest value wins. *"Plan is Pro."*

Send both, in the same call:

```json theme={null}
{
  "event": "subscription_started",
  "identity": { "email": "omar@raqmi.co" },
  "traits": { "plan": "pro", "subscription_status": "active" },
  "tags": ["paying"]
}
```

The first time you send a new trait key, Hamla creates the matching custom field automatically and segments can filter on it immediately. Keys are `snake_case`, and a field keeps the type it was born with.

Rule of thumb: **traits answer "who is this person today", events answer "what have they done".** Most good segments use both.

## Two complete examples

### A clinic

Your booking system already knows when a treatment finishes. Send it:

```json theme={null}
{ "event": "treatment_completed",
  "type": "purchase",
  "identity": { "phone": "+962790000000" },
  "value": 100, "currency": "JOD",
  "properties": { "treatment": "cleaning" },
  "traits": { "last_treatment": "cleaning" } }
```

Your word is `treatment_completed` and it stays that way everywhere. `type: "purchase"` is what makes the 100 JOD count as revenue — a clinic's money is money.

Now, with no further code:

1. **Trigger** — a campaign on `treatment_completed` sends an aftercare message an hour later, with **Can enter again later** on so it works for every visit.
2. **Segment** — *`treatment_completed` last time more than 180 days ago* is everyone due for a check-up.
3. **Finish line** — set `appointment_booked` as the goal, so anyone who rebooks stops getting reminders.

### A SaaS

Your billing webhook already fires. Send it:

```json theme={null}
{ "event": "subscription_started",
  "type": "purchase",
  "identity": { "email": "omar@raqmi.co", "platformCustomerId": "user_812" },
  "value": 29, "currency": "USD",
  "idempotencyKey": "invoice_9911",
  "traits": { "plan": "pro" } }
```

Now:

1. **Trigger** — onboarding starts the instant someone pays.
2. **Segment** — *plan is pro* scopes any campaign to paying customers, and stays correct as people upgrade and churn.
3. **Journey** — `subscription_started` moves them to *Decision* on its own.
4. Send `subscription_expired` on churn with `traits: { "subscription_status": "expired" }`, and your win-back segment fills itself.

## Bring your history with you

You do not have to start from zero. `POST /api/sdk/track/batch` takes up to 500 past events per request — the same body shape, in an `events` array, each with its real `occurredAt`.

One loop over your users table gives day-one meaning to a segment like "bought before, quiet for 60 days".

<Warning>
  **Backfilled history never sends anything.** Imported events fill profiles, counts and segments, but they do not start campaigns and do not fire segment-entry triggers. A year of old purchases must not send a year of messages tonight.

  Only live events — through `/api/sdk/track` or `hamla.track()` — can start a campaign.
</Warning>

Give every row an `idempotencyKey` (your invoice or order id) and the whole script is safe to re-run.

## Common questions

<AccordionGroup>
  <Accordion title="Do I need the script tag as well?">
    They cover different halves. The script tag sees the browser — visits, sources, on-site behavior. Events know your database — plans, payments, milestones. Either works alone. Together, Hamla sees the whole person.
  </Accordion>

  <Accordion title="Can I send events from the browser instead?">
    Yes, for things that genuinely happen in the page: `hamla.track('download_ebook', { asset: 'guide.pdf' })`. These count as deliberate and can start campaigns, and they need no key.

    Anything involving money or your own records should come from your server, where the secret key lives and the facts are real.
  </Accordion>

  <Accordion title="What if I send the same event twice?">
    Same `idempotencyKey` → counted once, and the response says `deduped: true`. No key → each send is a separate event, because sometimes people really do buy twice.
  </Accordion>

  <Accordion title="What names should I use?">
    Whatever your business actually calls them. Hamla never needs to know your vocabulary in advance — a clinic sends `treatment_completed`, a school sends `lesson_finished`.

    The only reason to borrow one of Hamla's names is the journey stage: `purchase`, `trial_started`, `subscription_started`, `subscription_renewed`, `repeat_purchase` and friends move people forward automatically.
  </Accordion>

  <Accordion title="A campaign is not firing. Why?">
    Three usual causes, in order:

    1. **The name does not match.** It is case- and character-exact.
    2. **The event came in through the batch endpoint.** Imports never trigger.
    3. **The person was already enrolled.** Turn on **Can enter again later** for repeating moments.
  </Accordion>
</AccordionGroup>

Field-by-field details, limits and error responses are in the [events reference](/reference/events).
