Skip to main content
Portfolio wallet strategy updates and withdrawals require approval through Turnkey. The API returns a turnkeyActivityId and a status indicating whether additional approval is needed.
Braid uses Turnkey to manage signing flows, but you do not need a relationship with Turnkey to sign approvals.

When approvals happen

  • Strategy updates (PATCH /v2/portfolio-wallets/{id}/strategy): returns status: pending_approval when the activity requires approval.
  • Withdrawals (POST /v2/portfolio-wallets/{id}/withdraw): creates a withdrawal in pending_approval and returns a turnkeyActivityId.

Approval flow

  1. Call the strategy update or withdrawal endpoint.
  2. Capture the turnkeyActivityId from the response. turnkeyActivityId is your tracking id for the approval step. Store it so you can query Turnkey directly or reconcile approval events in your system.
  3. Approve the activity in Turnkey (your signing flow, quorum, or policy).
  4. Once approved, the activity is kicked off and processing continues automatically.

Turnkey approval flow in detail

Our withdrawals endpoint returns a turnkeyActivityId. In Turnkey’s API this is the activityId you’ll query, and the activity’s fingerprint is what you submit to the approval endpoint.

Customer-side approval example (Turnkey SDK)

This example uses Turnkey’s server-side SDK to fetch the activity fingerprint and submit an approval vote.
Node
import { Turnkey } from "@turnkey/sdk-server";

// Mock keypair for docs only. Replace with your real customer signer key.
const TURNKEY_API_PUBLIC_KEY = "04aa...mock_public_key...ff";
const TURNKEY_API_PRIVATE_KEY = "11bb...mock_private_key...ee";
const TURNKEY_SUB_ORGANIZATION_ID = "your_turnkey_sub_organization_id";

const turnkey = new Turnkey({
  apiBaseUrl: "https://api.turnkey.com",
  defaultOrganizationId: TURNKEY_SUB_ORGANIZATION_ID,
  apiPublicKey: TURNKEY_API_PUBLIC_KEY,
  apiPrivateKey: TURNKEY_API_PRIVATE_KEY,
});

const apiClient = turnkey.apiClient();

export async function approveTurnkeyActivity(turnkeyActivityId) {
  // 1) Fetch the activity to get its fingerprint
  const { activity } = await apiClient.getActivity({
    organizationId: TURNKEY_SUB_ORGANIZATION_ID,
    activityId: turnkeyActivityId,
  });

  const fingerprint = activity?.fingerprint;
  if (!fingerprint) {
    throw new Error("Turnkey activity fingerprint missing");
  }

  // 2) Approve it (this registers your approval vote in Turnkey)
  await apiClient.approveActivity({
    type: "ACTIVITY_TYPE_APPROVE_ACTIVITY",
    timestampMs: String(Date.now()),
    organizationId: TURNKEY_SUB_ORGANIZATION_ID,
    parameters: { fingerprint },
  });
}

// Usage:
// const { turnkeyActivityId } = braidResponse;
// if (turnkeyActivityId) await approveTurnkeyActivity(turnkeyActivityId);
If Turnkey returns activity.status: ACTIVITY_STATUS_CONSENSUS_NEEDED, additional approvals (votes) are required per your Turnkey policy/quorum. You can keep polling getActivity until the underlying activity is approved, or subscribe to Turnkey webhooks.

Status updates

Use these events to monitor progress:
  • portfolio_wallet.strategy.status_changed
  • portfolio_wallet.withdrawal.status_changed
  • portfolio_wallet.withdrawal.payout.status_changed
If an activity fails, the withdrawal or strategy update will move to failed with a failureReason.