Every integration sends data to UMAP360 through a single endpoint: POST /v1-batch. You send a JSON batch of events, authenticated with a write key, and the platform resolves identity, computes attribution, and evaluates engagement rules for you.

The endpoint

POST https://YOUR_PROJECT_REF.supabase.co/functions/v1/v1-batch
Authorization: Bearer uk_live_YOUR_WRITE_KEY
Content-Type: application/json

The request body is a single object with a batch array of events:

{ "batch": [ /* 1–100 event objects */ ] }

Content-Type must be application/json — any other value is rejected with 400. Ingestion requires a write key (uk_live_); an admin key also works, but a read key returns 403.

A complete request

curl -X POST https://YOUR_PROJECT_REF.supabase.co/functions/v1/v1-batch \
  -H "Authorization: Bearer uk_live_YOUR_WRITE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "batch": [
      {
        "type": "track",
        "event": "Purchase Completed",
        "messageId": "msg_abc123",
        "timestamp": "2026-02-28T10:00:00.000Z",
        "anonymousId": "anon_xyz789",
        "userId": "user_456",
        "properties": {
          "order_id": "ORD-123",
          "total": 4999,
          "currency": "INR"
        },
        "context": {
          "page": { "url": "https://example.com/checkout", "path": "/checkout" },
          "campaign": { "source": "google", "medium": "cpc", "gclid": "abc123" }
        }
      }
    ]
  }'

Event schema

Every event is a JSON object. Four fields are required on every event, regardless of type — if any is missing, that event is rejected (the rest of the batch still processes):

FieldTypeNotes
typestringOne of track, identify, page, screen, alias, group.
messageIdstringUnique per event. Used for de-duplication on the server.
anonymousIdstringStable per-device/browser anonymous identifier.
timestampstringISO 8601, e.g. 2026-02-28T10:00:00.000Z.

The remaining fields are conditional or optional:

FieldTypeNotes
eventstringRequired for track. The event name, e.g. Purchase Completed.
userIdstringRequired for alias. A known user identifier; send it once you've identified the visitor.
groupIdstringRequired for group. The group/account identifier.
previousIdstringConventional on alias — the prior identity to link from.
propertiesobjectEvent properties (for track).
traitsobjectUser traits such as email, phone, name (for identify).
contextobjectDevice, page, and campaign context — see The context object.

What the server actually validates

The server enforces the four required fields above plus the per-type rule shown in the table below (track needs event, alias needs userId, group needs groupId). It does not reject an identify, page, or screen event for missing optional fields — but an identify with no userId and no traits, or a screen with no name in properties, carries no useful signal. Send the recommended fields even though they aren't strictly enforced.

It also screens identifiers: anonymousId (always) and userId / previousId (when present) are rejected if they exceed 200 characters or match a denylist of junk/sentinel values (null, undefined, anonymous, guest, the all-zero UUID, [object Object], …, matched case-insensitively after Unicode normalization) — the per-event reasons are Invalid anonymousId / Invalid userId / Invalid previousId. A track event whose event name exceeds 200 characters is rejected with event name too long. Each bad event is dropped on its own; the rest of the batch still ingests. This guards the identity graph from poisoning — the SDK mints anon_<uuid> ids that always pass.

Event types

TypePurposeAdditionally required
trackA custom behavioural event.event
identifyAttach a userId and traits to the current visitor.— (send userId and/or traits)
pageA page view (web).
screenA screen view (app).
aliasLink a previous identity to a userId.userId
groupAssociate the visitor with a group/account.groupId

How event names are stored

track events are stored under the name you send in event. The other types are stored under a system name, which is what you'll see in the dashboard and the Profiles API:

Sent typeStored event_name
page$pageview
screen$screen
identify$identify
alias$alias
group$group

alias now merges profiles; group does not yet

alias is acted on server-side: an alias event whose previousId is distinct from both userId and anonymousId folds the prior identity's profile into the userId profile (a real identity merge). A self-alias — or one where previousId equals the current anonymousId — is handled by ordinary identity resolution rather than a merge. Because previousId now drives a merge, an illegal/junk previousId is rejected per-event (Invalid previousId).

group is not yet acted ongroup events are accepted, validated, and stored as $group, but groupId is not used to associate profiles today.

For the common anonymous→known case, identify (the same anonymousId plus a userId/traits) is still the simplest path; reach for alias when you need to link two distinct identities. See Identity stitching.

The context object

context is free-form JSON and is stored largely as-sent alongside the event — the server never validates or rejects an event for its context contents. It makes two changes on the way in: it adds a server-derived ip_address, and it strips context.fingerprint when consent is absent (see below). UMAP360 recognises the following fields for attribution and identity:

