Skip to Content
StreamsSending Events

Sending Events

Zeotap’s Events API is compatible with the Segment specification, making it easy to migrate from Segment or use existing Segment libraries. This page covers the API format for all event types with full examples.

API Endpoint

Event ingest runs on a dedicated stream host, separate from the control-plane API you use to manage warehouses, models and syncs. It has its own base URL — written as $EVENTS_BASE_URL throughout this page — its own /v1 paths, and its own authentication. Nothing here goes to $API_BASE_URL, and a control-plane API token will not work.

POST $EVENTS_BASE_URL/v1/events

$EVENTS_BASE_URL is shown alongside your event source keys in Events > Event Sources. Each event type also has its own path — /v1/track, /v1/identify, /v1/page, /v1/screen, /v1/group — and /v1/batch accepts many events at once. The unified /v1/events endpoint above takes the event type from the body’s type field, which is what the examples on this page use.

Authentication

Events are authenticated with a write key passed in the X-Write-Key header — not with the Authorization: Bearer token or X-Workspace-ID header the control-plane API expects. The workspace is derived from the key:

curl -X POST "$EVENTS_BASE_URL/v1/events" \ -H "X-Write-Key: YOUR_WRITE_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }'

The write key can also be passed as:

  • the Authorization: Basic base64(key:) header (Segment/analytics.js compatible), or
  • a ?writeKey=YOUR_WRITE_KEY query parameter (required for navigator.sendBeacon which cannot set custom headers).

Write keys are public identifiers — they are embedded in client-side JavaScript and are designed to be visible in outbound requests. Use the event source scoping and consent/rate-limit controls on each key to gate what events it can produce.

Response

StatusMeaning
200 OKEvent accepted
400 Bad RequestInvalid event format (malformed JSON, or a body over the size limit)
401 UnauthorizedInvalid, missing, or revoked write key
500 Internal Server ErrorEvent could not be accepted. Safe to retry with the same messageId.

A 200 means the event was accepted for processing, not that it has been validated against a contract, transformed, written to your warehouse, or forwarded. Those happen downstream. An event that breaches a contract is still accepted here and recorded as a violation rather than rejected at the API, so contract enforcement is never something you detect from the ingest response.

Request bodies are capped at 1 MB for a single event and 10 MB for a batch.

Common Fields

All event types share these fields:

FieldTypeRequiredDescription
typestringYesEvent type: track, identify, page, screen, group
userIdstringConditionalThe known user identifier. Required if anonymousId is not provided.
anonymousIdstringConditionalA client-generated anonymous identifier. Required if userId is not provided.
timestampstring (ISO 8601)NoWhen the event occurred. Defaults to server receipt time.
contextobjectNoAdditional context (IP, user agent, locale, device info, etc.)
integrationsobjectNoPer-destination forwarding overrides (e.g., { "Amplitude": false })
messageIdstringNoClient-generated unique ID for deduplication. Auto-generated if omitted.

Track Events

Track events record user actions — anything a user does that you want to measure.

Format

{ "type": "track", "userId": "user_123", "event": "Order Completed", "properties": { "order_id": "ord_456", "total": 99.99, "currency": "USD", "products": [ { "product_id": "prod_789", "name": "Zeotap T-Shirt", "price": 29.99, "quantity": 2 }, { "product_id": "prod_012", "name": "Zeotap Sticker Pack", "price": 9.99, "quantity": 4 } ], "coupon": "SUMMER20", "payment_method": "credit_card" }, "timestamp": "2025-03-15T14:30:00Z", "context": { "ip": "203.0.113.42", "userAgent": "Mozilla/5.0 ...", "locale": "en-US", "page": { "url": "https://shop.example.com/checkout/confirmation", "referrer": "https://shop.example.com/checkout/payment" } } }

curl Example

curl -X POST "$EVENTS_BASE_URL/v1/events" \ -H "X-Write-Key: sk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "type": "track", "userId": "user_123", "event": "Order Completed", "properties": { "order_id": "ord_456", "total": 99.99, "currency": "USD", "products": [ { "product_id": "prod_789", "name": "Zeotap T-Shirt", "price": 29.99, "quantity": 2 } ] }, "timestamp": "2025-03-15T14:30:00Z" }'

