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

# Events

> Say what happened, not which template to send.

Sending a template couples your application to messaging: the template name, its language, the order of its placeholders. Change the copy and you deploy code.

Events invert that. Your application says what happened:

```bash theme={null}
curl https://lazyapp.in/v1/events \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-shipped" \
  -d '{
    "type": "order.shipped",
    "recipient": { "phone_number": "+919812345670" },
    "properties": {
      "order_id": "1042",
      "eta": "Thursday",
      "tracking_url": "https://acme.example/track/1042"
    }
  }'
```

LazyApp resolves the event to a template, maps the properties onto its placeholders, and sends. The mapping lives in your workspace — set it in the console or provision it over the API — so marketing can change the wording without touching your code.

## Listing mappings

```bash theme={null}
curl https://lazyapp.in/v1/event-mappings \
  -H "Authorization: Bearer $LAZYAPP_API_KEY"
```

**Required scope:** `templates:read`.

## Provisioning mappings

Platforms can create or update mappings without the console:

```bash theme={null}
curl -X PUT https://lazyapp.in/v1/event-mappings/provision \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: acme-real-estate-mappings" \
  -d '{ "pack": "real_estate" }'
```

Or pass an explicit list (not both `pack` and `mappings`):

```json theme={null}
{
  "mappings": [
    {
      "event_type": "order.shipped",
      "template_name": "order_shipped",
      "language": "en",
      "body_variables": ["order_id", "eta"],
      "pricing_category": "utility"
    }
  ]
}
```

**Required scope:** `templates:write`.

To also seed workflows and Flows for a vertical, use [automation packs](https://docs.lazyapp.in/sending/automation-packs.md).

## Addressing

By phone number:

```json theme={null}
{ "recipient": { "phone_number": "+919812345670" } }
```

or with `type` and `value`, the same shape as a contact:

```json theme={null}
{ "recipient": { "type": "phone_number", "value": "+919812345670" } }
```

Or by contact, when you already track people in LazyApp:

```json theme={null}
{ "recipient": { "type": "contact_id", "value": "9f8b1c2d-4e5f-4a6b-8c7d-0e1f2a3b4c5d" } }
```

## Record without sending

Not every business event should message the customer. A lead being qualified, a visit booked by an agent or a ticket created are worth keeping, and worth starting a workflow from, without a WhatsApp message. Send `"deliver": false`:

```json theme={null}
{
  "type": "enquiry.received",
  "recipient": { "phone_number": "+919812345670" },
  "properties": { "stage": "qualified", "score": 72 },
  "deliver": false,
  "idempotency_key": "lead-42-qualified"
}
```

```json theme={null}
{ "uuid": null, "event_uuid": "7d1c…", "status": "recorded", "type": "enquiry.received" }
```

The answer is `202`. The event appears in `GET /v1/events` with status `recorded` and starts `api_event` workflows. It needs no template mapping: a type with no mapping is refused only when you ask for it to be delivered.

## Event types

`type` accepts the built-in library plus any event you define in your workspace. Defining your own also lets you declare a property schema, so a malformed event is rejected at the API rather than producing a message with an empty placeholder.

Define a type over the API with `PUT /v1/event-definitions/{type}`. Namespace it (`real_estate.visit_booked`, `clinic.appointment_booked`) so two products sharing a workspace never fight over one type:

```bash theme={null}
curl -X PUT https://lazyapp.in/v1/event-definitions/real_estate.visit_booked \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Visit booked",
    "schema": {
      "required": ["property_id"],
      "properties": {
        "property_id": { "type": "string" },
        "slot": { "enum": ["morning", "evening"] }
      }
    }
  }'
```

The call is idempotent, so run it from your setup script on every deploy. `GET /v1/event-definitions` lists your types, and `DELETE /v1/event-definitions/{type}` archives one: new events of that type are refused, and past ones stay in the history.

## Idempotency

Events are the natural place for permanent deduplication. The body-level `idempotency_key` is enforced by a unique constraint, so one business event produces one message — forever, not just for 24 hours:

```json theme={null}
{ "idempotency_key": "order-1042-shipped" }
```

That matters when your own job runner might replay a task weeks later. See [idempotency](https://docs.lazyapp.in/concepts/idempotency.md) for how this differs from the header.

## Replaying

If an event failed for a transient reason, replay it:

```bash theme={null}
curl -X POST https://lazyapp.in/v1/events/{uuid}/replay \
  -H "Authorization: Bearer $LAZYAPP_API_KEY"
```

The replay runs under a fresh idempotency key, so the original deduplication does not block it.

## Ingesting from another system

For a CRM or storefront that cannot be taught LazyApp's event shape, there is a generic ingress:

```bash theme={null}
curl https://lazyapp.in/v1/integrations/shopify/events \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"topic": "fulfillment/create", "order": {"id": 1042}}'
```

The body is handed to your workflows as the trigger payload, filtered by source, so an automation can be authored against whatever that system already sends.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.