# Send events

The shape of an event for POST /v1/events: its name, id, time and value, the customer identifiers and click ids that match it, and how to test and batch.

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';

Each event is one object in the `events` array of `POST /v1/events`. A request carries up to 1,000 events for one profile.

## First event

Four fields are required: `eventName`, `eventTime`, `eventId` and `user`. This is the smallest event that is stored and forwarded.

<Tabs items={['Node.js', 'Python', 'curl']}>
<Tab value="Node.js">
```typescript
import Zernio from '@zernio/node';

const zernio = new Zernio();

await zernio.conversions.sendEvents({
  body: {
    profileId: '66a1f0c2a4b9d3e8f1a2b3c4',
    events: [{ eventName: 'Lead', eventTime: 1798848000, eventId: 'lead_881', user: { email: 'ana@example.com' } }]
  }
});
```
</Tab>
<Tab value="Python">
```python
from zernio import Zernio

client = Zernio()

client.conversions.send_events(
    profile_id="66a1f0c2a4b9d3e8f1a2b3c4",
    events=[{"eventName": "Lead", "eventTime": 1798848000, "eventId": "lead_881", "user": {"email": "ana@example.com"}}],
)
```
</Tab>
<Tab value="curl">
```bash
curl -X POST "https://zernio.com/api/v1/events" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profileId": "66a1f0c2a4b9d3e8f1a2b3c4",
    "events": [{ "eventName": "Lead", "eventTime": 1798848000, "eventId": "lead_881", "user": { "email": "ana@example.com" } }]
  }'
```
</Tab>
</Tabs>

## The event

| Field | Required | What it is |
|---|---|---|
| `eventName` | Yes | What happened: `Purchase`, `Lead` or one of the names below |
| `eventTime` | Yes | When it happened, in unix seconds |
| `eventId` | Yes | Your own id for this event. Resending it replaces the stored event |
| `user` | Yes | Who did it: identifiers and click ids, see below |
| `value`, `currency` | No | The amount and its ISO 4217 code, such as `USD` |
| `sourceUrl` | No | The page where it happened, with its UTM tags and click ids |
| `campaign` | No | The ad campaign behind it, as `id` or `name` |
| `actionSource` | No | Where it happened: `web`, `app`, `offline`, `crm`, `phone_call`, `system_generated` or `business_messaging` |
| `items` | No | The products in an order: `id`, `name`, `price`, `quantity`, `category` |
| `platformData` | No | Extra fields for one platform that Zernio does not map yet |

## Event names

Use a standard name when one fits: `Purchase`, `Lead`, `QualifiedLead`, `CompleteRegistration`, `AddToCart`, `InitiateCheckout`, `AddPaymentInfo`, `Subscribe`, `StartTrial`, `ViewContent`, `Search`, `Contact`, `SubmitApplication`, `Schedule`.

Any other string works too. Meta and TikTok keep names as sent, Pinterest, Reddit and OpenAI map the standard ones, and Google and LinkedIn take the type from the destination. See [Platforms](/products/conversions-api/platforms).

`Purchase`, `Lead` and `QualifiedLead` also count in campaign results, as sales and revenue, leads and qualified leads.

## Identify the customer

The more identifiers an event carries, the more often the platform can tie it to a person who saw the ad.

| Field | Sent as |
|---|---|
| `email`, `phone` | The main match keys, read by almost every platform |
| `firstName`, `lastName` | Names, read by Meta, LinkedIn, Pinterest and Google Ads |
| `externalId` | Your own customer id |
| `ipAddress`, `userAgent`, `country` | The device and country |
| `city`, `state`, `zip`, `dob`, `gender` | More match keys for Meta and Pinterest. Google Ads uses `zip` with the name and country |
| `leadId` | The Meta lead id, for CRM events about a Meta lead |

Send plain values. Zernio hashes them before they leave and stores only the hashes. Which platform reads which field is on [Platforms](/products/conversions-api/platforms#customer-identifiers).

## Click ids

A click id names the exact ad click. It is the strongest match a platform has. Read it from the landing page URL and keep it until the conversion.

| Click id | From | Used by |
|---|---|---|
| `fbc`, `fbp` | `fbclid` in the URL, the `_fbp` cookie | <PlatformName slug="meta-ads" page="conversions" /> |
| `gclid`, `gbraid`, `wbraid` | The URL Google sends the click to | <PlatformName slug="google-ads" page="conversions" /> |
| `li_fat_id` | The URL, with LinkedIn enhanced conversions on | <PlatformName slug="linkedin-ads" page="conversions" /> |
| `epik` | The URL or the `_epik` cookie | <PlatformName slug="pinterest-ads" page="conversions" /> |
| `rdt_cid`, `rdt_uuid` | The URL, the `_rdt_uuid` cookie | <PlatformName slug="reddit-ads" page="conversions" /> |

Put them in `user.clickIds`. A `gclid` also lets Zernio find the event's campaign.

## Tie it to a campaign

Zernio counts an event for the campaign behind it. Give it at least one way to find that campaign:

- Keep `sourceUrl` with its `utm_campaign` tag and click ids.
- Send `campaign.id`, the platform's campaign id, when you know it.
- Send `campaign.name` when you only know the name.

The order Zernio tries them in is on [How an event finds its campaign](/products/conversions-api#how-an-event-finds-its-campaign).

## Test before going live

Add `testCode` to the request. Meta, Pinterest and Reddit receive the events in their test mode, such as Meta's Test Events tool.

Google Ads, LinkedIn, TikTok and OpenAI have no test mode: a test event reaches the live account there. Test events are never stored by Zernio.

## How it behaves

- **Dedup.** `eventId` is unique per profile. The same id again replaces the stored event. Platforms use it too, to drop a copy their pixel already sent.
- **Retrying a request.** Send an `Idempotency-Key` header. A retried request with the same key returns the first answer instead of sending twice.
- **Sync or async.** By default the response waits for every destination. `mode: "async"` stores and queues the sends, then answers 202.
- **Time.** `eventTime` is seconds. A value in milliseconds is read as a date thousands of years ahead.

## If it fails

| Error | Cause | Fix |
|---|---|---|
| 400 on `events` | Empty, or more than 1,000 events | Send 1 to 1,000 per request |
| 400 on a field | A required field is missing or has the wrong type | Read `param` in the error |
| 403 `ads_addon_required` | The account has no Ads add-on | Add the Ads add-on |
| `failures` on a destination | The platform refused some events | Read each failure's `message` and fix that event |

## Related

- [Conversions API overview](/products/conversions-api)
- [Platforms](/products/conversions-api/platforms)
- [Send events reference](/conversions/send-events)
- [Idempotency](/guides/idempotency)