Common Track Events

EventPropertiesDescription
Signed Upplan, sourceUser created an account
Logged InmethodUser signed in
Product Viewedproduct_id, name, category, priceUser viewed a product page
Product Addedproduct_id, name, price, quantity, cart_idUser added item to cart
Cart Viewedcart_id, products[], totalUser viewed their cart
Checkout Startedorder_id, total, products[]User initiated checkout
Order Completedorder_id, total, currency, products[], couponUser completed a purchase
Subscription Createdplan, price, intervalUser started a subscription

Identify Calls

Identify calls associate traits (attributes) with a user. Use them when you learn something new about a user.

Format

{ "type": "identify", "userId": "user_123", "traits": { "email": "jane@example.com", "name": "Jane Smith", "plan": "enterprise", "company": { "id": "comp_456", "name": "Acme Corp", "industry": "Technology", "employee_count": 250 }, "created_at": "2024-01-15T09:00:00Z", "lifetime_value": 4500.00, "newsletter_subscribed": true, "tags": ["vip", "beta-tester"] }, "timestamp": "2025-03-15T14:30:00Z", "context": { "ip": "203.0.113.42", "userAgent": "Mozilla/5.0 ..." } }

curl Example

curl -X POST "$EVENTS_BASE_URL/v1/events" \ -H "X-Write-Key: sk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "type": "identify", "userId": "user_123", "traits": { "email": "jane@example.com", "name": "Jane Smith", "plan": "enterprise", "lifetime_value": 4500.00 } }'

Behavior

  • Traits are merged — new traits are added and existing traits are updated
  • To remove a trait, set it to null
  • Identify calls update the user profile used by audiences and orchestrations
  • If only anonymousId is provided, the traits are associated with the anonymous profile until an identify call with both anonymousId and userId links them

Page Views

Page calls record when a user views a page on your website.

Format

{ "type": "page", "userId": "user_123", "name": "Pricing", "category": "Marketing", "properties": { "url": "https://www.example.com/pricing", "path": "/pricing", "title": "Pricing - Example", "referrer": "https://www.google.com/", "search": "?plan=enterprise", "utm_source": "google", "utm_medium": "cpc", "utm_campaign": "brand-q1" }, "timestamp": "2025-03-15T14:30:00Z" }

curl Example

curl -X POST "$EVENTS_BASE_URL/v1/events" \ -H "X-Write-Key: sk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "type": "page", "userId": "user_123", "name": "Pricing", "properties": { "url": "https://www.example.com/pricing", "path": "/pricing", "title": "Pricing - Example", "referrer": "https://www.google.com/" } }'

Screen Views

Screen calls record when a user views a screen in your mobile app. The format is identical to page calls but uses type: "screen".

Format

{ "type": "screen", "userId": "user_123", "name": "Settings", "category": "Account", "properties": { "variation": "dark_mode" }, "timestamp": "2025-03-15T14:30:00Z", "context": { "device": { "type": "ios", "model": "iPhone 15 Pro", "manufacturer": "Apple" }, "os": { "name": "iOS", "version": "18.2" }, "app": { "name": "MyApp", "version": "3.1.0", "build": "245" } } }

curl Example

curl -X POST "$EVENTS_BASE_URL/v1/events" \ -H "X-Write-Key: sk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "type": "screen", "userId": "user_123", "name": "Settings", "category": "Account", "properties": { "variation": "dark_mode" }, "context": { "device": { "type": "ios", "model": "iPhone 15 Pro" } } }'

Group Calls

Group calls associate a user with a company, organization, or team.

Format

{ "type": "group", "userId": "user_123", "groupId": "comp_456", "traits": { "name": "Acme Corp", "industry": "Technology", "plan": "enterprise", "employee_count": 250, "website": "https://acme.example.com", "created_at": "2023-06-01T00:00:00Z", "mrr": 15000.00 }, "timestamp": "2025-03-15T14:30:00Z" }

curl Example

