For the complete documentation index, see llms.txt. This page is also available as Markdown.

Stealth App — Server Communication

This documents every network exchange the stealth app makes, plus how the Go utility communicates.

  • The stealth app runs headless (service controller → activity tracker) and talks to four distinct backends, not just one "origin".

  • The Go utility does NOT talk to any server. It is a local child process invoked by the C++ side over CLI args + stdout JSON. All its output goes back to C++, which then forwards data to the servers over HTTP/WebSocket.

  • All HTTP goes through a single REST client wrapper (60 s timeout). WebSockets go through a shared WebSocket client.


Backends / hosts

#
Host
Host source
Auth
Purpose

1

https://origin.in.we360.ai (the "origin server")

Hard-coded (compile-time constant)

X-Api-Key (public key)

Bootstrap/discovery: tenant + device manifests, app versions

2

Tenant API gateway

Tenant manifest (apiGatewayUrl)

Authorization: Bearer <token>

Core "me" API, activity event sync, screen-recording uploads, websockets

3

Keycloak auth server

Tenant manifest (apiKeycloakUrl + realm)

client_id + refresh_token

OAuth token refresh

4

WebSocket endpoint

Derived from #2 (https:wss:)

?token=<access_token> query

Remote commands + live streaming

Shared on every HTTP call: User-Agent: Stealth/<ver> <os>, plus any shared_headers returned in the tenant manifest. The access token, tenant id, auth server URL/realm, device id, etc. are stored locally in the config store.


Host resolution — hard-coded vs. tenant manifest

Only one host is baked into the binary; every other host is discovered at runtime.

  • https://origin.in.we360.ai is the only hard-coded host. It is a compile-time constant and is the discovery client's fixed entry point. The app always knows how to reach the origin server with no prior configuration.

  • Everything else is bootstrapped from the tenant manifest that the origin server returns. When the tenant manifest is fetched, the app persists these fields into the local config store and reads them back for all subsequent calls:

    • API gateway hostapiGatewayUrl from the manifest (stored under api_gateway_url, with a legacy fallback key coreServiceUrl). This host serves the entire core "me" API, raw-event sync, screen-recording URL requests, domain-blocking manifest, and direct-login.

    • Keycloak host + realmapiKeycloakUrl and apiKeycloakRealm from the manifest. The token endpoint is built as {apiKeycloakUrl}/realms/{realm}/protocol/openid-connect/token.

    • WebSocket host is not stored separately — it is derived at call time from the API gateway host by swapping the https: scheme for wss:. So the websocket endpoint always lives on the same host as the API gateway.

  • Screen-recording upload host is neither. The PUT upload target is a signed URL returned in the body of the get-upload-urls response, so it points at whatever cloud storage the backend chooses at runtime.

Implication: the origin server is the single root of trust. By returning a tenant manifest, it dictates which API gateway and which Keycloak the client will talk to from that point on. Re-pointing the app to a different environment is done entirely through the manifest, except for the origin host itself, which requires a rebuild.

Offline / pre-provisioned discovery. Administrators can instead supply the tenant manifest as a local protected file, zs-origin.json (Windows C:\Windows\System32\, macOS /Library/Preferences/, Linux /etc/). When present and valid — and admin-owned / not user-writable, which the agent enforces — the app loads discovery data from that file and skips all origin.in.we360.ai/discover/tenant_by_* calls (including the 30-min refresh), so the origin host can be fully firewall-blocked. The data path (gateway, auth, sync, streaming) is unchanged. This moves the discovery root of trust from a vendor-hosted network endpoint to a local artifact under the customer's own ACLs. See Pre-Provisioned Origin Data for the schema, behavior, and security model.


1. Origin server — discovery client

Bootstrap/discovery. Reads are unauthenticated except the device lookup and service-event post, which carry the public X-Api-Key.

Verb & Endpoint
Sends
Receives

GET /discover/tenant_by_base_domain/?baseDomain=<domain>

Tenant manifest (tenant id, api gateway url, keycloak url/realm, base domain, shared headers, SSO config)

GET /discover/tenant_by_id/?id=<tenantId>

Tenant manifest

GET /discover/device_by_host_name/?tenant_id=&host_name= + X-Api-Key

tenant id, hostname

Device manifest (cached locally)

GET /discover/app_versions/

App versions (for updater)

POST /discover/service-events// + X-Api-Key

JSON service event telling about installation/uninstallation health and status

In the stealth flow, the service controller fetches the tenant manifest by id on a timer (every 30 min) to keep it fresh. On manifest success the manifest is persisted and triggers a device-manifest lookup.


2. Core "me" API — core API client

Base = tenant gateway. Every call: Authorization: Bearer <accessToken>, tz, User-Agent, Content-Type: application/json. A 401 response triggers a Keycloak token refresh; 403 triggers re-onboarding.

Verb & Endpoint
Request body
Response

GET /api/v1/me/manifest/

User manifest (user/tenant config, feature flags)

GET /api/v1/me/always-on-mode-login/

{ stealth_config: {...} }

GET /api/v1/me/status/

User status (punch logs etc.)

GET /api/v1/me/break_types/

break types list

POST /api/v1/me/v2/punch_in/

