Skip to main content
Every request the Portal SDK makes to Portal’s own APIs carries a trace ID. Portal’s server-side logs are indexed by that value, so a single ID turns “a signing request failed sometime Tuesday” into an exact record. As of Android SDK 9.1.0 you can supply your own trace ID, log it next to your own request IDs, and hand it to Portal support.

Overview

A trace ID is a UUID v4 sent as the X-Portal-Trace-Id HTTP header. The SDK attaches it to requests it makes to Portal-owned hosts, and maps it to the reqId field on MPC signing metadata so the same value covers both the API call and the MPC operation it triggers. There are two ways a trace ID comes into existence:
  • The SDK generates one. This is the default and requires no code changes.
  • You supply one. Pass it through RequestOptions, SendAssetParams, or rawSign to stitch Portal’s traces into your own observability.

Automatic tracing

You do not have to do anything. When you do not supply a trace ID, the SDK generates a fresh UUID for each request. Tracing is always on, and there is no way to disable it.

Which requests carry the header

The header is attached only when the request targets a Portal-owned host. The SDK checks the URL with isPortalOwnedUrl before setting the header, which is true for:
  • Hosts containing portalhq — for example api.portalhq.io and mpc.portalhq.io
  • Local development hosts — localhost, *.localhost, 127.0.0.1, and 10.0.2.2 (the Android emulator’s host loopback)
Everything else is excluded. In practice that means:
  • Third-party RPC endpoints configured through your gateway config do not receive the header. If you inspect traffic to your own Alchemy or Infura endpoint and see no X-Portal-Trace-Id, that is expected behavior, not a bug.
  • Google Drive requests made while storing or fetching a backup share do not receive the header.
Two consequences worth internalizing: you will not see the header on traffic to the third-party RPC providers you configure, and the SDK will not hand a correlation ID to them. The host check is a substring match on portalhq, so a custom host that happens to contain portalhq is treated as Portal-owned.
isPortalOwnedUrl is public, so you can check a URL yourself:

Supplying your own trace ID

Three public entry points accept a trace ID.

Provider requests

RequestOptions gained a traceId property:
RequestOptions.traceId is always mapped to the MPC signing metadata reqId, even when the RPC URL itself is a third-party host that does not receive the header. That is the non-obvious payoff: one value correlates the API request and the MPC signing operation it triggers.

Sending assets

SendAssetParams gained a traceId property:

Raw signing

rawSign gained a four-argument overload. The original three-argument signature is unchanged:

Multi-step flows share one ID

When an operation makes several requests, all of them carry the same trace ID. sendAsset resolves one trace ID at the start of the call and reuses it for the build-transaction request, the MPC signing operation, and the broadcast. Pass traceId in SendAssetParams and the whole flow is correlated under your value:
MPC wallet operations — createWallet, backupWallet, recoverWallet, and wallet ejection — and the EIP-7702 upgrade in portal.evmAccountType.upgradeTo7702 also share a single trace ID across all of their sub-requests. These operations generate the ID internally and do not accept one from the caller.
Generate one trace ID per user-facing action, not per request. A single ID spanning “user tapped Send” is what makes the correlation useful; a fresh ID per HTTP call tells you nothing you did not already know.

Reading the ID back

BackupWalletResponse gained shareIds and traceId:
This is the only place the SDK hands a generated trace ID back to you. No other response type exposes one, so do not go looking for the same accessor elsewhere.
traceId defaults to an empty string, not null. Check it with isNotEmpty() rather than a null check.

Helpers

The io.portalhq.android.utils package exposes the tracing primitives the SDK uses internally.
resolveTraceId exists so your own functions can take an optional trace ID and always have a concrete value to log:
The two Map extensions are for code that builds or inspects header maps directly — an OkHttp interceptor that stamps your own outbound requests with the same ID, for example:

Sharing a trace ID with Portal support

Log the trace ID alongside your own request IDs so that when something goes wrong you can look up both sides of the same event. Include the ID in support tickets — it is the fastest way for Portal to find the exact request.
A trace ID is not a secret, but it is a correlation handle. Do not log it next to user PII in a system with a different retention policy than your Portal logs.

Signature changes in 9.1.0

Threading traceId through the transport and API layers touched a lot of signatures. Almost none of it is breaking.
If you only call Portal’s APIs from Kotlin, 9.1.0 requires no changes. Every Api and EvmAccountTypeApi method that gained a traceId parameter did so through an overload, or — for ejectClient and storedClientBackupShare — through a default value plus @JvmOverloads, so those original signatures survive in both source and bytecode. PortalRequests is the exception: its methods gained a defaulted traceId without @JvmOverloads, so Kotlin source callers are unaffected but the pre-9.1.0 JVM descriptors are not preserved — see Transport layer below.

The overload-pair pattern

Most Api methods kept their original arity and gained a new one alongside it:
EvmAccountTypeApi follows the same pattern for getStatus, buildAuthorizationList, and buildAuthorizationTransaction. EvmAccountTypePortalDependency — the small interface you implement if you supply your own Portal dependency to EvmAccountType — gained a defaulted overload, so existing implementations still compile:

Two methods changed in place

ejectClient and storedClientBackupShare gained traceId in place with a default value rather than through a hand-written overload:
Both carry @JvmOverloads, so the compiler also emits the original zero-traceId arity into the bytecode. The 9.0.x JVM descriptors are preserved, which means existing calls keep resolving and no recompile is required:

Transport layer

Every PortalRequests method gained a trailing defaulted traceId:
This is the one change in 9.1.0 that can break a build. These methods changed in place rather than gaining an overload, so if you subclass PortalRequests to stub the transport in tests, your existing override fails with 'get' overrides nothing. until you add the traceId parameter.Because there is no @JvmOverloads here, the pre-9.1.0 JVM method descriptors are gone as well. Kotlin source callers are fine — the default value covers them — but a binary compiled against 9.0.x that calls these methods directly will not link against 9.1.0. Recompile any such module rather than dropping the new SDK in beside it.

Other affected types

Next Steps