curl -X POST "$EVENTS_BASE_URL/v1/events" \ -H "X-Write-Key: sk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "type": "group", "userId": "user_123", "groupId": "comp_456", "traits": { "name": "Acme Corp", "industry": "Technology", "plan": "enterprise", "employee_count": 250 } }'

Behavior

  • Group traits are merged (same as identify)
  • A user can belong to multiple groups
  • Group associations are used by audience conditions that reference company/account attributes

Batch Endpoint

For server-side integrations sending many events, use the batch endpoint to send up to 500 events in a single request:

curl -X POST "$EVENTS_BASE_URL/v1/batch" \ -H "X-Write-Key: sk_live_abc123" \ -H "Content-Type: application/json" \ -d '{ "batch": [ { "type": "track", "userId": "user_123", "event": "Product Viewed", "properties": { "product_id": "prod_789" }, "timestamp": "2025-03-15T14:30:00Z" }, { "type": "track", "userId": "user_456", "event": "Order Completed", "properties": { "order_id": "ord_789", "total": 149.99 }, "timestamp": "2025-03-15T14:31:00Z" } ] }'

Batch Response

The batch endpoint returns a summary:

{ "received": 2, "accepted": 2, "rejected": 0, "errors": [] }

If some events are rejected, the errors array contains per-event error details with the event index.

Context Object

The context object provides additional metadata about the event. While optional, it enriches your data for analysis:

FieldTypeDescription
context.ipstringUser’s IP address (used for geo enrichment)
context.userAgentstringBrowser/device user agent string
context.localestringUser’s locale (e.g., en-US)
context.timezonestringUser’s timezone (e.g., America/New_York)
context.page.urlstringCurrent page URL
context.page.pathstringCurrent page path
context.page.referrerstringReferring URL
context.page.titlestringPage title
context.device.typestringDevice type (ios, android, web)
context.device.modelstringDevice model
context.device.manufacturerstringDevice manufacturer
context.os.namestringOperating system name
context.os.versionstringOperating system version
context.app.namestringApplication name
context.app.versionstringApplication version
context.campaign.sourcestringUTM source
context.campaign.mediumstringUTM medium
context.campaign.namestringUTM campaign name
context.consentobjectConsent state — one of the three forms below

Attaching consent state to every event is the recommended way to keep compliance data alongside your analytics. context.consent accepts exactly one of three mutually exclusive forms:

FormFieldsWhen to use
TCF v2 string{ "tcfString": "..." }EU/GDPR — server derives category booleans from TCF purpose consent bits
US Privacy string{ "usPrivacy": "1YNN" }CCPA — server derives advertising/marketing opt-out status
Pre-parsed categories{ "analytics": true, "marketing": false, ... }When your CMP already provides resolved boolean values

Consent values are:

  • Evaluated by forwarding rules — rules can require specific consent categories to be granted before forwarding an event to a destination. When raw strings (TCF/US Privacy) are provided, the server automatically resolves categories using standard IAB purpose mappings.
  • Persisted in the warehouse — both the raw string and derived categories land in dedicated columns (consent_string, us_privacy, consent_categories) on event tables for audit and cohort analysis.

TCF v2 example:

{ "type": "track", "userId": "user_123", "event": "Product Viewed", "properties": { "product_id": "prod_789" }, "context": { "consent": { "tcfString": "CPXxRfAPXxRfAAfKAB..." } } }

Pre-parsed example:

{ "type": "track", "userId": "user_123", "event": "Product Viewed", "properties": { "product_id": "prod_789" }, "context": { "consent": { "analytics": true, "marketing": false, "advertising": false, "functional": true } } }

The Browser SDK manages consent state automatically via setConsent() — call it once when the user interacts with your consent banner and every subsequent event carries the current state.

Deduplication

Events are deduplicated using the messageId field. If you send two events with the same messageId, the second is silently ignored. This is useful for retry logic — if your request times out but the server received it, retrying with the same messageId prevents duplicate events.

If you don’t provide a messageId, Zeotap generates one server-side (no deduplication for those events).

Next Steps

Last updated on