onError fires when a run never reaches a settled outcome at all. This is distinct from onClose, which means the person dismissed a run that did open. Every message today is a plain string. There is no error code to branch on.
When it fires
The Android only row also reaches a React Native app running on Android.
An empty session reports an error. A bad, expired or already used session does not. The web SDK makes no network call when it opens a session by id, so nothing rejects at call time. The problem shows up on the hosted page itself, inside the screen. The person can close that screen, which calls
onClose.
The flow itself runs on the browser SDK inside the native screen, so anything the browser SDK rejects with reaches onError with the same message.
Dismissal is not an error
Closing the screen without finishing callsonClose, or on React Native resolves the promise with { token: null, verified: false, vyc: null }. Look for a missing token instead. Do not treat this as a rejection.
React Native: onError and the promise both fire
A native error callsonError and rejects the returned promise with a real Error, so err.message reads the same string onError receives. Use whichever fits your code: the callback for a component that never awaits the handle, catch for one that does.