> ## Documentation Index
> Fetch the complete documentation index at: https://turnkey-0e7c1f5b-connor-waas-doc-platform-framing.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Embedded Wallet-as-a-Service

> Distribute embedded wallets through your developer platform with your own SDK, APIs, or UI components. Turnkey handles key management; your platform owns the developer experience.

export const FeatureCard = ({title, description, icon, logo, href}) => {
  return <a href={href} className="not-prose font-normal group ring-0 ring-transparent cursor-pointer block rounded-lg border border-zinc-950/10 dark:border-white/10 bg-white dark:bg-transparent p-5 no-underline hover:border-primary/40 transition-colors">
      <div className="tk-card-row">
        <span className="tk-card-icon-wrap">
          {logo ? <img src={`/images/networks/${logo}.svg`} className="tk-card-network-logo" alt="" /> : <span className="tk-card-icon" style={{
    maskImage: `url(/images/icons/${icon}.svg)`,
    WebkitMaskImage: `url(/images/icons/${icon}.svg)`
  }} />}
        </span>
        <div>
          <div className="font-semibold text-sm text-zinc-950 dark:text-white group-hover:text-primary transition-colors">
            {title}
          </div>
          {description && <div className="text-sm text-zinc-500 dark:text-zinc-400 mt-1">
              {description}
            </div>}
        </div>
      </div>
    </a>;
};

Your platform sits between Turnkey and your developers: you expose your own SDK or APIs, and Turnkey is invisible to the people integrating with you. Your platform controls onboarding, authentication, transaction approval, and billing, all on top of Turnkey's key management infrastructure.

## Powered by Turnkey

