Event Ingestion
Server event ingestion authenticates with an explicitly scoped secret API key. Browser SDK ingestion uses the short-lived runtime token obtained from the public site ID. The server stamps the trust class; client-supplied trust fields are ignored.
Secret-authenticated events may update authoritative commerce/profile state and reach workflows with external side effects. Browser events are limited to analytics and bounded onsite behavior.
POST /events/track
Track one event.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
event_type | string | Yes | Supported canonical event name. |
session_id | string | Yes | Non-empty session identifier. |
properties | object | No | Event-specific fields. |
user_id | string | No | Merchant-scoped authenticated user ID. |
device_id | string | No | Non-empty device identifier. |
client_event_id | string | No | Stable client identifier for retry deduplication. |
occurred_at | ISO 8601 string | No | Client occurrence time; receipt time is used when omitted. |
context | object | Yes | Browser or client context. |
context.page_url | URL | Yes | Page associated with the event. |
context.user_agent | string | Yes | Non-empty client user agent. |
Optional context fields include referrer, page_title, screen dimensions, device_type, browser, OS, language, and timezone.
- cURL
- JavaScript
- Python
curl --request POST \
'https://dashboard.upsur.ge/api/v1/events/track' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: upsurge_sk_test_replace_me' \
--data @event.json
const response = await fetch(
'https://dashboard.upsur.ge/api/v1/events/track',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.UPSURGE_SECRET_API_KEY,
},
body: JSON.stringify(event),
},
);
const payload = await response.json();
import os
import requests
response = requests.post(
"https://dashboard.upsur.ge/api/v1/events/track",
headers={"X-API-Key": os.environ["UPSURGE_SECRET_API_KEY"]},
json=event,
timeout=30,
)
payload = response.json()
Responses
201— stored as a new event.200— accepted as a duplicate;data.deduplicatedistrue.400— envelope or event-specific validation failed.401— key is absent, invalid, expired, or lacks event permission.429— hourly key limit exceeded.
{
"success": true,
"data": {
"event_id": "V1StGXR8_Z5jdHi6B-myT",
"timestamp": "2026-07-11T20:00:00.000Z"
},
"meta": {
"timestamp": "2026-07-11T20:00:00.012Z",
"request_id": "R8k2example"
}
}
POST /events/batch
Track 1–100 events with the same event shape:
{
"events": [
{
"event_type": "page_view",
"session_id": "session_01JEXAMPLE",
"client_event_id": "page-home-0001",
"context": {
"page_url": "https://shop.example.com/",
"user_agent": "custom-storefront/1.0"
}
}
]
}
The batch endpoint returns 201 when every item succeeds and 207 Multi-Status when at least one item fails. Always inspect data.results; failed items include an error and are not implied by the top-level success flag alone.
During unload only, the SDK may send { "api_key": "…", "events": [...] } with navigator.sendBeacon. Use a header in ordinary direct integrations.
Event-specific requirements
| Event | Required properties |
|---|---|
product_view, add_to_cart, remove_from_cart | product_id |
purchase | transaction_id or order_id; and total_amount, total, or revenue |
search | query |
custom | event_name |
chatbot_message | message |
| Recommendation impression | recommendation_id, campaign_id |
| Recommendation click | recommendation_id, campaign_id, product_id |
| Overlay lifecycle | overlay_id, campaign_id; quiz events require additional stable IDs |
Use the browser event helpers when possible; they normalize the common payloads.
Deduplication
Provide a stable client_event_id for retryable server and native integrations. Upsurge also derives stable keys from event type, site, session, occurrence time, page, and business identifiers. A duplicate response is successful but does not create another analytics event.