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

# Ingest an event

> Send a custom event to Privy. Use events to trigger **Flows** and other
automations from activity in your own systems (a completed purchase, a
finished quiz, a loyalty milestone, etc.).

Identify the contact with **exactly one** of `email`, `phone`, or `privy_id`.
If the contact doesn't exist yet, Privy creates one (by email or phone);
`privy_id` must reference an existing contact.

## Response status

Unlike most endpoints, a successful ingest returns **`200 OK`** with a
`status` field rather than `201`. A payload that fails validation returns
**`400 Bad Request`** with a top-level `errors` array — a different shape
from the standard `error` envelope used for auth and rate-limit errors.

## Idempotency

Set the `Idempotency-Key` header (or an `idempotency_key` body field; the
header wins if both are present) to safely retry. The first request ingests
the event; a retry with the same key returns the original response with
`idempotent_replay: true` instead of ingesting a duplicate. Only accepted
events are replayed — a rejected payload re-validates on retry.

## Limits

The request body must not exceed **256 KB**; larger bodies return `413`.

**Required scope:** `events_write`


<Warning>
  The custom events API is in **closed beta**. If you're interested in trying it,
  reach out to [Support](mailto:support@privy.com).
</Warning>

<Note>
  Accepted events can trigger **Flows** and other automations, keyed on the
  `event_type` you send. A successful `200` response means the event was accepted
  for processing — it does not guarantee a specific automation ran.
</Note>


## OpenAPI

````yaml openapi/privy-api.yaml POST /events
openapi: 3.1.0
info:
  title: Privy API
  version: '1.0'
  description: |
    The Privy API lets you programmatically manage your contact list.
    Create, update, unsubscribe, and remove contacts — or retrieve your
    full contact list with filtering and pagination.
servers:
  - url: https://api.privy.com/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Account
    description: Retrieve information about the authenticated Privy account.
  - name: Contacts
    description: Manage your contact list.
  - name: Orders
    description: Report orders placed in your store.
  - name: Events
    description: Ingest custom events to trigger Flows and other automations.
paths:
  /events:
    post:
      tags:
        - Events
      summary: Ingest an event
      description: >
        Send a custom event to Privy. Use events to trigger **Flows** and other

        automations from activity in your own systems (a completed purchase, a

        finished quiz, a loyalty milestone, etc.).


        Identify the contact with **exactly one** of `email`, `phone`, or
        `privy_id`.

        If the contact doesn't exist yet, Privy creates one (by email or phone);

        `privy_id` must reference an existing contact.


        ## Response status


        Unlike most endpoints, a successful ingest returns **`200 OK`** with a

        `status` field rather than `201`. A payload that fails validation
        returns

        **`400 Bad Request`** with a top-level `errors` array — a different
        shape

        from the standard `error` envelope used for auth and rate-limit errors.


        ## Idempotency


        Set the `Idempotency-Key` header (or an `idempotency_key` body field;
        the

        header wins if both are present) to safely retry. The first request
        ingests

        the event; a retry with the same key returns the original response with

        `idempotent_replay: true` instead of ingesting a duplicate. Only
        accepted

        events are replayed — a rejected payload re-validates on retry.


        ## Limits


        The request body must not exceed **256 KB**; larger bodies return `413`.


        **Required scope:** `events_write`
      operationId: ingestEvent
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: >
            Optional key to safely retry without creating a duplicate event. Use
            a

            unique value (such as a UUID) per distinct event. Takes precedence
            over

            an `idempotency_key` body field.
          schema:
            type: string
          example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IngestEventRequest'
            example:
              email: shopper@example.com
              event_type: purchase_completed
              event_time: '2026-06-15T10:30:00Z'
              event_data:
                order_id: ORD-12345
                total: 99.99
                currency: USD
      responses:
        '200':
          description: >
            The event was accepted, or replayed from a prior request with the
            same

            idempotency key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventAccepted'
              examples:
                accepted:
                  summary: Event accepted
                  value:
                    event_id: 507f1f77bcf86cd799439011
                    status: accepted
                idempotent_replay:
                  summary: Idempotent replay
                  value:
                    event_id: 507f1f77bcf86cd799439011
                    status: accepted
                    idempotent_replay: true
        '400':
          description: >
            The payload failed validation and was not ingested. Problems are
            listed

            in the top-level `errors` array (not the standard `error` envelope).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventRejected'
              example:
                event_id: 507f1f77bcf86cd799439012
                status: rejected
                errors:
                  - field: identifier
                    message: email, phone, or privy_id is required
                  - field: event_type
                    message: is required
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '429':
          $ref: '#/components/responses/RateLimited'
      security:
        - bearerAuth: []