{in_time, time_zone, device_id, tracker:{coordinate, isManual, "MANUAL"/"CLIENT_AUTO"}}

User status

POST /api/v1/me/punch_out/

punch-log tracker (coordinate, manual flag, source)

PUT /api/v1/me/device/

{machine_id, machine_user, machine_host_name, machine_ip, cpu_arch, os_name, os_version, platform}

Device (server device id, stored locally)

POST /api/v1/me/start_break/

{start_time, name, break_type:{id}, time_zone}

User break

POST /api/v1/me/end_break/

GET /api/v1/me/domain-blocking-manifest/

map of domain→action (URL blocking rules)

In stealth, the service controller pulls the user manifest, registers the device on first run (with machine details), and fetches the domain-blocking manifest on its update timers.


3. Activity & screen-recording sync — data sync service

Runs on a timer; backs off to a reduced frequency on failures. Skips when offline, sync-disabled, or user not identified. Base = tenant gateway, Bearer auth.

Verb & Endpoint
Sends
Receives

POST /api/v1/streams/raw_events/multiple/

{items:[ <activity event>, ... ]} — see payload below

accept-codes 2xx/400/409/412/413/422/428 mark events synced

POST /api/v1/screen-recording/get-upload-urls

{items:["<yyyy-MM-dd>/<file>.mp4", ...]}

{data:{upload_urls:{filename: signedUrl}}}

PUT <signed url> (cloud storage, off-gateway)

raw .mp4 bytes, Content-Type: video/mp4

200 → local file deleted

Activity event payload — the core tracking data: id, identity_id, tenant_id, session_id, event_type, application_name, title, url, path, image_data (base64 screenshot), start_time_epoch, end_time_epoch, system_status, device_id, active_time, idle_time, duration, keystroke_count, mouse_clicks, log_status.

Old local videos (>7 days) are purged each sync cycle.


4. Authentication — token manager

Verb & Endpoint
Sends
Receives

POST {auth_server}/realms/{realm}/protocol/openid-connect/token, application/x-www-form-urlencoded

client_id=<tenantId>&refresh_token=<rt>&grant_type=refresh_token

{access_token, refresh_token, expires_in} → stored in config

POST {api}/api/v2/onboarding/{tenantId}/standard-login/?username=&password=

username + password (query)

{access_token, refresh_token, access_token_expiry_epoch}

A 401/403/400 triggers re-onboarding. Refresh is rate-limited by a 5-min timer. The service controller requests a token refresh whenever a token-expired condition fires.


5. WebSocket channel — remote commands + live stream

Transport: a shared WebSocket client with a health-check ping timer, an outbound message queue drained on a timer, and auto-reconnect. URLs derive from the API base with https:wss: and ?token=<accessToken>.

Inbound commands — remote command listener

  • Connects (refreshed every 60 s when authenticated) to wss://<api-host>/websockets/subscribe/me_int?token=<token>.

  • Receives JSON commands {eventType, receiver, ...}. Handled types:

    • START_LIVESTREAM → add live-stream receiver, start streaming

    • SEND_SCREENSHOT → send a one-off screenshot (or enable continuous screenshot streaming)

    • PUNCH_OUT / FORCE_PUNCH_OUT → triggers a punch-out

    • SEND_LOGS → begins forwarding local diagnostic logs

Outbound live stream — live streamer

  • Per receiver, opens wss://<api-host>/websockets/publish/<identityId>?token=<token>.

  • Pushes messages of the form {payload: <base64 JSON>}:

    • App logs (on a timer): the current activity event plus identity_id, tenant_id — same fields as the sync payload.

    • Screenshots (alternating cycles, when enabled): {image_data (base64), idle_time, duration, active_time, event_type, system_status, identity_id, tenant_id}.

  • Auto-stops after an idle interval; sockets refreshed on a timer.


Go utility — local process helper

No server communication. The Go binary is a local helper; the C++ side spawns it as a child process and reads stdout.

Invocation
Direction
Data

processes

C++ ← Go (stdout)

JSON process summary of running processes; polled every 5 min. Parsed and cached.

terminate-process <pid> [--kill]

C++ → Go (args)

Kill/terminate a process by PID; queued and dequeued one at a time.

So the data path is: Go enumerates/kills local processes → returns JSON to C++ → C++ caches it and (via the sync service / live streamer) forwards process/activity data to the API gateway. The Go side never opens a socket to any server.


End-to-end flow (stealth)

  1. Bootstrap: the discovery client hits origin.in.we360.ai/discover/* → tenant + device manifests (API gateway URL, Keycloak config, shared headers) saved to the config store.

  2. Auth: the token manager obtains/refreshes a Bearer token against the tenant's Keycloak.

  3. Identity: the core API client registers the device and pulls user manifest, status, break types, and domain-blocking rules from the API gateway.

  4. Tracking: the activity tracker collects app/window/IO events (using the Go helper for process data); the data sync service batches them to the raw-events endpoint and uploads screen recordings via signed URLs.

  5. Realtime: the remote command listener subscribes over WebSocket for commands; the live streamer publishes live app logs/screenshots over WebSocket on demand.

Last updated

Was this helpful?