Skip to main content

Wallet External ID Migration

Wallet external IDs let you address a tenant user by an immutable application-defined wallet identifier. Use this guide when you already store wallet references in platformId, custom metadata, or an external user table and want to move those references into Steward’s Privy-style wallet external ID surface.

Before You Start

You need:
  • A platform key with platform:user:read, platform:user:write, and platform:tenant-user:read scopes when scoped platform keys are enabled.
  • The tenant ID that owns the external wallet namespace.
  • A source-of-truth mapping of userId or email to the desired walletExternalId.
Wallet external IDs are write-once per user and tenant. Run the lookup steps first and resolve conflicts before assigning values.

1. Normalize Source IDs

Choose one stable external ID format and keep it tenant-scoped. Good examples:
wallet_01J9X4T8GQ8A7P2
custody-user-8421
legacy-platform-id-ada
Avoid values that encode secrets, emails, phone numbers, or raw provider access tokens. Steward returns wallet external IDs in API responses and dashboard surfaces.

2. Check for Existing Assignments

Before assigning a value, ask Steward whether that wallet external ID is already claimed in the tenant.
const existing = await steward.platformUsers.resolveWalletExternalId({
  tenantId: "app-prod",
  walletExternalId: "legacy-platform-id-ada",
});

if (existing.user) {
  console.log("Already assigned to", existing.user.userId);
}

3. Assign Known Users

If you already know the Steward userId, assign the external ID directly.
await steward.platformUsers.assignWalletExternalId("usr_123", {
  tenantId: "app-prod",
  walletExternalId: "legacy-platform-id-ada",
});
Steward returns 409 if the external ID belongs to another user in that tenant or if this user already has a different wallet external ID in that tenant.

4. Connect or Create Missing Users

If your migration source only has an email and wallet external ID, use connect-or-create. Steward resolves the wallet external ID first, then connects an existing email user or creates a new user.
const result = await steward.platformUsers.connectOrCreateByWalletExternalId({
  tenantId: "app-prod",
  walletExternalId: "legacy-platform-id-ada",
  email: "ada@example.com",
  emailVerified: true,
  customMetadata: { migratedFrom: "legacy-platform-id" },
});

console.log(result.userId, result.isNew, result.createdExternalId);
After assignment, tenant user search can filter by wallet external ID.
const result = await steward.platformUsers.search("app-prod", {
  walletExternalId: "legacy-platform-id-ada",
});

if (result.users.length !== 1) {
  throw new Error("Migration verification failed");
}

Conflict Handling

ConflictMeaningResolution
walletExternalId already belongs to another user in this tenantThe source mapping points to an ID already claimed by a different Steward user.Fix the source mapping or merge/transfer the affected user before retrying.
walletExternalId is immutable for this user in this tenantThe target user already has a different external ID.Keep the existing ID, or create an explicit user merge/transfer plan.
tenantId is required for walletExternalId lookupExternal IDs are tenant-scoped.Pass the tenant ID on every lookup and assignment.
Tenant not foundThe target tenant does not exist or the platform key cannot access it.Create the tenant or use the correct platform key.

Cutover Checklist

  • Export or inventory all source wallet references before writing.
  • Normalize IDs and remove secrets or user PII from the external ID value.
  • Dry-run every ID through resolveWalletExternalId.
  • Assign known userId mappings first.
  • Use connect-or-create only for records that do not have a Steward userId.
  • Verify with tenant search by walletExternalId.
  • Update application code to look up users by walletExternalId instead of custom metadata or platformId.
  • Keep the old metadata value for one deploy cycle if your application still needs a rollback path.