components:
  schemas:
    IngestEventRequest:
      type: object
      description: >
        An event to ingest. You must identify the contact with **exactly one**
        of

        `email`, `phone`, or `privy_id`, and provide an `event_type`.


        Everything else is optional. Unknown top-level keys are ignored — put
        your

        own data under `event_data`.
      required:
        - event_type
      properties:
        email:
          type: string
          format: email
          description: >
            The contact's email address. Provide exactly one of `email`,
            `phone`,

            or `privy_id` to identify the contact.
          example: shopper@example.com
        phone:
          type: string
          description: >
            The contact's phone number in E.164 format (`+` followed by country

            code and number). Provide exactly one of `email`, `phone`, or
            `privy_id`.
          example: '+15551234567'
        privy_id:
          type: string
          description: >
            The contact's Privy customer ID (the `id` returned by the Contacts
            API,

            prefixed with `cus_`). Provide exactly one of `email`, `phone`, or
            `privy_id`.
          example: cus_a1b2c3d4e5f6g7h8
        event_type:
          type: string
          maxLength: 128
          pattern: ^[A-Za-z0-9_-]+$
          description: >
            A name for the event. Up to 128 characters; letters, numbers,
            hyphens,

            and underscores only. Choose a stable, descriptive name — you'll use
            it

            to trigger Flows (e.g. `purchase_completed`, `quiz_finished`).
          example: purchase_completed
        event_time:
          type: string
          format: date-time
          description: >
            ISO 8601 date-time the event occurred. Must be UTC — end the
            timestamp

            with `Z` (or a zero offset). Optional; defaults to the time Privy

            received the event.
          example: '2026-06-15T10:30:00Z'
        event_data:
          type: object
          additionalProperties: true
          description: >
            Arbitrary key-value data describing the event. Values may be
            strings,

            numbers, booleans, `null`, objects, or arrays. Objects and arrays
            may be

            nested up to 10 levels deep. Stored and made available to downstream

            automations verbatim.
          example:
            order_id: ORD-12345
            total: 99.99
            currency: USD
            items:
              - sku: SKU-1
                quantity: 2
        idempotency_key:
          type: string
          description: >
            Optional key to safely retry a request without creating a duplicate

            event. Prefer the `Idempotency-Key` header; if both are sent, the
            header

            wins. See the endpoint description for replay semantics.
          example: 550e8400-e29b-41d4-a716-446655440000
    EventAccepted:
      type: object
      description: The event was accepted (or replayed from a prior identical request).
      properties:
        event_id:
          type: string
          description: Privy's identifier for the stored event.
          example: 507f1f77bcf86cd799439011
        status:
          type: string
          enum:
            - accepted
          example: accepted
        idempotent_replay:
          type: boolean
          description: >
            Present and `true` only when this response was served from a prior

            request with the same idempotency key, rather than ingesting a new
            event.
          example: true
    EventRejected:
      type: object
      description: >
        The event failed shape validation. It is **not** ingested for processing

        (though Privy retains a copy for debugging). Note that this response
        does

        **not** use the standard `error` envelope — validation problems are

        returned in the top-level `errors` array.
      properties:
        event_id:
          type: string
          description: Privy's identifier for the stored (rejected) event.
          example: 507f1f77bcf86cd799439012
        status:
          type: string
          enum:
            - rejected
          example: rejected
        errors:
          type: array
          description: One entry per validation problem.
          items:
            type: object
            properties:
              field:
                type: string
                example: event_type
              message:
                type: string
                example: is required
    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              enum:
                - invalid_client
                - invalid_scope
                - unauthorized
                - insufficient_scope
                - not_found
                - conflict
                - validation_failed
                - rate_limited
              example: validation_failed
            message:
              type: string
              example: One or more fields are invalid
            details:
              type: array
              items:
                type: object
                properties:
                  field:
                    type: string
                    example: email
                  message:
                    type: string
                    example: is required
  responses:
    Unauthorized:
      description: Missing or invalid bearer token.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: unauthorized
              message: Bearer token is missing or invalid
    InsufficientScope:
      description: Token lacks the required scope for this endpoint.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: insufficient_scope
              message: Token does not have the required scope
    PayloadTooLarge:
      description: The request body exceeds the maximum allowed size.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: payload_too_large
              message: Request payload must not exceed 256KB.
    RateLimited:
      description: Rate limit exceeded. Retry after the specified time.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
          example: 42
        X-RateLimit-Limit-Minute:
          description: Maximum requests allowed per minute.
          schema:
            type: integer
          example: 60
        X-RateLimit-Remaining-Minute:
          description: Requests remaining in the current minute window.
          schema:
            type: integer
          example: 0
        X-RateLimit-Reset-Minute:
          description: Unix timestamp when the minute window resets.
          schema:
            type: integer
          example: 1711929600
        X-RateLimit-Limit-Day:
          description: Maximum requests allowed per day.
          schema:
            type: integer
          example: 10000
        X-RateLimit-Remaining-Day:
          description: Requests remaining in the current day window.
          schema:
            type: integer
          example: 9500
        X-RateLimit-Reset-Day:
          description: Unix timestamp when the day window resets.
          schema:
            type: integer
          example: 1712016000
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: rate_limited
              message: Rate limit exceeded
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API token or OAuth access token
      description: >
        Send either an API token or an OAuth access token as

        `Authorization: Bearer <token>`. See the Authentication page for
        details.

````