---
title: "Send events to every destination"
method: POST
path: "/v1/events"
tags: ["Conversions"]
---

# Send events to every destination

`POST /v1/events`

## Request

- Base URL: `https://zernio.com/api`
- URL: `https://zernio.com/api/v1/events`
- Auth: HTTP bearer

Sends conversion events once and forwards them to every ad platform destination saved for the profile (see `POST /v1/events/destinations`), or to the `destinations` given in the request. Requires the Ads add-on.

### Forwarding

- Each destination receives the events exactly as `POST /v1/ads/conversions` would send them, so the per-platform notes there (hashing, batching, `eventName` semantics, dedup on `eventId`) apply here too.
- A destination saved with `eventNames` only receives events with those names.
- One destination failing never blocks the others: each one reports its own result.

### Storage

Zernio also stores each event for the profile's reporting.

- Email, phone and `externalId` are stored only as SHA-256 hashes.
- `sourceUrl` keeps only its origin, path and attribution parameters (`utm_*`, `gclid`, `fbclid`, `ttclid`, `li_fat_id`, `epik`, `rdt_cid`, `gbraid`, `wbraid`).
- Events sent with `testCode` are forwarded but not stored.

## Request body

- object
  - `profileId` string, required: Profile the events belong to. Its saved destinations receive the events.
  - `events` ConversionEvent[], required
    - `eventName` string, required: Standard event name (Purchase, Lead, CompleteRegistration, AddToCart, InitiateCheckout, AddPaymentInfo, Subscribe, StartTrial, ViewContent, Search, Contact, SubmitApplication, Schedule) or a custom string. Custom strings work only on platforms that accept custom events: Meta, OpenAI Ads and Reddit. Per-platform behavior: - Meta: free-form; standard names match Meta's built-ins. - Google: ignored. The conversion action's category determines the type. - LinkedIn: ignored. The conversion rule's `type` is locked to the destination. - OpenAI Ads: a fixed subset (Purchase, Lead, AddToCart, ViewContent, InitiateCheckout, CompleteRegistration, Subscribe, StartTrial, Schedule) maps 1:1 onto OpenAI's own event-type enum. Anything else is sent as a custom event with the name preserved. - Reddit: Purchase, Lead, CompleteRegistration (SIGN_UP), AddToCart, AddToWishlist, ViewContent, Search and PageView (PAGE_VISIT) map onto Reddit's tracking types. Anything else is sent as a CUSTOM event with the name preserved.
    - `eventTime` integer, required: When the conversion happened, in unix seconds.
    - `eventId` string, required: Unique dedup key. Use the same eventId on pixel + CAPI to prevent double-counting. Mapped to: - Meta: event_id. - Google: transactionId. - LinkedIn: eventId. LinkedIn deduplicates against Insight Tag events with the same eventId; the Insight Tag event wins when both arrive. - Reddit: `metadata.conversion_id` (Reddit's dedup key against Reddit Pixel events).
    - `value` number: Conversion value in the specified currency.
    - `currency` string: ISO 4217 currency code.
    - `user` object, required: User identity fields. More signals mean higher match rates.
      - `email` string: Plaintext email. Hashed server-side.
      - `phone` string: Phone number, ideally E.164. Hashed server-side.
      - `firstName` string: Plaintext first name. Hashed server-side.
      - `lastName` string: Plaintext last name. Hashed server-side.
      - `externalId` string: Stable customer identifier (e.g. CRM user ID). Hashed server-side for Meta and Google. Sent as plaintext to LinkedIn (LinkedIn's Conversions API spec requires the raw value). Maximum effective list size on LinkedIn is 1.
      - `ipAddress` string: Client IP address. Sent plaintext.
      - `userAgent` string: Client user-agent string. Sent plaintext.
      - `country` string: ISO 3166-1 alpha-2 country code, e.g. 'us'.
      - `city` string: Meta advanced matching (ct). Plaintext city; normalized + SHA-256 hashed server-side. Meta only.
      - `state` string: Meta advanced matching (st). 2-letter ANSI for US; hashed server-side. Meta only.
      - `zip` string: Meta advanced matching (zp). US uses first 5 digits; hashed server-side. Meta only.
      - `dob` string: Meta advanced matching (db). YYYYMMDD; hashed server-side. Meta only.
      - `gender` string: Meta advanced matching (ge). 'f' or 'm'; hashed server-side. Meta only.
      - `leadId` string: Meta lead ID from a Lead Ad submission, as a string. Required for Conversion Leads CRM events: send it with `actionSource: 'crm'` and `platformData: { event_source: 'crm', lead_event_source: '<CRM name>' }`. Forwarded unhashed to Meta's `user_data.lead_id`. Meta only.
      - `clickIds` object: Platform click identifiers captured from the originating ad click.
        - `fbc` string: Meta click ID (from fbclid URL param).
        - `fbp` string: Meta browser ID (_fbp cookie).
        - `gclid` string: Google click ID (from gclid URL param).
        - `gbraid` string: Google iOS 14.5+ app attribution ID.
        - `wbraid` string: Google iOS 14.5+ web-to-app attribution ID.
        - `li_fat_id` string: LinkedIn first-party ad tracking click ID. Captured by parsing `li_fat_id` from landing-page URLs after the advertiser enables enhanced conversion tracking on the LinkedIn Insight Tag. Sent to LinkedIn as the LINKEDIN_FIRST_PARTY_ADS_TRACKING_UUID userId. Opaque token, not hashed.
        - `epik` string: Pinterest click ID (the `epik` URL param or `_epik` cookie). Sent to Pinterest as `click_id`, not hashed.
        - `rdt_cid` string: Reddit click ID (the `rdt_cid` URL param Reddit adds to ad clicks). Sent to Reddit as `click_id`, not hashed.
        - `rdt_uuid` string: Reddit Pixel browser id (the `_rdt_uuid` first-party cookie). Sent to Reddit as `user.uuid`, not hashed.
    - `items` object[]: Item-level detail for ecommerce events.
      - `id` string
      - `name` string
      - `price` number
      - `quantity` integer
      - `category` string
    - `sourceUrl` string, uri: URL where the conversion originated (used by Meta).
    - `actionSource` 'web' | 'app' | 'offline' | 'crm' | 'phone_call' | 'system_generated' | 'business_messaging': Where the conversion happened. Used by Meta. Google also requires an event source internally; omitting this field sends OTHER to Google. Send an explicit value for accurate origin reporting. `business_messaging` (Meta) marks an event from a WhatsApp, Messenger or Instagram Direct conversation and needs `messaging`.
    - `messaging` object: Meta only, read when `actionSource` is `business_messaging` (Conversions API for Business Messaging). - `channel` is sent as the event's `messaging_channel` (`instagram_direct` goes out as `instagram`). - Each id lands in Meta's `user_data` unhashed. An id that does not belong to `channel` is ignored. Meta accepts only its business messaging event names here: `LeadSubmitted`, `QualifiedLead`, `Purchase`, `AddToCart`, `InitiateCheckout`, `ViewContent`, `OrderCreated`, `OrderShipped`, `OrderDelivered`, `OrderCanceled`, `OrderReturned`, `CartAbandoned`, `RatingProvided`, `ReviewProvided`. `eventName` is forwarded unchanged.
      - `channel` 'whatsapp' | 'messenger' | 'instagram_direct', required
      - `wabaId` string: WhatsApp only. WhatsApp Business Account id, sent as `whatsapp_business_account_id`.
      - `ctwaClid` string: WhatsApp only. Click-to-WhatsApp click id from the first inbound message referral, sent as `ctwa_clid`.
      - `pageId` string: Messenger: Facebook Page id, sent as `page_id`. Instagram Direct: when `instagramBusinessAccountId` is absent, Zernio reads the Instagram professional account linked to this Page and sends its id as `ig_account_id`; if none is linked or the lookup fails, `page_id` is sent instead.
      - `instagramBusinessAccountId` string: Instagram Direct only. Instagram professional account id (`instagram_business_account` on the Page), sent as `ig_account_id` next to `ig_sid`.
      - `pageScopedUserId` string: Messenger only. The page-scoped user id (PSID) your Messenger webhook gives for the person, sent as `page_scoped_user_id` next to `page_id`.
      - `instagramScopedUserId` string: Instagram Direct only. The Instagram-scoped user id (IGSID) your Instagram messaging webhook gives for the person, sent as `ig_sid`.
    - `platformData` object: Escape hatch for platform-specific fields we haven't normalized. On Meta, keys are shallow-merged into `custom_data` only: fields Zernio already builds (`value`, `currency`, `contents`, `num_items`) always win on collision, and `user_data` (hashed match keys) is never touched. Use first-class fields (e.g. `user.leadId`) for anything that must reach `user_data`.
  - `destinations` object[]: Send to these destinations instead of the saved ones (every event goes to each).
    - `accountId` string, required: Connected ads account ID (metaads, googleads, linkedinads, tiktokads, openaiads, pinterestads or redditads).
    - `destinationId` string, required: Platform destination, as listed by GET /v1/accounts/{accountId}/conversion-destinations.
  - `consent` object: Batch-level user consent, forwarded to every destination (see POST /v1/ads/conversions).
    - `adUserData` 'GRANTED' | 'DENIED'
    - `adPersonalization` 'GRANTED' | 'DENIED'
  - `testCode` string: Forwarded as each platform test code (see POST /v1/ads/conversions). A root-level `test_event_code` is accepted as an alias. Test events are not stored.

## Response `200`

Events processed. Check each destination for partial failure.

- object
  - `eventsReceived` integer: Events in the request.
  - `stored` boolean: Whether the events were stored for reporting (always false with testCode).
  - `testCode` string, nullable: The test code forwarded to every destination, or `null` when the request carried none.
  - `destinations` object[]
    - `accountId` string
    - `destinationId` string
    - `platform` 'metaads' | 'googleads' | 'linkedinads' | 'tiktokads' | 'openaiads' | 'pinterestads' | 'redditads'
    - `eventsSent` integer: Events the platform accepted.
    - `eventsFailed` integer
    - `failures` object[]
      - `eventIndex` integer: Index into the events sent to this destination.
      - `eventId` string
      - `message` string
      - `code` union
        - string
        - integer
    - `error` string: Set when the destination could not be reached at all (account disconnected, unsupported platform, platform error).

## Other responses

- `400`: Invalid body, including a `test_event_code` that is not a non-empty string, or `testCode` and `test_event_code` with different values (`param: test_event_code`).
- `401`: Missing or invalid API key. `code` is `missing_credentials` when no Authorization header was sent and `invalid_credentials` when the key is unknown, revoked or expired.
- `404`: Resource not found

## Changes

- **2026-10-27** `45d71f903fcc`: 1 info
  - added the optional property `testCode` to the response with the `200` status
- **2026-10-26** `2ccee257b7c2`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/zernio/apis/zernio-api/changes/v1/events/post.md)

---

[API](https://skmtc.dev/zernio/apis/zernio-api.md) - [All operations](https://skmtc.dev/zernio/apis/zernio-api/llms.txt) - [OpenAPI document](https://skmtc.dev/zernio/apis/zernio-api/revisions/c319eb5d95b6?raw)
