Error codes
Every CrossdeckError the SDK can throw. Each entry has a stable code string, a human description, a one-sentence resolution, and a retryable flag indicating whether the SDK auto-recovers (no developer action needed) or whether the failure requires intervention. Same surface Stripe ships at stripe.com/docs/error-codes.
TL;DR
- Every error the SDK throws is a
CrossdeckErrorwith a stabletype+code. - Catch by
codefor specific recovery (code === "environment_mismatch") or bytypefor broad categories (type === "authentication_error"). retryable: trueerrors are handled by the SDK's built-in retry — your code doesn't need to do anything special.- The same list ships as a machine-readable JSON sidecar at
@cross-deck/web/error-codes.jsonfor AI assistants, error dashboards, and tooling.
CrossdeckError shape
import { CrossdeckError } from "@cross-deck/web";
try {
await Crossdeck.identify("");
} catch (err) {
if (err instanceof CrossdeckError) {
err.type; // "invalid_request_error"
err.code; // "missing_user_id"
err.message; // human-readable
err.requestId; // server-issued; quote in support tickets
err.status; // HTTP status if the error came from an API response
err.retryAfterMs;// server's Retry-After when present
}
}
Seven distinct type values group errors into broad categories:
| type | What it means |
|---|---|
authentication_error | Bad API key, missing key, expired key. Server rejected the credential. |
permission_error | Key is valid but not authorised for the operation (e.g. wrong origin, wrong project). |
invalid_request_error | Malformed input — missing required field, invalid format, etc. |
rate_limit_error | Too many requests. Honour the retryAfterMs field. |
network_error | Failure at the transport layer — DNS, TCP, TLS, timeout, abort. |
internal_error | Crossdeck-side bug or transient outage. Server-issued. |
configuration_error | Wrong init options — caught client-side at Crossdeck.init(). |
Configuration errors
Thrown synchronously by Crossdeck.init(). Not retryable — fix the init options.
| code | Description | Resolution |
|---|---|---|
invalid_public_key |
The publishable key passed to Crossdeck.init() doesn't start with cd_pub_. |
Copy the key from your Crossdeck dashboard → API keys page. |
missing_app_id |
Crossdeck.init() was called without an appId. |
Add appId to your init options — find it in the dashboard's Apps page. |
invalid_environment |
Crossdeck.init() requires environment: "production" | "sandbox". |
Pass the literal string "production" or "sandbox" — no other values are accepted. |
environment_mismatch |
The publishable key's env prefix doesn't match the declared environment option. |
Either change environment to match the key prefix (cd_pub_test_ ↔ sandbox, cd_pub_live_ ↔ production), or swap the key. |
not_initialized |
An SDK method was called before Crossdeck.init(). |
Call Crossdeck.init({ appId, publicKey, environment }) once at app startup before any other method. |
Validation errors
Thrown when SDK method arguments don't meet the contract. Not retryable — fix the call site.
| code | Description | Resolution |
|---|---|---|
missing_user_id |
identify() was called with an empty userId. |
Pass a stable, non-empty user identifier from your auth layer — never a hardcoded placeholder. |
missing_event_name |
track() was called without an event name. |
Pass a non-empty string as the first argument. |
missing_group_type |
group() was called without a group type. |
Pass a non-empty type (e.g. "org", "team") as the first argument. |
missing_signed_transaction_info |
syncPurchases() was called without StoreKit 2 signed transaction info. |
Pass the JWS string from Transaction.currentEntitlements / Transaction.updates. |
Network errors
Transport-layer failures. Retryable — the SDK's built-in exponential-backoff retry handles them automatically for the event queue. Manual API calls (identify, getEntitlements, syncPurchases) surface them to the caller.
| code | Description | Resolution |
|---|---|---|
fetch_failed |
The underlying fetch() call failed (typically a network outage or DNS issue). |
Check the user's network. The SDK will retry automatically with exponential backoff (1s → 60s with jitter, capped at maxMs). |
request_timeout |
A request was aborted after the configured timeoutMs (default 15s). |
Check the user's connection. Increase timeoutMs in init options if the user is on a known-slow network. |
Internal errors
Crossdeck-side bugs or transient outages.
| code | Description | Resolution |
|---|---|---|
invalid_json_response |
The server returned a 2xx response with an unparseable body. | Likely a transient backend bug. Retry; if it persists, contact support with the requestId. |
Full table
The complete reference, sorted by type. The SDK exports the same list at runtime as CROSSDECK_ERROR_CODES, and ships it as a sidecar JSON for tooling.
import { CROSSDECK_ERROR_CODES, getErrorCode } from "@cross-deck/web";
CROSSDECK_ERROR_CODES;
// → ErrorCodeEntry[] — same shape as the table above
getErrorCode("environment_mismatch");
// → { code, type, description, resolution, retryable }
| code | type | retryable | Resolution summary |
|---|---|---|---|
invalid_public_key | configuration_error | No | Copy the key from the dashboard. |
missing_app_id | configuration_error | No | Add appId from the Apps page. |
invalid_environment | configuration_error | No | Pass "sandbox" or "production". |
environment_mismatch | configuration_error | No | Reconcile key prefix with declared env. |
not_initialized | configuration_error | No | Call init() before any other method. |
missing_user_id | invalid_request_error | No | Pass a non-empty userId. |
missing_event_name | invalid_request_error | No | Pass a non-empty event name. |
missing_group_type | invalid_request_error | No | Pass a non-empty group type. |
missing_signed_transaction_info | invalid_request_error | No | Pass the JWS from StoreKit 2. |
fetch_failed | network_error | Yes | SDK retries automatically. |
request_timeout | network_error | Yes | Check network or increase timeoutMs. |
invalid_json_response | internal_error | Yes | Retry; contact support with requestId if persistent. |
Server-side errors that come back via the API (rate limits, permission denials, validation failures) follow Stripe's envelope format — { error: { type, code, message, request_id } } — and surface as CrossdeckError with the appropriate status. The code values for those are issued server-side and may extend the list above.
Machine-readable JSON
For AI assistants, error-aggregator dashboards, and any tooling that wants the table without booting a JavaScript runtime, the SDK ships a sidecar JSON file in the npm tarball:
node_modules/@cross-deck/web/dist/error-codes.json
Shape:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"generatedAt": "2026-05-11T11:23:00.000Z",
"sdk": "@cross-deck/web",
"codes": [
{
"code": "environment_mismatch",
"type": "configuration_error",
"description": "The publishable key's env prefix doesn't match the declared environment option.",
"resolution": "Either change `environment` to match the key prefix (cd_pub_test_ ↔ sandbox, cd_pub_live_ ↔ production), or swap the key for one minted in the right env.",
"retryable": false
},
...
]
}
It is also reachable via the unpkg CDN at https://unpkg.com/@cross-deck/web@1.0.0/dist/error-codes.json for tooling that doesn't pull the package into its dependency tree.
Handling errors in app code
Two patterns. Pick by intent.
Catch a specific code for recovery
try {
await Crossdeck.identify(userId, { traits });
} catch (err) {
if (err instanceof CrossdeckError && err.code === "environment_mismatch") {
// Re-init with the right env, or surface a developer-facing toast.
} else {
// Unknown error — surface generically + log.
console.error("identify failed:", err);
}
}
Branch by category for broad strokes
if (err instanceof CrossdeckError) {
switch (err.type) {
case "authentication_error":
// API key is bad — alert the developer, not the user.
break;
case "rate_limit_error":
// Honour err.retryAfterMs — back off.
break;
case "network_error":
// Transient. SDK auto-retries the event queue; manual calls fail.
break;
}
}
track().
Crossdeck.track() is fire-and-forget and synchronous. It never throws on a network failure — that's the queue's job. The only way track() throws is the validation case missing_event_name, which is a programmer error caught the moment you call track("").
This list reflects @cross-deck/web@1.0.0. New codes added in future releases will appear here in lockstep with the SDK source at sdks/web/src/error-codes.ts.