Troubleshooting
Error handling
All failures surface as a KasookooError:
| Property | Meaning |
|---|---|
err.message | Human-readable description. For validation failures the offending fields are appended, e.g. Request validation failed (last_name: Field required) |
err.code | Machine-readable. Either the SDK's own — network_error, invalid_config, invalid_params, not_initialized, push_unsupported, device_registration_failed, ... — or, when the API reports one, its code passed straight through (not_found, validation_error, ...). Falls back to api_error. |
err.status | HTTP status code (when the error came from an API response) |
err.details | Full raw response body, for debugging |
Correlation & tracing
The SDK attaches correlation headers to every API request, logs structured JSON to the browser console, and — if the optional OpenTelemetry packages are installed — reports logs and traces to Kasookoo. None of it needs any setup.
What gets sent automatically
| Header | Purpose | When it's set |
|---|---|---|
X-Request-ID | Unique per HTTP request | Every request |
X-Call-Trace-ID | Stable id for one whole user action (a call, a message send) — reused across every request that action makes | At call start / message send |
X-User-ID | The resolved Kasookoo user id | After init() resolves the user |
X-Feature-Type | Which feature category the request belongs to — in_app_call, sip_call, in_app_messaging, whatsapp_messaging, etc. | On feature-related requests |
initCall() / initSipCall() generate one callTraceId for the whole call lifecycle (token mint, reject, hang-up all share it) and expose it as call.callTraceId. Each sendMessage() / sendLocation() / sendWhatsAppMessage() gets its own short-lived one. Read-only calls (getConversations, getMessages, etc.) don't send a callTraceId.
Console logging
The SDK logs one compact structured JSON line per request to the browser console — useful for your own debugging, or if you forward console output to a log pipeline:
{"level":"info","ts":1785407238.633,"logger":"kasookoo-sdk.http","msg":"POST /api/v1/webrtc/call/tokens 200","event":"call_tokens_get","api_url":"https://.../api/v1/webrtc/call/tokens","method":"POST","status_code":200,"ok":true,"request_id":"7ccb6f86...","call_trace_id":"a1b2c3...","service":"kasookoo-web-sdk"}On by default. Configure or disable it via init():
const kasookoo = await KasookooClient.init({
publishableKey: "pk_live_...",
subject: "[email protected]",
logging: {
enabled: true, // set false to silence it entirely
level: ["info", "warn", "error"], // which level(s) to show — default. See below.
service: "my-web-app",
env: "production",
redactConsolePaths: true, // default — masks request paths/URLs before they hit the console
},
})level picks exactly which levels to show — not a minimum. Pass one level (level: "debug") or a list (level: ["debug", "error"]) — only what you name is emitted, everything else is skipped. There's no "and everything above it" behavior: asking for "warn" alone shows only warn, not error too — list every level you want. Defaults to ["info", "warn", "error"], i.e. everything except the noisier debug level.
Sensitive fields (token, password, authorization, etc.) are redacted automatically before anything is logged. Separately, redactConsolePaths (on by default) masks the request path in what's printed to the console — e.g. GET [redacted] 200 instead of GET /api/v1/users/filter?limit=50 200 — since devtools is visible to anyone using the browser.
logging only controls the browser console. OpenTelemetry export is separate: when its packages are installed it always receives the full, unredacted entries, even with logging.enabled: false.
Debugging in your app
Listen for trace:request to get the same ids in your own code — fires once per SDK HTTP request, after the response is received:
kasookoo.on("trace:request", ({ url, method, requestId, callTraceId, status, ok }) => {
console.debug("[kasookoo]", { url, requestId, callTraceId, status })
})Using the SDK's logger in your own code
getLogger() is exported directly, so your own app can log in the exact same structured JSON format shown above — useful if you want your app's logs to interleave cleanly with the SDK's, in the console or in a forwarded log pipeline.
There are two ways to use it:
Option A — share the SDK's settings. Call getLogger(name) with just a name. This logger uses the same enabled/level/redactConsolePaths config as KasookooConfig.logging — changing the SDK's logging config changes this logger's behavior too.
import { getLogger } from "kasookoo-sdk"
const logger = getLogger("my-app.checkout")
logger.info({ msg: "order_placed", order_id: "ord_42" })Option B — an independent logger with its own settings. Pass a second argument to give this logger its own enabled, level, service, etc. — completely separate from the SDK's, in both directions:
import { getLogger } from "kasookoo-sdk"
// Always shows debug + error, regardless of the SDK's own logging.level —
// and unaffected if that's reconfigured later.
const checkoutLogger = getLogger("my-app.checkout", { level: ["debug", "error"] })
checkoutLogger.debug("cart_loaded")Use Option A when you want your logs to simply follow the SDK's configuration. Use Option B when you want a different level (or service name, or on/off switch) for your own module, independent of the SDK's — e.g. debugging your own checkout flow at debug level while the SDK's own internal logs stay at their normal level.
configureLogger() is what init() calls internally to apply KasookooConfig.logging, and can also be called directly to change the SDK's shared settings later — this only affects Option A loggers (including the SDK's own internal loggers); Option B loggers are untouched:
import { configureLogger } from "kasookoo-sdk"
configureLogger({ level: "debug" })OpenTelemetry (logs and traces)
The SDK reports its own logs and traces to Kasookoo so integration problems can be diagnosed. This starts automatically when the optional OpenTelemetry packages are installed, and does nothing when they aren't:
npm install @opentelemetry/sdk-trace-web @opentelemetry/sdk-trace-base \
@opentelemetry/exporter-trace-otlp-http @opentelemetry/sdk-logs \
@opentelemetry/exporter-logs-otlp-http @opentelemetry/instrumentation \
@opentelemetry/instrumentation-fetch @opentelemetry/resources \
@opentelemetry/semantic-conventions @opentelemetry/api- Every API request is logged with the action performed (
event, e.g.user_identify) and the API called (api_url), plus the response body withtoken,secretandpasswordfields masked. - Problems that never reach the API — a denied permission, a push or service worker failure, an expired session, why a call ended — are logged as named events.
- A whole call is one trace, and its logs, events and requests share one
call_trace_id.
Kasookoo's own collector always receives this; it can't be turned off or redirected. To also send to a collector of your own:
const kasookoo = await KasookooClient.init({
publishableKey: "pk_live_...",
subject: "[email protected]",
telemetry: { endpoint: "https://your-collector.example.com/v1/traces" },
})telemetry also takes logsEndpoint, serviceName and environment; they only affect your own collector.
Advanced: manual correlation
For integrations outside the built-in call/messaging flows, the header-building helpers are exported directly:
import { FEATURE, buildCorrelationHeaders, newCallTraceId } from "kasookoo-sdk"
const headers = buildCorrelationHeaders({
callTraceId: newCallTraceId(),
userId: "usr_abc",
featureType: FEATURE.IN_APP_MESSAGING,
})
await fetch("https://sdk.kasookoo.ai/api/v1/...", {
headers: { ...headers, Authorization: "Bearer ..." },
})Updated 15 days ago
