Skip to content

Ingest Events (Server-Side)

POST /cdp/eventsauthenticated, server-to-server event ingestion. Send batches of events with your vyg_ API key so profiles are enriched from systems other than the storefront pixel (back-office apps, internal services, custom integrations).

Ingestion is asynchronous by default: the API validates every event at the edge, queues the accepted ones, and returns 202 immediately. A worker forwards queued events to the CDP with bounded concurrency — expect an accepted event to be visible on the profile within about a minute under normal load.

For debugging and low-volume request/response integrations there is an optional synchronous mode (?sync=true) that forwards the batch inline and returns the CDP’s processing result per event.

POST /cdp/events
Content-Type: application/json
Authorization: Bearer vyg_your_api_key
  • Batch cap: at most 50 events per request (10 with ?sync=true). A larger batch is rejected 400 and nothing is enqueued.
  • Body cap: 256 KB. A larger body is rejected 413 before it is parsed.
  • Scope is server-side. Every event is stamped with the shop scope resolved from your API credential. Do not send scope, shopDomain, or a source object anywhere in the batch — any such field is rejected 400 with the offending JSON path, even when the value matches your own shop.
FieldTypeRequiredNotes
eventTypestringyesA letter followed by letters/digits/underscores (max 64 chars).
profileIdstringyesMax 60 chars, no : characters.
sessionIdstringnoMax 60 chars, no : characters. Defaults to a server-minted id.
propertiesobjectnoEvent properties. Must not contain scope/shopDomain.
flattenedPropertiesobjectnoFlattened properties. Must not contain scope/shopDomain.
timeStampstringnoISO-8601. Defaults to forward time.
targetobjectno{ itemId, itemType, properties? } — no scope.

Unknown fields on an event are rejected (per-event) — the envelope schema is strict.

{
"events": [
{
"eventType": "orderShipped",
"profileId": "shopify_your-shop_1234567890",
"properties": { "orderId": "5678", "carrier": "ups" }
},
{
"eventType": "supportTicketClosed",
"profileId": "shopify_your-shop_1234567890",
"sessionId": "support-20260611-0001",
"properties": { "ticketId": "T-991" }
}
]
}

202 Accepted with per-event accounting — invalid events are rejected loudly, never silently dropped, and valid events in the same batch are still accepted:

{
"accepted": 1,
"rejected": [
{
"index": 1,
"errors": [
{
"path": "$.events[1].profileId",
"message": "profileId must not contain \":\" (Unomi eventcollector rejects colons in identifiers)"
}
]
}
],
"scope": "your-shop.myshopify.com"
}
  • accepted — number of events queued for delivery.
  • rejected[] — one entry per invalid event: its index in your events array and the validation errors with JSON paths into your request body.

POST /cdp/events?sync=true forwards the validated batch inline to the CDP and returns 200 with the CDP’s processing result per event. It is a debugging / low-volume tool — your request latency is coupled to CDP health by design. Use the default asynchronous mode for anything sustained or bulk.

Exactly the same validation, server-side scope forcing, and metering apply as in async mode (one shared validator) — plus three sync-specific guards:

  • Tighter batch cap: at most 10 events per sync request. A larger batch is rejected 400 (sync_batch_too_large) — switch to async (omit ?sync) for up to 50.
  • Hard 5-second forward budget: if the CDP times out, is unreachable, or answers 5xx, the API returns 503 (upstream_unavailable). Nothing is queued — the sync path never falls back to the async queue, so a retry cannot double-ingest from our side. Retry the batch yourself if needed (at-least-once duplicates are tolerated).
  • Its own throttle: sync requests are rate-limited (429, sync_throttled) on top of the API-wide limits. Back off and retry, or use the async path.

Any other ?sync value (?sync=1, ?sync=yes, …) is rejected 400 — only true enables synchronous mode (and false is the explicit async default), so you can never silently fall through to async while believing you got a synchronous result.

{
"accepted": 1,
"rejected": [],
"results": [
{
"index": 0,
"ok": true,
"status": 200,
"result": { "updated": 1, "processedItems": 1 }
}
],
"scope": "your-shop.myshopify.com"
}
  • results[] — one entry per forwarded event (by its index in your events array): the CDP’s HTTP status and, on success, its parsed processing result.
  • An event the CDP rejects permanently (4xx — e.g. an eventType with no registered schema) appears as ok: false with its status and a bounded error diagnostic. The request itself still returns 200; permanent rejections are per-event and are not retried.
  • rejected[] — same per-event edge-validation accounting as async mode.
Terminal window
curl -s -X POST "https://cdp.vyg.app/cdp/events?sync=true" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer vyg_your_api_key" \
-d '{
"events": [
{ "eventType": "orderShipped", "profileId": "shopify_your-shop_123", "properties": { "orderId": "5678" } }
]
}'
Terminal window
curl -s -X POST "https://cdp.vyg.app/cdp/events" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer vyg_your_api_key" \
-d '{
"events": [
{ "eventType": "orderShipped", "profileId": "shopify_your-shop_123", "properties": { "orderId": "5678" } }
]
}'
Statuserror codeWhen
400bad_requestBody is not valid JSON, has no events array, the array is empty, or ?sync is invalid.
400batch_too_largeMore than 50 events in one batch.
400sync_batch_too_largeMore than 10 events in one ?sync=true batch — use async mode for larger batches.
400scope_bindingAny event carries scope, shopDomain, or source — per-event JSON paths included.
401unauthorizedMissing or invalid credential.
403forbiddenAuthenticated, but no connected integration resolves a CDP scope for this brand.
405method_not_allowedMethod other than POST / OPTIONS.
413payload_too_largeBody exceeds the 256 KB cap.
429sync_throttledSync mode’s dedicated rate limit — back off and retry, or use the async path.
503service_unavailableThe ingest queue was unavailable — no events were accepted; retry the batch (async mode).
503upstream_unavailableSync mode: the CDP timed out / was unreachable / answered 5xx. Nothing queued — retry.

See Errors for the full envelope.

profileId and sessionId are CDP keyword identifiers: at most 60 characters and no : characters. Compose composite keys with underscores, e.g. shopify_your-shop_1234567890.

Each request records one usage row against your brand, the same as every other authenticated CDP API request.