Skip to main content
Portfolio wallet withdrawals can require cryptographic approval before execution. When that happens, Ground creates a signing activity and exposes it through the Ground Portal and the Turnkey approval API.
The customer 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

  • Withdrawals (POST /v2/wallets/{id}/withdrawals): each payout leg transitions to pending_customer_approval when it’s ready for signing.
At most one customer approval can be outstanding per walletId + chain. If you create multiple withdrawals rapidly, later payouts queue behind the earlier approval automatically.

What does not require approval

Strategy updates (PATCH /v2/wallets/{id}/strategy) do not require this customer 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 payout leg needs approval, payoutLegs[].status and the external payout step state become pending_customer_approval. Poll GET /v2/wallets/:id/withdrawals/:withdrawalId or subscribe to the portfolio_wallet.withdrawal.status_changed webhook to detect when a payout enters pending_customer_approval. Then call GET /v2/turnkey/activities/pending to fetch the pending approval details and activity fingerprint for the authenticated organization.

Approving an activity

  1. Capture the pending payout leg/step from the withdrawal response or webhook.
  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 the customer signer in customer-controlled infrastructure.
  6. Submit the stamp with POST /v2/turnkey/activities/{activityId}/vote.
  7. Processing continues automatically once approved.
Do not send customer private keys to Ground or any service outside the customer’s control. Approval integrations should use the customer signer to produce a custody-provider approval stamp; raw signing credentials should never leave customer-controlled infrastructure. Example API sequence:
The approval-request response includes a turnkeyRequest object and a stampPayload string. Stamp stampPayload locally with the customer 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 a Turnkey-compatible stamper owned by the customer. For a customer-held Turnkey API key, stamp the payload from your own backend or signing service:
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 customer 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_customer_approval signal, it should verify the transaction before approving. This is critical because an approval is an irreversible cryptographic signature — once signed and broadcast, the transaction cannot be reversed. What to verify:
  1. Destination address — confirm the pending activity’s destinationAddress matches the address your system originally submitted in the withdrawal request. Reject if it doesn’t match.
  2. Amount — confirm the displayed amount is reasonable for the withdrawal and yield source. Do not assume it must exactly match the originally requested amount for asynchronous sources.
  3. Chain and token — confirm destinationChain and destinationToken match your expectations.
  4. Withdrawal correlation — match the pending approval back to a withdrawal your system actually initiated. Reject orphaned or unexpected approval requests.
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. When a payout leg or step enters pending_customer_approval, fetch GET /v2/turnkey/activities/pending, verify the pending approval details, request POST /v2/turnkey/activity-approval-request, stamp the returned stampPayload locally, and submit the vote.
  2. Polling-driven — poll GET /v2/wallets/:id/withdrawals/:withdrawalId and check payoutLegs[].status / payoutLegs[].steps[].state.
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
If approval-related execution fails, the withdrawal eventually moves to failed with a failureReason.