Skip to main content

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 resolved contact_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:
Send up to 50 events per call. Each event needs a 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 (or null when the slot default applied):
Pass 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 the OrbitInApp 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:
Pass 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:
The event lands in the same event store as /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 with fetch({ 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:
Always 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):
Batching discipline is yours on the server. The browser SDK transparently batches up to 25 events every 2 seconds and flushes on page exit; a raw server-side caller must decide this trade-off explicitly. Group events into a single /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:
Every page has a stable orbit.anonymousId you can reference in server-side flows; orbit.reset() clears a remembered identity for explicit logouts.

See also