interface Context {
  page?:     { url?: string; path?: string; title?: string; referrer?: string;
               search?: string; hash?: string; language?: string };
  campaign?: { source?: string; medium?: string; name?: string; term?: string; content?: string;
               // click IDs fed to the offline-conversion (Google / Meta CAPI) pipeline:
               gclid?: string; fbclid?: string; ttclid?: string; msclkid?: string; li_fat_id?: string;
               gbraid?: string; wbraid?: string; twclid?: string; igshid?: string;
               epik?: string; dclid?: string; rdt_cid?: string; gad_source?: string;
               referrer_chain?: { url: string; timestamp: string }[] };
  device?:   { id?: string; type?: string; manufacturer?: string; model?: string; uaClientHints?: object };
  browser?:  { name?: string; version?: string };
  os?:       { name?: string; version?: string };
  screen?:   { width?: number; height?: number; density?: number;
               viewport?: { innerWidth?: number; innerHeight?: number } };
  network?:  { online?: boolean; wifi?: boolean; cellular?: boolean; saveData?: boolean };
  session?:  { id?: string; started_at?: string; page_views?: number;
               events_count?: number; is_new_session?: boolean };
  fingerprint?: { hash?: string; components?: object };
  consent?:  boolean | { fingerprinting?: boolean; identity?: boolean; analytics?: boolean };
  gpc?: boolean;                       // Global Privacy Control signal, when asserted
  locale?: string;
  timezone?: string;
  timezone_offset_mins?: number;
  app?:     { name?: string; version?: string; build?: string; namespace?: string };
  library?: { name?: string; version?: string };
  user_agent?: string;
}

The Web SDK populates all of these for you; direct-REST integrators send whatever subset they have.

Click-ID fields under campaign (gclid, fbclid, …) are associated with the visitor's profile and used by the offline-conversion pipeline (Google / Meta CAPI), which reads each profile's stored click-IDs.

Fingerprint requires explicit consent

If you send context.fingerprint, the server strips it unless the same context carries an explicit grant — consent: true, consent.fingerprinting: true, or consent.identity: true. Without that grant the fingerprint is dropped before identity resolution and never stored. See Consent — DPDP & GDPR.

The response

A request that passes authentication, batch-shape validation, rate limits, and your plan's event quota returns 200 OK. The body reports how many events were accepted:

{ "success": true, "processed": 1, "failed": 0, "duration_ms": 45 }

If some events in the batch fail validation or processing, the request is still 200success flips to false and a per-event errors array lists the first ten failures, each as "<messageId>: <reason>":

{
  "success": false,
  "processed": 8,
  "failed": 2,
  "errors": [
    "msg_abc: messageId is required",
    "msg_def: event name is required for track events"
  ],
  "duration_ms": 120
}

Check the body, not just the status code

Because partial failures return 200, a 200 does not guarantee every event landed. Inspect failed and errors on every response, not just the HTTP status. The full list of per-event reasons is on the Errors & rate limits page.

An empty batch ({ "batch": [] }) is accepted and returns { "success": true, "processed": 0, "failed": 0, "duration_ms": ... }.

Response headers

Every response carries X-Request-ID (your value echoed back, or a server-minted one — useful for support correlation), API-Version: 1, X-Content-Type-Options: nosniff, and Referrer-Policy: no-referrer — see Response headers. While your org is in a billing grace period, a successful 200 also carries X-UMAP360-Quota-Status: grace (exposed via Access-Control-Expose-Headers, so cross-origin JS can read it); an active org sends no such header.

Two warn-only transforms (not rejections)

Two server-side adjustments are applied but never reject an event: (1) a timestamp dated more than ~5 minutes in the future is clamped (which also keeps retry de-duplication stable); (2) oversized properties / traits (more than ~300 keys, a value over ~32 KiB, or nesting deeper than 8 levels) emit a soft-cap warning but are still stored. The only hard request ceilings are the 1 MiB body and 100-event batch caps below.

Batch limits

LimitValueOn breach
Events per request100400Batch too large: maximum 100 events per request
Request body size1 MiB (1,048,576 bytes)413 — structured body with limit_bytes and a hint

The 1 MiB cap is enforced twice, so a request with a missing or understated Content-Length can't slip past it: first as a fast pre-auth check on the Content-Length header, then again while the body is read — the parser streams and aborts at the ceiling on the actual bytes received. Either way you get the structured 413 shown above. Split large volumes into multiple requests of ≤100 events / ≤1 MiB each — see Event volume.

Next

Last updated 2026-06-10

We use cookies for analytics — to understand how visitors use UMAP360 and improve the product. Essential cookies (session, forms) always run; analytics cookies wait for your call. See cookie policy.