Skip to main content

High-risk Operation Verification

When a user initiates a sensitive operation in your system (transfer, account deletion, password change, etc.), you can use the Palm verification feature to require the user to confirm via palm scan on the PalmxApp.

Flow Overview

┌─ Your Frontend ───────────────────────────────────────────────┐
│ │
│ User initiates dangerous operation → request your backend │
│ Wait for verification result from backend → display to user │
│ │
└───────────────────────────────────────────────────────────────┘
│ ↑
↓ │
┌─ Your Backend ────────────────────────────────────────────────┐
│ │
│ 1. Detect dangerous operation → call VerifyCreate to create │
│ a verification challenge │
│ 2. Poll VerifyQuery to check verification status │
│ 3. Status is approved → release the operation, return result │
│ to frontend │
│ │
└───────────────────────────────────────────────────────────────┘

(Palm push notification)

┌─ PalmxApp ───────────────────────────────────────────────────┐
│ │
│ User sees the verification request on their phone → │
│ palm scan to confirm / deny │
│ │
└───────────────────────────────────────────────────────────────┘

Step 1: Backend Creates a Verification Challenge

POST /palm/openai/x/oauth/verify/challenges/create
Authorization: Bearer <API_KEY>
Content-Type: application/json

{
"ChallengeId": "550e8400-e29b-41d4-a716-446655440000", // UUID you generated (idempotency key)
"OpenId": "a1b2c3d4e5f6...", // User's open_id (obtained during binding)
"Action": "withdraw", // Business action identifier
"ActionDetail": "{\"amount\":\"1000 USD\"}" // Business detail JSON (≤512 bytes, displayed to user)
}

Response:

{
"code": 0,
"data": {
"ExpiresAt": 1700000120, // Verification expiration timestamp (seconds)
"Status": "pending" // Initial status
}
}

Idempotency: Re-submitting the same ChallengeId will not create a new challenge, but will return the existing status.

Step 2: Backend Polls for Verification Results

Your frontend polls your own backend API, and your backend calls the Palm VerifyQuery API to check the status:

POST /palm/openai/x/oauth/verify/challenges/query
Authorization: Bearer <API_KEY>
Content-Type: application/json

{
"ChallengeId": "550e8400-e29b-41d4-a716-446655440000"
}

Response:

{
"code": 0,
"data": {
"Status": "approved" // pending / approved / denied / expired
}
}

Status Reference

StatusMeaningFrontend Action
pendingWaiting for user confirmationContinue polling (recommended 2s interval)
approvedUser confirmedStop polling, notify backend to proceed
deniedUser rejectedStop polling, notify user the operation was denied
expiredTimed out without responseStop polling, notify timeout, allow user to retry

Complete Backend Example (Go)

// POST /api/withdraw — Initiate withdrawal, create verification challenge
func WithdrawHandler(w http.ResponseWriter, r *http.Request) {
bizId := GetCurrentBizId(r) // Your system's business identifier (order number, user ID, etc.)
openId := db.GetPalmOpenId(bizId)

// Create verification challenge
challengeId := uuid.New().String()
body, _ := json.Marshal(map[string]string{
"ChallengeId": challengeId,
"OpenId": openId,
"Action": "withdraw",
"ActionDetail": fmt.Sprintf(`{"amount":"%s","to":"%s"}`, req.Amount, req.To),
})
httpReq, _ := http.NewRequest("POST",
"https://open.palmoa.youtu.qq.com/palm/openai/x/oauth/verify/challenges/create",
bytes.NewReader(body))
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Authorization", "Bearer "+API_KEY)
http.DefaultClient.Do(httpReq)

// Save challenge-business association, return info needed by frontend
db.SavePendingWithdraw(bizId, challengeId, req.Amount, req.To)
json.NewEncoder(w).Encode(map[string]interface{}{"success": true})
}

// POST /api/withdraw/poll — Frontend polls verification status
func WithdrawPollHandler(w http.ResponseWriter, r *http.Request) {
bizId := GetCurrentBizId(r)
challengeId := db.GetPendingChallenge(bizId)

// Call Palm VerifyQuery
body, _ := json.Marshal(map[string]string{"ChallengeId": challengeId})
httpReq, _ := http.NewRequest("POST",
"https://open.palmoa.youtu.qq.com/palm/openai/x/oauth/verify/challenges/query",
bytes.NewReader(body))
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Authorization", "Bearer "+API_KEY)

resp, _ := http.DefaultClient.Do(httpReq)
defer resp.Body.Close()

var result struct {
Data struct {
Status string `json:"Status"`
} `json:"data"`
}
json.NewDecoder(resp.Body).Decode(&result)

// approved → execute business logic
if result.Data.Status == "approved" {
executeWithdraw(bizId, challengeId)
}

// Return your own business status to frontend
json.NewEncoder(w).Encode(map[string]string{"status": result.Data.Status})
}