Skip to content

Troubleshooting and error reference

When an app misbehaves on the platform it is usually one of a few things: a missing readiness call, a surface that never reported its size, a handshake the shell never answered, or a payload that cannot cross the iframe boundary. This page collects those failure modes with their fixes, then lists every error code the SDK emits.

Your app connects but receives no intents or events

Section titled “Your app connects but receives no intents or events”

connect() resolved and the session looks right, but the handlers you registered with provide() and subscribe() never fire.

The shell holds all intent, event, and navigation traffic for your app until you call platform.ready(). Until that call nothing is delivered, so an app that forgets it looks connected but stays inert. Wire your provide(), subscribe(), and route.onShellNavigate() handlers, then call ready() once. See The readiness barrier.

An embedded surface never renders or never sizes

Section titled “An embedded surface never renders or never sizes”

embedSurface() returned a handle, but the embedded iframe stays blank, or it renders clipped at zero height. There are three causes, each visible in how the SDK handles a surface:

  • The embed was refused or torn down. embedSurface() resolves synchronously; the surface is only live once its ready promise resolves, and that promise rejects if the shell refuses the embed or tears it down. Attach .catch to handle.ready (an unhandled rejection escapes otherwise) and read e.code to see why. That code is the shell’s close reason, not one of the codes the SDK mints; see the error code reference for how to read it.
  • The surface never reported a height. The shell sizes the surface’s iframe from the height the surface reports. Inside the surface body, call conn.observe(rootElement) (or conn.reportHeight(px)) so the shell can track your content; without it the iframe can stay at zero.
  • ready() was never called, or called too early. Call conn.ready() once the surface has rendered, and report height after layout so the first height the shell sees is the final one.

See Provide and host an embedded surface for the full provider and host wiring.

connect() (or bootstrap()) neither resolves nor rejects for a while, then rejects with [platform] handshake timed out waiting for welcome.

After your app posts its hello, the SDK waits for the shell’s welcome. If none arrives within ten seconds it rejects with that error. The SDK accepts a welcome only when its origin matches the platformShellOrigin stamped on your entry URL and its appId matches yours; a message from any other origin, or for a different app, is ignored, so an origin or id mismatch also ends in this same timeout.

Confirm the shell is actually loading your app and that its origin matches the one on your entry URL. This is a thrown Error, not a PlatformError with a code.

connect() throws synchronously, before any handshake, in three cases:

  • No window is present, for example during server-side rendering: [platform] connect() must run in a browser.
  • The app is running at the top level instead of inside the shell’s iframe: [platform] connect() must run inside the platform shell iframe (no parent window found).
  • The entry URL does not carry the parameters the shell stamps on it: [platform] missing handshake params on the app URL (platformShellOrigin, platformAppId).

In practice all three mean your app is running outside the shell. Let the shell load it, or, for local development without a shell, pass a fallback to bootstrap() so the app comes up in its logged-out standalone state instead of throwing. connectSurface() throws the equivalent errors for a surface entrypoint.

invoke(), publish(), notify.toast(), log(), embedSurface(), a modal open, or a surface close() throws a SerializationError synchronously, before the call leaves your app.

Every payload that crosses the iframe boundary must be structured-clone serialisable. The SDK checks each one eagerly and throws SerializationError rather than letting the browser raise an opaque DataCloneError. Pass plain data only: no functions, DOM nodes, or class instances. The error’s context names the offending call, for example invoke:customer.lookup.

Every code below arrives on a PlatformError ({ code, message, details? }), the shape that invoke() rejects with and that a surface or service call carries on failure. These are the codes the SDK itself mints.

codewhat it meansretryable?what to do
TIMEOUTNo response arrived within the call’s window: 30 seconds for invoke, notify, navigate, and flags; about 30 minutes for an open modal.Yes; it is a transient failure.Make sure the provider or service actually responds. For an invoke whose work is legitimately long, raise InvokeOptions.timeoutMs.
NO_HANDLERThe provider that received your invoke has no handler registered for that exact intent@version.Only if the provider had simply not registered yet; a name or version mismatch will not fix itself.Check the provider calls provide(intent, version, ...) for the version you requested, and that it has called ready().
HANDLER_ERRORThe provider’s intent handler threw. message carries the provider’s own error text.Depends on the provider’s failure, not on the SDK.Read message; the fault is in the provider’s handler, not in your call.
UNKNOWNThe call failed but no structured error came back, so the SDK substituted this, with the message Intent failed or Service failed.Unknown; the failure carried no detail.Treat it as an unclassified failure and check the provider and shell logs.