* [**Helius**](https://www.helius.dev/use-case/wallets) -- Solana's leading RPC and API platform. Helius offers Wallet-as-a-Service to its developers using Turnkey's key management infrastructure alongside its RPC, data streaming, and transaction landing services.
* [**DIMO**](https://www.turnkey.com/customers/how-dimo-is-bringing-transportation-solutions-onchain-with-turnkey) -- decentralized transportation network with 165,000+ connected vehicles. 90% reduction in onboarding time, 30% increase in completion rates. Built their own [transactions SDK](https://github.com/DIMO-Network/transactions) on Turnkey's infrastructure.

## Why Wallet-as-a-Service?

With [consumer wallets](/solutions/embedded-wallets/embedded-consumer-wallet) and [business wallets](/solutions/embedded-wallets/embedded-business-wallets), your application integrates Turnkey directly and manages wallets for end users. With Wallet-as-a-Service, you build wallet infrastructure on top of Turnkey, and your customers integrate with your SDK rather than Turnkey directly.

Your platform handles auth, signing flows, and transaction submission on behalf of your developers. Turnkey processes and secures every signing request, but your customers never interact with Turnkey directly.

This gives you control over:

* **The developer experience.** Your APIs, your SDK, your branding.
* **Transaction approval.** Your backend decides which requests go through and when.
* **Billing and metering.** Adapt usage to your business model.
* **Compliance.** Run your own checks before any transaction executes.

### When to use Wallet-as-a-Service

| Scenario                                              | Why Wallet-as-a-Service fits                                                                                         |
| :---------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------- |
| Your customers integrate with your SDK, not Turnkey's | You own the developer experience end-to-end. Developers never interact with Turnkey directly.                        |
| You need platform-level approval on every transaction | Your backend evaluates billing status, compliance checks, or risk rules before any fund-moving transaction executes. |
| You want per-user key isolation with no shared state  | Each end user maps to a Turnkey sub-organization: isolated wallets, credentials, policies, and activity logs.        |

## Architecture

Each end user maps to a [Turnkey sub-organization](/features/sub-organizations). The sub-org contains the user's wallets, credentials, policies, and activity logs, fully isolated from other users and from your platform's management layer.

<Frame>
  <img src="https://mintcdn.com/turnkey-0e7c1f5b-connor-waas-doc-platform-framing/PqQFaOX7KeA3iWcJ/images/embedded-wallets/waas.png?fit=max&auto=format&n=PqQFaOX7KeA3iWcJ&q=85&s=c00533a8fc10a89ff225d3afd3727756" alt="Wallet-as-a-Service platform and end user sub-organization model" width="2604" height="1839" data-path="images/embedded-wallets/waas.png" />
</Frame>

### Transaction authorization flow

In the simplest configuration, your platform holds the sole API key that authorizes transactions. End users authenticate to your platform; your platform validates the request and signs via Turnkey on their behalf.

1. End user initiates a transaction from your embedded wallet UI
2. End user authenticates to your platform (passkey, OTP, OAuth, or equivalent)
3. Your platform evaluates its own rules on the backend: billing status, risk controls, compliance checks
4. Your platform approves via API key and the transaction executes on Turnkey
5. If your platform withholds approval, the transaction does not execute

This flow keeps Turnkey entirely behind your platform. Developers and end users interact only with your SDK and APIs.

<Note>
  This baseline is a custodial arrangement: your platform holds the only credential that can authorize signing. To make end users verifiably non-custodial, add them as a root user and require their approval on every transaction. See [End-user co-signing](#end-user-co-signing-2-of-2-root-quorum) and [Custody models](/solutions/embedded-wallets/overview#custody-models).
</Note>

See [Core concepts](/get-started/about-turnkey#core-concepts) and [Root quorum](/features/users/root-quorum).

## Implementation

### Step 1: Create a sub-organization per end user

Map each end user to a [sub-organization](/features/sub-organizations). Your parent organization has read-only access to sub-orgs and can initiate auth and recovery flows, but cannot sign transactions or modify policies within them.

Creating a sub-org returns a `subOrganizationId`. You can use this identifier to map your users to a Turnkey sub-organization. Future API calls to Turnkey such as wallet lookups, policy changes, and signing will reference this sub-organization identifier.

In the baseline model, your platform is the sole root user of each sub-org, authenticated via API key. Decide up front what every sub-org gets by default: wallet structure, supported chains and accounts, and baseline policies.

```javascript theme={"system"}
const subOrg = await turnkeyClient.createSubOrganization({
  parameters: {
    subOrganizationName: `User Wallet - ${userId}`,
    rootUsers: [
      { userName: "Platform", apiKeys: [{ publicKey: PROVIDER_KEY }] },
    ],
    rootQuorumThreshold: 1,
    wallet: {
      walletName: "Primary Wallet",
      accounts: [
        {
          /* eth account */
        },
      ],
    },
  },
});

// Store this against your own user record
await db.users.update(userId, { turnkeySubOrgId: subOrg.subOrganizationId });
```

Two options to layer in at creation time:

* **End-user authenticator.** Add a passkey (or equivalent) as a second root user if you want users to hold their own signing key. See [End-user co-signing](#end-user-co-signing-2-of-2-root-quorum).
* **Delegated access.** For automation or backend-initiated workflows, add a scoped non-root API key via [Delegated Access](/features/policies/delegated-access/overview). Keep it tightly policy-scoped: it should never have broad signing authority or bypass user consent.

If you want users to be able to export their keys independently, add an export policy:

```javascript theme={"system"}
await turnkeyClient.createPolicy({
  organizationId: subOrgId,
  parameters: {
    policyName: "Allow User Wallet Export",
    effect: "EFFECT_ALLOW",
    consensus: `approvers.any(user, user.id == '${endUserId}')`,
    condition: `activity.type == 'ACTIVITY_TYPE_EXPORT_WALLET'
      && wallet.id == '${walletId}'`,
  },
});
```

See [Sub-organizations](/features/sub-organizations), [Policies](/features/policies/overview), and [Export wallets](/features/wallets/export-wallets).

### Step 2: Build your integration surface

Create the SDK, APIs, or UI components that developers will integrate with. Abstract Turnkey and expose only your platform's intended wallet, auth, and signing flows.

* **Embedded Wallet Kit (EWK):** Fork or wrap [EWK](/solutions/embedded-wallets/integration-guide/react/index) components (authentication, wallet UI, approval prompts) with your branding.
* **Backend service:** Handle platform API key approvals, billing and risk evaluation, and activity monitoring through your backend.
* **SDK abstraction layer:** Wrap Turnkey's SDK calls behind your own interface for full control over the developer experience.

See [Embedded Wallet Kit](/solutions/embedded-wallets/integration-guide/react/index) and [SDK Reference](/sdks/introduction).

### Step 3: Wire the wallet into your platform flow

Integrate the wallet into your onboarding and runtime flows so every integration inherits a working embedded wallet.

* **Onboarding:** Create the sub-org as part of user registration. The end user should experience this as a natural part of your sign-up flow.
* **Client initialization:** Initialize the Turnkey client with the user's sub-org context on each session. Use [sessions](/features/authentication/sessions/overview) for batched signing workflows to reduce authentication friction.
* **Transaction flow:** Your backend receives the request, runs its checks, then submits the approval to Turnkey via API key.
* **Recovery:** Expose the export flow in your settings UI so users can self-serve wallet recovery if needed. Turnkey's enclave encrypts the mnemonic to a user-generated target key via HPKE. Neither Turnkey nor your platform can view the exported material.

## Hardening your platform

The setup above is the quickest way to get up and running with Wallet-as-a-Service. For additional layers of security and abuse protection, see the options below.

### End-user co-signing (2-of-2 root quorum)

Add the end user as a second root member and raise the quorum threshold to 2. With this model, every fund-moving transaction requires both the end user's authenticator (passkey or equivalent) and your platform's API key. Neither party can move funds unilaterally.

<Frame>
  <img src="https://mintcdn.com/turnkey-0e7c1f5b-connor-waas-doc-platform-framing/8EIomqax5hcwdEaO/images/embedded-wallets/waas-tx-auth.png?fit=max&auto=format&n=8EIomqax5hcwdEaO&q=85&s=d5fee05c6b0ed4a46e47a0bb5defa86d" alt="Wallet-as-a-Service transaction authorization with platform co-signing" width="2604" height="1839" data-path="images/embedded-wallets/waas-tx-auth.png" />
</Frame>

This is the right choice when:

* You want end users to be verifiably non-custodial
* Your compliance posture requires end-user consent on every transaction
* You are building a platform where user key sovereignty is a differentiator

To set this up, create the sub-org with both root users at a 1-of-2 threshold, add the export policy so users retain an escape hatch, then raise the threshold to 2-of-2:

```javascript theme={"system"}
// Add end user as second root member at org creation
rootUsers: [
  { userName: "Platform", apiKeys: [{ publicKey: PROVIDER_KEY }] },
  {
    userName: "End User",
    authenticators: [
      {
        /* passkey */
      },
    ],
  },
],
rootQuorumThreshold: 1, // start at 1-of-2 so you can configure policies first

// ... create export policy ...

// Then raise to 2-of-2
await turnkeyClient.updateRootQuorum({
  organizationId: subOrgId,
  parameters: { threshold: 2, userIds: [providerUserId, endUserId] },
});
```

Set the threshold last so your platform can configure policies (including export) with only your platform's approval. Root quorum actions bypass the policy engine, but an explicit policy can allow an individual root user to act alone for a specific activity. That is what keeps export working: once the 2-of-2 quorum is in place, the end user can still trigger exports via the export policy without your platform's co-signature.

See [Root quorum](/features/users/root-quorum) and [Policy examples](/features/policies/examples/access-control).

### Proxying and IP allowlisting

Turnkey's public API has an open CORS policy, so your SDK can POST signed requests straight to `api.turnkey.com`. Routing them through your backend instead gives your platform a single egress point for every Turnkey call. At that entry point you can validate requests before forwarding them, apply rate limits per merchant or per user, persist activity results such as new wallet addresses, and feed your own monitoring.

Treat this as a soft gate. Requests are signed on the client, so your proxy can forward, drop, or delay a request but cannot alter it, and a client holding a valid credential can still reach Turnkey directly. Keep the enforceable controls in [policies](/features/policies/overview) and use the proxy for operational visibility and gating.

IP allowlisting hardens the traffic that does originate from your servers. Define trusted CIDR blocks and requests from any other IP are rejected. Configure this at the organization level for all requests, or per API key to scope specific keys to specific environments. The org-level allowlist must be enabled for per-key rules to take effect.

<Warning>
  IP allowlisting applies to the **parent organization only**. Sub-organizations do not inherit parent allowlist rules and cannot define their own, so requests authenticated against a sub-org, including your platform's transaction approvals, are not evaluated against an allowlist. Enabling an org-level allowlist with no CIDR rules blocks all API traffic, so stage rules with `enabled: false` first.
</Warning>

Available to Enterprise clients on the Scale tier or higher.

See [Proxying signed requests](/features/authentication/proxying-signed-requests) and [IP Allowlist](/features/ip-allowlisting/overview).

### Captcha protection

Enable Cloudflare Turnstile to block bots and credential-stuffing attacks at the two highest-risk entry points: email/SMS OTP requests and new account signups. Configurable per organization in the Turnkey Dashboard.

<Note>
  Enforcement is automatic only for `@turnkey/react-wallet-kit` (v2.4.0+). If your platform builds its own UI on `@turnkey/core`, you must render the Turnstile widget and pass captcha tokens to the relevant SDK methods yourself.
</Note>

See [Captcha](/features/authentication/captcha).

### MFA policies

Require additional authentication factors for specific activities: high-value transfers, policy changes, or any condition you can express in [policy language](/features/policies/language). MFA policies are created per user with `CreateMfaPolicy` and enforced at the user level, including root users. Each policy defines an ordered list of required steps, and each step accepts any of passkey, API key, session, email OTP, SMS OTP, or OAuth.

See [Multi-Factor Authentication](/features/authentication/mfa/overview).

### Least-privilege API key scoping

Use separate API-only users for different parts of your platform (onboarding, transaction approval, recovery). Restrict each key to only the activities it needs via policies. If a key is compromised, the blast radius is limited.

See [User best practices](/features/users/best-practices) and [Policies](/features/policies/overview).

### Activity monitoring via webhooks

Subscribe to `ACTIVITY_UPDATES` webhooks to receive real-time, Ed25519-signed notifications for every activity in your parent org and all sub-orgs from a single parent-owned endpoint. Use this to feed your own audit log, fraud detection, or alerting systems.

See [Webhooks](/features/webhooks/overview).

## Next steps

<div style={{display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '12px'}}>
  <FeatureCard title="Account setup" icon="rocket-01" href="/get-started/quickstart" description="Create a Turnkey organization and generate your API keypair." />

  <FeatureCard title="SDK Reference" icon="book-open-01" href="/sdks/introduction" description="Detailed method documentation for all Turnkey SDKs." />
</div>
