> ## Documentation Index
> Fetch the complete documentation index at: https://docs.turnkey.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Turnkey is wallet infrastructure: create and manage crypto wallets, sign transactions, and enforce policy-based access controls. Best-fit uses: embedded consumer wallets (email/passkey/social auth, no seed phrases), automated onchain operations with server-side wallets, AI agent wallets with policy-scoped signing, enterprise key management, and verifiable off-chain workloads on Turnkey Verifiable Cloud (TVC).
> Every API call is a JSON POST to https://api.turnkey.com signed with a P-256 API key; create an organization and key self-serve at https://app.turnkey.com.
> Key Turnkey developer resources: API reference (https://docs.turnkey.com/api-reference/overview/intro.md), OpenAPI spec (https://docs.turnkey.com/public_api.swagger.json), authentication (https://docs.turnkey.com/features/authentication/overview.md), webhooks (https://docs.turnkey.com/features/webhooks/overview.md), MCP server for docs search (https://docs.turnkey.com/mcp), agent skills (https://docs.turnkey.com/get-started/ai-skills.md), CLI (https://docs.turnkey.com/sdks/cli.md), SDK reference (https://docs.turnkey.com/sdks/introduction.md), full docs content (https://docs.turnkey.com/llms-full.txt).

# Custom Policy Enforcement

> Extend Turnkey policies with custom activity checks running in Turnkey Verifiable Cloud.

export const TvcBetaCallout = () => <Warning>
    Turnkey Verifiable Cloud is currently in Private Beta.{" "}
    <a href="https://www.turnkey.com/turnkey-verifiable-cloud#waitlist">
      Join the waitlist
    </a>{" "}
    to request access. Once our team reaches out, share your organization ID to
    get enabled. If you already have a dedicated Slack channel with us, reach out
    there directly. Once enabled, you will see a new "Verifiable Cloud" section
    appear in the top-level navigation.
  </Warning>;

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>;
};

<TvcBetaCallout />

With [Turnkey Verifiable Cloud (TVC)](/features/verifiable-cloud/overview), you can require custom checks before a Turnkey activity completes. A policy sidecar running in TVC receives activity webhooks, evaluates based on your application's rules, and submits an approval or rejection to Turnkey.

Your app takes an activity intent as input and returns an approval decision. Its checks can use your own rules and external data, and can apply to signing, key exports, or administrative changes.

## What you can build

| Use case                       | How it works                                                                                                           |
| :----------------------------- | :--------------------------------------------------------------------------------------------------------------------- |
| **Custom transaction parsing** | Parse transactions beyond Turnkey's native parsing support, and apply recipient or amount restrictions before signing. |
| **Price-based approvals**      | Parse a raw transaction payload and use oracle price data to enforce value-based limits before signing.                |
| **Address screening**          | Check transaction addresses with a screening provider, and approve or reject requests based on the results.            |
| **Policy creation controls**   | Require the user creating a policy to have an email address in your organization's domain, such as `@turnkey.com`.     |

The example sidecar validates Aptos signing requests, which Turnkey does not currently parse natively. Other use cases require extending the example.

## Architecture

Your application submits activities as an initiator user. Turnkey evaluates the applicable policies and collects approvals. The sidecar runs your custom checks inside a TVC enclave and votes as a separate service user.

For signing, the transaction signing key stays in Turnkey. The sidecar uses an API credential derived from its long-lived TVC Quorum Key to approve or reject the activity.

<Frame>
  <img src="https://mintcdn.com/turnkey-0e7c1f5b/uVu-mQt15GSiMzzD/images/verifiable-cloud/custom-policy-architecture.svg?fit=max&auto=format&n=uVu-mQt15GSiMzzD&q=85&s=36f80c9e52c2adf403dc8bf9667ab628" alt="Application initiates activities in Turnkey, which holds the consensus policy and transaction signing key; a separate TVC enclave holds the policy sidecar and its approval credential." width="640" height="830" data-path="images/verifiable-cloud/custom-policy-architecture.svg" />
