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.
Sign approvals with your registered passkey or machine key. Ground receives the signature, never your private signing key.

When approvals happen

Set up signing before you need to withdraw. In production, a request that would take your organization’s cumulative withdrawals above $200 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.
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. Once the required chain policy is in place, Stellar setup and cross-chain allocations happen automatically without per-transaction customer approval. Enabling a new chain policy for an organization that controls its signing policy requires a separate approval in the portal. External payouts continue through the customer approval flow below.

Approving a signing policy change

For an existing organization, Ground can remove its own policy-editing permissions using its existing authority. That one-time removal does not require your signature or change your root signers. After removal, policy changes require your configured root quorum through the flow below. Ground may ask your organization to approve a policy change when enabling a new chain or a new transaction route. A policy approval is separate from approving an individual withdrawal. It is effective only after your root quorum approves it and the change completes.
  1. In the Portal Approvals tab, select the policy change row in Pending Activities to open its details in the side drawer. Review the current policy and proposed policy for each step. The Portal checks the live policy and your signer eligibility before offering the approval action for the current step. Technical verification fingerprints are available under Technical verification details inside the exact policy change.
  2. Each configured root signer opens the same proposal, reviews the full request, and selects Begin signing. The drawer guides them through each remaining signature, naming the policy being signed and marking applied changes complete. The drawer shows who has approved and how many more approvals are needed. Your first signature counts toward quorum; each root user counts once. A machine root signer can use the activity-approval API with the displayed activity ID. Those votes also appear in the proposal details.
  3. Sign each change when prompted. If more root approvals are required, the drawer waits for quorum before advancing. You can close it and return to continue from the current change. After the proposal is applied, it appears as a Completed row in Completed Activities. Select the row to open a side pane with its explanation, signer votes and exact policy changes. Then poll the wallet or feature status again.
Automatic Stellar trustline and destination-binding setup does not appear in completed approvals. These operations run as part of portfolio wallet setup. Customer-approved Stellar withdrawals remain in approval history. If a policy change is missing or rejected, the new feature remains pending; contact Ground for a new proposal. A rejected proposal stays in completed history; a new attempt requires fresh approvals. Existing transactions covered by your current policy continue under that policy. If the Portal cannot load policy changes, select Retry. Approval controls return after the policy checks succeed. Contact Ground if the error persists. Ground also checks the installed policy after the change completes. If that check fails, the feature stays pending and Ground is alerted to review it.

Exporting your signing policies

If signing policy export is enabled for your organization, open Settings → Signing → Signing policy export and select Export signing policies. The JSON download contains a timestamp, your organization and signing-account IDs, the current root quorum, and the current policy names, IDs, effects, conditions, consensus rules and notes. Keep this snapshot with your organization’s signing records; it contains no private keys. When export is being enabled through a policy proposal, the export control appears after every step is approved and verified. Partial approvals and rejected proposals leave it unavailable. If an export fails, retry; contact Ground if the failure persists.

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. After detecting a pending payout, call GET /v2/turnkey/activities/pending to fetch the approval details and activity fingerprint for the authenticated organization. Match payout approvals with activityKind: "external_payout" and the expected withdrawalId.

Approving an activity

  1. Fetch the matching activity from GET /v2/turnkey/activities/pending.
  2. Verify its transaction details.
  3. Prepare the approval with POST /v2/turnkey/activity-approval-request.
  4. Sign the returned signingRequest with your registered signing key.
  5. Submit customerApprovalStamp to POST /v2/turnkey/activities/{activityId}/vote.
Processing continues once the required number of customers approve. Your passkey and machine keys count as the same customer. Enable signing and register a machine key in Settings → Signing. Your API key authenticates requests; your signing key authorizes activities. Keep them separate and never send your private signing key to Ground. Fetch the activity from GET /v2/turnkey/activities/pending, verify its amount, recipient, chain, and token, then use its activityId unchanged. The example below prepares and submits a vote using a registered P-256 machine key. Set BASE_URL, GROUND_API_KEY, ACTIVITY_ID, and SIGNING_KEY_PATH (the exported PEM file). ACTION defaults to approve; use reject to reject the activity.
Version 1 uses P-256 with SHA-256, a base64 payload, and a base64 DER signature. Sign the decoded bytes unchanged. Do not serialize the JSON again or pre-hash it before calling sign("sha256", ...). Return context unchanged. Prepare again after five minutes; existing votes remain on the same activity. For passkeys, the Portal handles signing. Custom browser integrations use the returned signingRequest.webauthn.challenge unchanged with user verification required. Submit version, context, and a webauthn object containing credentialId, authenticatorData, clientDataJSON, and signature, each encoded as base64url, in customerApprovalStamp. Votes return HTTP 202 while pending or processing and 200 when terminal. approvalProgress counts distinct customers; a person’s passkey and machine key count as one vote. Partially approved activities stay pending. currentUserApproved: true means your vote is recorded. The pending list returns approvalStatus: null and canApprove: false when status cannot be verified. A null progress count means the count is unknown, not zero. Prepare rejections too. When signingRequest is null, submit only { action: "reject" }; otherwise sign normally. A submitting transaction cannot be cancelled. Refresh the activity list after a 409; a replacement payout needs fresh approvals. If preparation returns 503, retry when status is available. Approval completion is separate from payout settlement—monitor the withdrawal until it completes.

Verify before you approve

Never blindly approve an 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.
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. When an approval becomes pending, fetch GET /v2/turnkey/activities/pending, verify the details, request POST /v2/turnkey/activity-approval-request, sign the returned signingRequest 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.