Troubleshooting and rejections
Diagnose request validation, Relay v2, and signed callback failures.
Troubleshooting and rejections
Use the exact error boundary to find the problem. A wallet rejection, a relay response, and a callback verification error represent different states.
Request and envelope errors
| Symptom | Likely cause | Fix |
|---|---|---|
dapp.origin is rejected | The value is not a credential-free global HTTPS origin, or it contains a path, query, or fragment. | Store the canonical origin only, then rebuild the request. |
request has expired | exp is not in the future. | Create a fresh request. The SDK limits the expiry window to one hour. |
nonce must be 16-128 characters | A supplied nonce is too short, too long, or uses an unsupported character. | Let the SDK generate a nonce or supply a valid one. |
callback ... match dapp.origin | A direct callback has a different origin. | Use a callback on the dApp origin, or use the official Relay v2 callback URL. |
redirect_uri ... match dapp.origin | A redirect points to another origin or to the relay. | Serve the redirect route on the dApp origin. |
request_hash does not match envelope | A caller supplied a stale hash after changing a request or delivery field. | Recreate the envelope rather than editing its JSON. |
payload too large | The encoded envelope exceeds the SDK's 8192-byte limit. | Reduce request data and optional fields before encoding. |
Relay v2 HTTP responses
| Status or message | Meaning | Fix |
|---|---|---|
409 Session already initialized | The session was registered already. | Create a fresh session or reuse the already prepared session without registering it again. |
403 Missing capability, Invalid capability, or Forbidden | The URL lacks the right opaque authorization value, has the wrong kind of URL, or was not registered. | Use the exact URLs returned by prepareRelaySession(). Keep callback and read URLs in their separate roles. |
404 Not found | The path or method does not match a Relay v2 route. | Use the SDK-generated registerUrl, callbackUrl, streamUrl, or resultUrl. |
404 { status: "pending" } from resultUrl | No callback result has arrived yet. | Continue polling within the session lifetime, or use the SSE stream. |
405 Method not allowed | The HTTP method does not match the endpoint. | Use POST for registration and callback, and GET for stream and result. |
413 Payload too large | A registration or callback body exceeds the relay limit. | Send only the JSON body required by the API. |
415 Unsupported media type | A POST did not use a JSON content type. | Send Content-Type: application/json. |
429 Too many requests | Edge rate limiting or the per-session listener limit was reached. | Back off, stop duplicate subscriptions, and retry with a controlled schedule. |
503 Relay temporarily unavailable | The mandatory relay rate limiter is unavailable. | Retry after the server's Retry-After interval. |
Relay stream timed out | The five-minute SSE wait ended without a result. | Start a new request and subscription if the user still needs to act. |
Callback already accepted | A second callback was posted for a session whose result was already stored. | Treat the first accepted result as authoritative and make handling idempotent. |
Signed callback verification failures
| Error family | Meaning | Fix |
|---|---|---|
Callback body must be a signed Glyph callback envelope | Strict verification was requested but the body is unsigned or malformed. | Require the Wallet v2 signed envelope and do not fall back to shape parsing. |
Callback payload request_hash does not match expected request | The result is not bound to the request record you opened. | Reject it and investigate request storage or callback routing. |
Callback payload network does not match expected network | The result is bound to another network. | Reject it. Rebuild the request with the intended network. |
Callback payload dapp_origin does not match expected origin | The signed origin differs from the stored dApp origin. | Reject it and check canonical origin storage. |
Callback payload relay callback_url does not match expected callback URL | The result is bound to another delivery route. | Pass the original callback URL to expectedCallbackUrl and reject mismatches. |
Callback signed_payload is not canonical | The proof signs a different serialization of the payload. | Reject it. Do not reserialize or normalize the received payload before verification. |
Callback payload result_hash does not match result | The result was changed or the payload was not built from that result. | Reject it. |
Callback envelope public key is not trusted | The proof key is outside your configured trust list. | Reject it or update trust configuration through a deliberate key-management process. |
Callback envelope signature is invalid | Your Qubic SchnorrQ verifier returned false. | Reject it and inspect the exact UTF-8 payload, decoded signature, and decoded public key passed to the verifier. |
verifySignature is required | A signed envelope reached the SDK without a dApp-side verifier. | Use @qubic.org/crypto@1.0.0: check the algorithm, hash the supplied payload with k12(payload, 32), and return verify(digest, signature, publicKey). |
Wallet-side rejections
A user rejection is returned as a normal signed result:
{
status: "rejected",
type: "transfer",
nonce: request.nonce,
reason: "user_rejected",
}Handle status: "rejected" separately from malformed input, expired requests, replayed nonces, and failed signature verification. Do not retry automatically with the same nonce. Build a new request when a new user action is intended.
Replay and stale state
Wallet v2 replay tracking binds the network, dApp origin, nonce, and request hash. A request received again within the replay window is not a new approval opportunity. Discard stale request records and do not reuse a callback result after your application has handled it.
