Skip to main content
Portfolio wallet operations can require cryptographic approval before execution. When that happens, Ground creates a signing activity and exposes it through the Ground Portal and Ground approval API.
Your organization’s configured signer must generate the Turnkey approval stamp outside Ground’s servers. Ground receives the stamp and validates it against the pending activity before forwarding the vote to Turnkey.

When approvals happen

Set up signing before you need to withdraw. In production, a request that would take your organization’s cumulative withdrawals above 200returns‘withdrawalsigningrequired‘untilanorganizationownerenablessigningandenrollsasigner.Exactly200 returns `withdrawal_signing_required` until an organization owner enables signing and enrolls a signer. Exactly 200 is allowed without signing. The total includes pending withdrawals across all organization wallets; cancelled and failed withdrawals do not count. Set up signing in Ground Portal → Settings → Signing. Organizations with limited self-serve access have a separate $100 cumulative production withdrawal limit.
  • Withdrawals (POST /v2/wallets/{id}/withdrawals): when signing is enabled, each payout leg transitions to pending_customer_approval when it’s ready for signing.
  • Stellar deposit setup (POST /v2/wallets/{id}/deposit-addresses/{chain}/enable): Ground creates a stellar_trustline activity that authorizes the wallet to receive Circle USDC.
At most one approval can be outstanding per walletId + chain. If multiple activities become ready on the same chain, later activities queue behind the earlier approval automatically.

What does not require approval

Strategy updates (PATCH /v2/wallets/{id}/strategy) do not require this organization approval flow. They are API-authenticated requests; later rebalances may still be executed by Ground under the wallet’s configured policy.

Detecting a pending approval

When a withdrawal payout needs approval, payoutLegs[].status and the external payout step state become pending_customer_approval. Poll GET /v2/wallets/:id/withdrawals/:withdrawalId or subscribe to portfolio_wallet.withdrawal.status_changed to detect this state. For Stellar deposit setup, subscribe to portfolio_wallet.approval.status_changed. A pending event includes the activityId, activityKind, chain, and validity window. After receiving either signal, call GET /v2/turnkey/activities/pending to fetch the approval details and activity fingerprint for the authenticated organization.

Approving an activity

  1. Capture the pending activity from the relevant webhook, withdrawal response, or withdrawal status response.
  2. Fetch the matching activity from GET /v2/turnkey/activities/pending.
  3. Verify the transaction details (see best practice below).
  4. Request the Turnkey approval or rejection payload from POST /v2/turnkey/activity-approval-request.
  5. Stamp stampPayload with your organization signer in infrastructure you control.
  6. Submit the stamp with POST /v2/turnkey/activities/{activityId}/vote.
  7. Processing continues automatically once approved.
Do not send signing keys to Ground. Approval integrations should produce the custody-provider approval stamp inside infrastructure you control. Example API sequence:
The approval-request response includes a turnkeyRequest object and a stampPayload string. Stamp stampPayload locally with the organization signer, then submit the resulting Turnkey stamp:

Stamping the approval payload

Ground does not stamp the Turnkey approval for you. Your integration must create customerApprovalStamp by passing the exact stampPayload string to your organization’s Turnkey-compatible stamper. The API field retains the name customerApprovalStamp. For a machine signer, stamp the payload from your own backend or signing service: Enable signing and register a machine signing key at Ground Portal → Settings → Signing. See dashboard and signer setup for enrollment and the exact controls. Ground API keys authenticate API requests. Approval signing keys authorize activities and must be stored separately. The Portal displays a compressed public key as hex and exports the private key as PKCS#8 PEM. ApiKeyStamper expects the private scalar as hex. If using the Portal export, convert it locally in your Node.js signing service when loading the secret:
The example below uses apiPrivateKey from this conversion. If your secret is already the hexadecimal private scalar, set apiPrivateKey from TURNKEY_API_PRIVATE_KEY instead. TURNKEY_API_PUBLIC_KEY is the matching public hex value from the Portal. Never log either private-key representation.
The stamp returned by Turnkey has this shape:
For a passkey signer, run the stamping step in the browser with Turnkey’s WebAuthn stamper and submit the returned stamp to your backend before calling the vote endpoint:
Never send TURNKEY_API_PRIVATE_KEY, raw passkey material, or any other signing secret to Ground. Ground only needs the customerApprovalStamp object, the original turnkeyRequest, and the intended action.

Verify before you approve

Never blindly approve a Turnkey activity. Always verify the transaction details match what you expect before signing.
When your system receives a pending approval, verify the activity before signing. Once an approved transaction is broadcast, it cannot be reversed.

Withdrawal payouts

  1. Destination address — confirm the pending activity’s destinationAddress matches the address your system originally submitted in the withdrawal request. Reject it if the address does not match.
  2. Amount — confirm the displayed amount is reasonable for the withdrawal and yield source. Asynchronous sources may not exactly match the originally requested amount.
  3. Chain and token — confirm destinationChain and destinationToken match your request, including the exact sandbox network. Validate the destination using the network’s address format.
  4. Withdrawal correlation — match the pending approval to a withdrawal your system initiated. Reject orphaned, expired, or unexpected approvals.

Stellar deposit setup

For a stellar_trustline activity, confirm the wallet ID, activity ID, activity kind, stellar or stellar_testnet chain, and activity fingerprint match a deposit setup your system initiated. This activity has no withdrawal destination or payout amount. Reject orphaned, expired, or unexpected approvals. Example verification flow:
This pattern protects against compromised intermediaries, replay attacks, and bugs that could cause funds to be sent to unintended destinations. Treat every approval request as untrusted input until your system has independently verified it.

Automation patterns

Two patterns work well for handling approvals:
  1. Webhook-driven — subscribe to portfolio_wallet.withdrawal.status_changed for withdrawal payouts and portfolio_wallet.approval.status_changed for Stellar setup. When an approval becomes pending, fetch GET /v2/turnkey/activities/pending, verify the details, request POST /v2/turnkey/activity-approval-request, stamp the returned stampPayload locally, and submit the vote.
  2. Polling-driven — for withdrawals, poll GET /v2/wallets/:id/withdrawals/:withdrawalId and check payoutLegs[].status and payoutLegs[].steps[].state. Poll GET /v2/turnkey/activities/pending as the approval backstop.
Recommended: use webhooks as the primary trigger, with polling as a backstop (webhooks are best-effort delivery). See Webhooks for how to register and verify webhook deliveries.

Status updates

Subscribe to these events to monitor progress:
  • portfolio_wallet.withdrawal.status_changed
  • portfolio_wallet.withdrawal.payout.status_changed
  • portfolio_wallet.approval.status_changed
If approval-related execution fails, the withdrawal eventually moves to failed with a failureReason. Approval authorizes signing. Continue tracking each payout and its withdrawal until completed; see Withdraw Funds.