Embedded Web SDK API
The endpoints that the@devotel-orbit/web browser SDK calls at runtime. Mounted at /sdk/* (no /api/v1 prefix) so client-side embeds don’t have to think about API versions.
Base path: https://api.orbit.devotel.io/sdk
Authentication: Tenant public key (dv_live_pk_*) sent in the X-API-Key header. CORS is open — these endpoints are designed to be called directly from your website’s JavaScript.
Why a separate base path: server-to-server keys (dv_live_sk_* in the Authorization header) are secret. Browser-embedded public keys (dv_live_pk_* in X-API-Key) are safe to ship in client-side code. The split base prevents accidentally exposing a server key in a browser bundle.
Response envelope: every endpoint returns the platform envelope — data plus a meta block with request_id and timestamp.
Each endpoint below pairs the raw cURL with the equivalent @devotel-orbit/web TypeScript client — one endpoint, one SDK method, same envelope. The sample pairs follow the SDK sample coverage policy.
Identify
Resolve an anonymous browser visitor to a known contact (by email or phone). Events captured before the visitor identifies attach to the right contact once the server backfills. The response returns the resolvedcontact_id:
anonymous_id is required (it ties the call back to the browser session); the SDK sends its browser-sticky id for you. Include at least one of email or phone — omitting both returns a 400/422 validation error. display_name and traits are optional. Identifying is idempotent: repeat calls with the same email or phone update the same contact_id.
Track
Capture one or more custom events (page view, button click, video watched, item added to cart). Events flow into the same store that powers segmentation, scoring, and contact timelines. A batch returns the count of accepted events:name; properties and an ISO-8601 timestamp are optional (properties are capped at 8 KiB per event — split or trim larger payloads). The required anonymous_id identifies the browser session, so events captured before a visitor identifies still attach to the right contact. You can also pass identity ({ "email": ... } or { "phone": ... }) to attribute the batch to an existing contact without an identify call. In the SDK, track() batches events client-side and attaches the sticky anonymous id automatically.
Personalize
Resolve the right personalization slot variant for the current visitor at runtime. The response carries the slot’s content, CTA, matched variant, and the segment that matched (ornull when the slot default applied):
phone or email alongside anonymous_id to resolve the visitor’s segment. With no matching variant, content is empty and variant is null — render your site’s own default. Responses carry a Cache-Control: private, max-age=60 header, so repeat pulls from the same browser hit the local cache.
In-app feed
Pull the visitor’s eligible content cards and in-app messages — the feed theOrbitInApp renderer polls. Authoring lives under Messages → In-app (PUT /api/v1/messages/in-app); this endpoint is the owned-channel delivery leg. No carrier is involved — delivery is this pull from your own site or app:
anonymous_id on the query so segment-targeted surfaces resolve: a surface authored with segmentLabels is delivered only when that visitor’s resolved segment matches. When the channel is disabled or nothing is authored, the feed is { "enabled": false, "cards": [], "messages": [] } — never an error. Responses carry Cache-Control: private, max-age=30, stale-while-revalidate=60, because per-visitor targeting must not leak into shared caches. fetchFeed() returns the feed already assembled — expired and locally-dismissed surfaces dropped, pinned cards first, messages priority-sorted — an empty feed on any transport error.
In-app events
Report engagement with a content card or in-app message — an impression, click, or dismissal.button_id identifies which CTA a click hit, and user_id carries your host app’s end-user id when one is known:
/sdk/track, so in-app engagement shows on the contact timeline and in analytics alongside on-site behaviour. The row is recorded under anonymous_id alone; a later /sdk/identify backfills it onto the contact, exactly like pre-identify tracked events. The SDK surfaces (OrbitInApp in the web SDK, OrbitInAppClient in the React Native SDK) report these events for you — call this endpoint directly only when you render the feed with your own components.
Anonymous unload track
Send the pending event batch on page unload without embedding a key. Modern browsers flush on unload withfetch({ keepalive: true }) — the key stays on the X-API-Key header and this endpoint is never touched. A small tail of older browsers lacks keepalive, so the SDK falls back to navigator.sendBeacon against this key-free path:
202, with no authentication — a page unloading cannot act on an error, so every outcome is accepted. Events land in a tenant-unattributed staging area keyed only by the anonymous_id, and the next /sdk/identify call reconciles them into the resolving tenant. Until reconciliation the rows carry no tenant attribution, so a keyless caller cannot steer events into another tenant. Note the asymmetry: staging accepts the same event body as /sdk/track, but unlike /sdk/track no identity block can resolve it early — only a later identify attaches it.
Server-to-server calls
The/sdk/identify and /sdk/track endpoints also accept calls from your backend — a CRM sync, an order pipeline, a nightly export job — not just from the browser. Same envelope and body shape, sent with the tenant public key (dv_live_pk_) in the X-API-Key header. The endpoints accept both transports, but use the right key class: server-to-server pipelines send the public key, never a secret key (dv_live_sk_). Browsers remain the preferred integration for behavioural events — the @devotel-orbit/web package batches them safely, handles CORS, and manages anonymous ids automatically. Only use the server-side path when the events genuinely originate in your backend.
A Node.js server-side push (a CRM-side event push, plain fetch idiom — the typed helpers on the Node.js SDK page wrap the same endpoints when you want them):
/sdk/track batch (up to 50 per call) instead of firing one HTTP request per event — both to stay under the per-key pacing and to keep CORS/browser semantics out of the loop. Grouping by callers like a CRM import also lets you pass a single shared identity in one batch call rather than one call per contact.
Use the SDK instead of raw fetch
For browser integrations, use the@devotel-orbit/web package — it handles the anonymous id for you (a stable id persisted in the visitor’s browser), batches events into groups of up to 25 every 2 seconds, and flushes on page exit. The raw-fetch flow above becomes:
orbit.anonymousId you can reference in server-side flows; orbit.reset() clears a remembered identity for explicit logouts.
See also
- Personalization API — the dashboard side that defines slots
- Web SDK reference — the JS package wrapping these endpoints