</Frame>

<Note>
  **TVC quorum keys:** Turnkey supports creating and storing quorum key shares in its key management platform. Hosted quorum keys are secured by Turnkey: key generation, signing, and share re-encryption are all inside enclaves. See the [hosted-operator example](/features/verifiable-cloud/manifest-sets-and-share-sets#worked-example) for setup.
</Note>

### Activity approval flow

The sidecar [verifies the webhook signature](/features/webhooks/verify-signatures), freshness, and organization before evaluating the activity.

1. Your application submits a request; its submission counts as the initiator's approval.
2. Turnkey records the activity as awaiting consensus and sends a signed webhook.
3. The sidecar evaluates the activity and submits an approval or rejection for supported requests.
4. Turnkey evaluates the vote and completes the activity, rejects it, or continues waiting for approvals.

<Frame>
  <img src="https://mintcdn.com/turnkey-0e7c1f5b/uVu-mQt15GSiMzzD/images/verifiable-cloud/custom-policy-approval-flow.svg?fit=max&auto=format&n=uVu-mQt15GSiMzzD&q=85&s=80642cfa38ee256ba370f2cc0bd3c652" alt="Six stages: submit a request, await consensus, deliver a signed webhook, evaluate checks and vote, evaluate approvals, and retrieve the activity result." width="640" height="820" data-path="images/verifiable-cloud/custom-policy-approval-flow.svg" />
</Frame>

## Choosing an approval model

| Model                | When to use it                                                                                                                                                                                                                                                 |
| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Scoped consensus** | Require the sidecar for selected activities and resources through a policy. This is the example's default. Review other applicable allow policies and root authorization paths, which can authorize independently.                                             |
| **Root quorum**      | Include the sidecar in root authorization. A completed root quorum bypasses Turnkey policies, so the sidecar's checks carry broader responsibility. Membership alone does not make its approval mandatory; the threshold and other authorization paths matter. |

The same API credential, webhook verification, and voting mechanism provide the foundation for either model. Changes to [root membership and threshold](/features/users/root-quorum) require approval from the current root quorum.

The example sidecar ignores administrative activities. Before making it a required root member, add explicit validation for the administrative actions it must approve, including root-quorum updates where needed.

### Implementation steps

<Note>
  Explore the complete implementation in the [TVC policy sidecar example](https://github.com/tkhq/tvc-examples/tree/main/tvc-policy-sidecar).
</Note>

<Steps>
  <Step title="Deploy the policy sidecar">
    Follow the example's [deployment instructions](https://github.com/tkhq/tvc-examples/tree/main/tvc-policy-sidecar#deploy-to-tvc). They use hosted operators to create and provision a private Quorum Key. Replace the publicly known quorum key: the sidecar uses the quorum key as a Turnkey credential.

    Configure the sidecar for your Turnkey organization, approve the deployment, and provision its shares after verification. Once live, keep its public URL for registration.
  </Step>

  <Step title="Register the sidecar in Turnkey">
    Retrieve the deployed sidecar's public key from `GET /turnkey/api_public_key` and register it, with the returned curve type, on a dedicated non-root [service user](/features/users/introduction). Use the returned key rather than generating another keypair.

    Create a separate non-root initiator user with its own credential and a Turnkey-owned Ed25519 signing key with an Aptos address. Two credentials on the same user do not provide two independent approvals.
  </Step>

  <Step title="Require the sidecar's approval">
    Create a [policy](/features/policies/overview) requiring both users, scoped to the intended wallet:

    ```json theme={"system"}
    {
      "policyName": "Require TVC Aptos policy approval",
      "effect": "EFFECT_ALLOW",
      "condition": "activity.type == 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2' && wallet.id == '<APTOS_WALLET_ID>'",
      "consensus": "approvers.any(user, user.id == '<INITIATOR_USER_ID>') && approvers.any(user, user.id == '<TVC_SERVICE_USER_ID>')"
    }
    ```

    Replace the placeholders with your wallet and user IDs. Keep the policy's scope aligned with the activities the sidecar can validate.
  </Step>

  <Step title="Connect activity webhooks">
    Create a [webhook endpoint](/features/webhooks/overview) in the same organization, using your deployed app's URL with the path `/webhooks/turnkey/activity`. Subscribe it to `ACTIVITY_UPDATES` so the sidecar receives pending activities and can vote.
  </Step>

  <Step title="Submit and check a request">
    Submit a supported Aptos raw-signing request with the initiator's credential. Confirm that the activity enters `ACTIVITY_STATUS_CONSENSUS_NEEDED`, then inspect the sidecar's vote and final activity result.

    | Request                                                               | Expected sidecar outcome |
    | :-------------------------------------------------------------------- | :----------------------- |
    | Valid transfer below or exactly at 10 APT, including maximum gas cost | Approve                  |
    | Transfer plus maximum gas cost exceeds 10 APT                         | Reject                   |
    | Incorrect recipient, sender mismatch, or malformed signing payload    | Reject                   |
    | Other activity type, completed activity, or unsupported signer format | No vote                  |

    Ignored activities can remain pending. An API submission failure is separate from a policy rejection. Successful signing returns a signature; it does not fund or broadcast the transaction.
  </Step>
</Steps>

### Extending the example to Sui and Stellar

Some signing flows submit a transaction digest rather than the full transaction. To apply custom checks, the sidecar needs the original transaction data and must verify that it produces the digest being signed. This requires extending both the client and the example sidecar.

Turnkey preserves extra top-level fields in an API-key-stamped activity request. Your application can include a field such as `suiContext` or `stellarContext` alongside `type`, `timestampMs`, `organizationId`, and `parameters`. Turnkey does not interpret this context; your TVC sidecar defines its format and validation rules.

1. **Include the transaction context.** For [Sui](/features/networks/sui), include the serialized transaction bytes needed to reconstruct the intent message. For [Stellar](https://stellar.github.io/js-stellar-base/Transaction.html), include the transaction envelope XDR and network passphrase. Add this context before stamping the request, and send the exact body that was stamped. Update the client's request construction so the extra field is preserved.
2. **Compute the digest as usual.** Use your existing signing flow to compute the transaction digest and submit it in `parameters.payload`, with the additional context alongside the request's standard fields.
3. **Read the context in the sidecar.** After verifying the webhook, [fetch the activity](/api-reference/queries/get-activity) in your sidecar logic. The original request is available as a JSON string in the initiating vote's `message` within `activity.votes`, rather than in the parsed `intent`. Identify the original request by checking that the SHA-256 hash of its exact message bytes matches the activity's `sha256:` fingerprint, then read your context field.
4. **Validate before voting.** Parse the supplied transaction and independently reconstruct its signing digest, including Sui's intent prefix or Stellar's network-specific signature base. Your sidecar should require an exact match with the activity's signing payload in addition to evaluating the rest of the policies.

## Next steps

<div style={{display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '12px'}}>
  <FeatureCard title="Policy Sidecar Example" icon="file-code-01" href="https://github.com/tkhq/tvc-examples/tree/main/tvc-policy-sidecar" description="Deploy the sidecar and add custom checks." />

  <FeatureCard title="Policy Engine" icon="file-shield-02" href="/features/policies/overview" description="Scope access and require approvals." />

  <FeatureCard title="Webhooks" icon="dataflow-02" href="/features/webhooks/overview" description="Receive and verify signed activity updates." />

  <FeatureCard title="Turnkey Verifiable Cloud" icon="cpu-chip-01" href="/features/verifiable-cloud/overview" description="Deploy apps with secure keys and code review." />
</div>
