# Send events and contacts from your app

Source: https://help.meisa.io/articles/send-events-from-your-app

Your app keeps Meisa up to date with two calls: `POST /contacts/upsert/` whenever a user signs up or their details change, and `POST /events/track/` when they do something that matters, like starting a trial. Automations in Meisa then decide what to send.

Both calls need an API key. Upsert needs **Add and update contacts** (`contacts:write`) and tracking needs **Track events** (`events:track`). See [Create and manage API keys](/articles/create-an-api-key).

## Step 1: Create or update the contact (upsert)

Call upsert on signup and whenever something you segment on changes, like the plan.

```bash
curl -X POST "https://api.meisa.io/api/v1/contacts/upsert/" \
  -H "X-API-Key: meisa_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "maya@example.com",
    "external_id": "user_8412",
    "first_name": "Maya",
    "last_name": "Chen",
    "source": "signup",
    "custom_fields": { "plan": "trial", "company": "Acme" },
    "tag_names": ["trial-user"]
  }'
```

How it works:

- Meisa looks for the contact by `external_id` first, then by `email`. If neither matches, it creates a new contact.
- `email` is required. Send `external_id` (your own user ID) too, so a user who changes their email stays the same contact.
- `custom_fields` are merged: fields you send are updated, fields you leave out are kept.
- `tag_names` only adds tags. To remove a tag, use `POST /contacts/{id}/remove_tag/` with `tag_name`.
- `source` tells automations where the contact came from: `api`, `signup`, `import`, `manual` or `initial_sync`. Use `initial_sync` for a one-off backfill so **Contact is created** automations can ignore it.

Upsert can start automations: **Contact is created** for new contacts, **Field changes** when a custom field changes, and **Tag is added** for new tags.

## Step 2: Track events

Send an event when the user does something you want to react to.

```bash
curl -X POST "https://api.meisa.io/api/v1/events/track/" \
  -H "X-API-Key: meisa_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "event_name": "trial_started",
    "external_id": "user_8412",
    "properties": { "plan": "pro" }
  }'
```

- `event_name` is required. Identify the person with `email` or `external_id`.
- The contact must already exist, so upsert first. Tracking an event for an unknown person returns `404`.
- A successful call returns `"status": "tracked"` with the `contact_id`.

Use short, stable names in snake_case, like `trial_started`, `project_created` or `hit_usage_limit`. The automation trigger **Your app sends an event** must use exactly the same name.

## Step 3: Let automations do the rest

In Meisa, create automations that react to your data:

- **Your app sends an event** `trial_started`, then **Start a sequence** "Trial onboarding".
- **Field changes** on `plan` changed to `pro`, then **Stop a sequence** "Trial onboarding" and **Start a sequence** "Welcome to Pro".

See [Create an automation rule](/articles/create-an-automation-rule).

## Good practice

- Make the calls from your backend, not the browser, so your API key stays secret.
- Don't block your signup flow on Meisa. Send the calls in a background job and retry on `429` or `5xx` errors with backoff.
- Keep field names consistent. A field called `plan` in one call and `Plan` in another becomes two fields.

## Troubleshooting

- **404 on events/track**: the contact doesn't exist yet, or the `external_id` doesn't match. Call upsert first.
- **403**: the key is missing the permission for that call.
- **The automation didn't fire**: check the event name matches exactly, the automation is on, and for new contacts that the source is ticked under **Which contacts count**.

## Related articles

- [Create an automation rule](/articles/create-an-automation-rule)
- [Use custom fields and tags](/articles/custom-fields-and-tags)
- [Sync new signups from your app to Meisa](/articles/sync-signups-from-your-app)
- [API quickstart: your first API call](/articles/api-quickstart)
