Record contact events

POST/api/v1/contact-events
Requires anorganization API keywith the scopecontact_events:write

Tells Mailyte that something happened to a person in your own system — a checkout started, an order completed, an application submitted. Automations that start on that event, wait for it or leave on it react within seconds. Read Sending contact events before you integrate.

Send up to 500 events in events, or post a single event's fields at the top level. Each event is checked on its own: a bad one is listed in rejected with its position and the reason, and the others are recorded. The request fails as a whole only when its shape is wrong — no events, more than 500, an item that is not an object — or the key lacks contact_events:write.

Four things to know before you send the first one:

  1. An event is not consent. An email we have never seen creates a contact with the status unconfirmed. It can enter automations, but no automated email reaches it until the contact is subscribed. Record consent through the contacts endpoints, not here.
  2. Send unique_id. Repeating an event with the same unique_id is counted under duplicates and changes nothing, which is what makes a retry safe. Without one, a key is derived from the event's name, person, time and properties — which only catches an exact repeat.
  3. Old events never start anything. An event whose occurred_at is more than 7 days before we receive it is stored as historical and never starts, moves or ends a journey. Events are kept for 90 days from occurred_at, so anything older is removed overnight.
  4. The limit is 600 requests a minute per organization, across all its keys. Batch rather than sending one request per event.

Request body

  • eventsarray<object>required

    The events to record. To send one event you may instead put its fields at the top level of the body.

    • eventstringrequired

      What happened, in lowercase snake_case starting with a letter, up to 64 characters: `checkout_started`, `order_completed`. This is the name the automation builder offers.

    • emailstring

      Who it happened to. Matched case-insensitively; an address we have not seen creates an `unconfirmed` contact. Send this or `contact_id`.

    • contact_idstring

      The contact's Mailyte id, instead of `email`. Wins when both are sent. Must belong to your organization.

    • namestring

      The person's name. Used only when this event creates the contact; never changes an existing one.

    • occurred_atstring

      When it happened, ISO 8601 with an offset. Defaults to now. At most five minutes in the future. More than 7 days in the past makes it historical: stored, and never starts or moves a journey.

    • unique_idstring

      Your own id for this occurrence — an order number, a checkout id. Unique per organization: a repeat is reported as a duplicate and ignored.

    • propertiesobject

      Anything about it, up to 16 KB as JSON. Available to the journey as `{{ event.<key> }}` in emails and to its checks, e.g. `event.total`.

Request

POST/api/v1/contact-events
curl -X POST 'https://app.mailyte.com/api/v1/contact-events' \
  -H 'Authorization: Bearer mk_live_YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "events": [
      {
        "event": "checkout_started",
        "email": "ada@example.com",
        "occurred_at": "2026-09-23T10:04:11+01:00",
        "unique_id": "checkout-81234",
        "properties": {
          "cart_value": 45000,
          "currency": "NGN",
          "items": 3
        }
      },
      {
        "event": "order_completed",
        "email": "tunde@example.com",
        "unique_id": "order-50917",
        "properties": {
          "total": 12500,
          "category": "consumable"
        }
      }
    ]
  }'
The key names its own organization, so no X-Organization-ID header is needed.

Response

Accepted. Read `rejected` — a 202 does not mean every event was recorded.

  • dataobject
    • acceptedinteger

      Events recorded.

    • duplicatesinteger

      Events already recorded under the same unique_id; nothing happened twice.

    • rejectedarray<object>
      • indexinteger

        Position in the events array you sent.

      • reasonstring

Returned inside the standard envelope.

Errors

StatusWhen
401The API key is missing, unknown, revoked or expired. All four answer identically, on purpose: distinguishing them would confirm which keys exist.
403The key is valid but may not do this: it lacks the required scope, its IP allowlist does not include you, or this endpoint does not accept API keys.
404No such resource in this organization.
422The request was understood but the values were not acceptable.
429Too many requests, or the organization has spent its sending allowance. `Retry-After` says how long to wait.

Every status, with what causes it and what to do, is on the error reference.