Skip to content

Withdrawal Rules Manual

About This Page

What: All rules that affect withdrawal behavior — each one clearly states: what it is, why it exists, and where to change it for requirement changes Audience: Product managers who need to know "can this rule be changed, and how" Prerequisites: Withdrawal Methods OverviewReading time: 5 minutes Owner: Withdrawal Product Manager

Key Takeaway: Withdrawal rules fall into four major categories — approval templates (6 types), high-risk determination (6 factors), auto-withdrawal conditions (6 items), and three-tier limit system — each rule is annotated with its configuration location and change method.


Approval Templates

What: Based on the withdrawal source and risk determination, the system selects an approval template for each withdrawal task. The template determines how many approval steps the withdrawal requires.

Template KeyApproval StepsApplicable ScenarioTrigger Source
withdraw_defaultConfirm → RemittanceNormal withdrawalUser-initiated from App
withdraw_unusualAudit → Confirm → RemittanceNormal account abnormal withdrawalHighRiskCheck upgrade
withdraw_omConfirm → RemittanceOM (Omnibus) account withdrawalOM sub-account initiated
withdraw_om_unusualAudit → Confirm → RemittanceOM account abnormal withdrawalHighRiskCheck upgrade
fundConfirm → RemittanceCash Treasure/fund redemption withdrawalCash Treasure redemption/fund redemption callback
fund_unusualAudit → Confirm → RemittanceCash Treasure/fund redemption abnormal withdrawalHighRiskCheck upgrade

Why: Most withdrawals go through 2 steps (Confirm → Remittance). Only withdrawals flagged as abnormal (unusual) get an extra Audit step — this applies not just to OM accounts but also to normal accounts and fund redemptions.

Note: Tasks are created with default/om/fund template by default. Only after HighRiskCheck executes may they be upgraded to the corresponding unusual template. Before HighRiskCheck completes, the user sees "Processing".

If requirements change: Modifying approval templates

Code location: withdraw/src/app/Business/Task.php$stepTemplates array

Common change scenarios:

  • Add a new template (e.g., VIP users skip Confirm) → Add new template key + Step array
  • Remove OM distinction → Merge withdraw_om and withdraw_default
  • Add a new step to a template → Create new Step class (implementing IFStep interface), add to template array

Approval Steps and Permissions

What: Each withdrawal goes through up to three approval steps, each requiring different CRM permissions:

StepNamePermission CodeWhen Needed
Step 1AuditPERMISSION_CASH_TASKS_OUT_AUDITunusual template only
Step 2ConfirmPERMISSION_CASH_TASKS_OUT_CONFIRMAll withdrawals
Step 3RemittancePERMISSION_CASH_TASKS_OUT_REMITAll withdrawals

Why: Withdrawal involves funds leaving, which is higher risk than deposit. Erroneous deposits can be reversed (money is still in our account), but erroneous withdrawals are difficult to recover once remitted. Multi-layer confirmation is a safety valve.


High-Risk Determination (6 Factors)

What: After a withdrawal task is created, the queue event HighRiskCheck automatically executes 6-factor detection. A bitmask is used to calculate the risk value, with each factor occupying one bit:

FactorDetection ContentConsequence When Hit
HIGH_RISK_USERUser risk level = 3Triggers Audit
HIGH_RISK_AREAReceiving bank in high-risk region (includes PR, CN)Triggers Audit
HIGH_RISK_FREQUENCY>= 10 withdrawals at the same time pointRecord only
HIGH_RISK_AMOUNTAmount exceeds max_amount (BST only)Record only
HIGH_RISK_FRAUDULENTAnti-fraud detection failsTriggers Audit
HIGH_RISK_SWIFTSWIFT Code on risk listTriggers Audit
Bitmask technical details

The high_risk field in the task table stores using bitmask: USER=1, AREA=2, FREQUENCY=4, AMOUNT=8, FRAUDULENT=16, SWIFT=32. For example, high_risk = 3 means both USER(1) and AREA(2) were hit.

Why FREQUENCY and AMOUNT don't trigger Audit: These two factors already have independent safety mechanisms controlling them (10 transactions per day cap, three-tier limit system), so additional manual review is unnecessary.

If requirements change: Adjusting high-risk rules

Code location:

  • Risk calculation: withdraw/src/app/Business/HighRisk/HighRiskBiz.php → various checkHighRisk*() methods
  • Audit determination: withdraw/src/app/Business/Event/HighRiskCheck.phpneedAudit() method

Common change scenarios:

  • Add a new factor that triggers Audit → Add bit check in needAudit()
  • Modify high-risk country list → Adjust API interface's returned country list (updated every 5 minutes)
  • Make FREQUENCY also trigger Audit → Add HIGH_RISK_FREQUENCY check in needAudit()
  • CN (mainland) no longer considered high-risk → Remove from hardcoded country list in HighRiskBiz.php's checkHighRiskArea()

Monthly Review Process

In addition to real-time 6-factor detection, high-risk customers also undergo monthly reviews:

StepActionResponsible PersonDeadline
1Export all withdrawal tasks from last month with high_risk ≠ 0Withdrawal OpsBefore 5th of each month
2Review transaction records, focusing on suspicious patterns belowWithdrawal OpsBefore 10th of each month
3Compile suspicious records into a report, escalate to compliance teamWithdrawal OpsWithin 2 business days of discovery
4Compliance team evaluates whether to escalate to SAR (Suspicious Activity Report)Compliance TeamWithin 5 business days of receipt

Suspicious transaction pattern reference:

  • Same user with frequent large withdrawals in short period (daily average amount surges 3x or more)
  • Withdrawal destination changes frequently (switching multiple bank cards in short period)
  • Quick withdrawal after deposit (deposit-to-withdrawal interval < 24 hours and amounts are close)
  • Withdrawal destination is high-risk region (AREA factor hit) with abnormal amount

Auto-Withdrawal Conditions

What: At the Remittance step, BST channels check 6 conditions. All must pass for fully automated withdrawal; if any one fails, it degrades to manual approval:

#ConditionDescription
1Channel is auto_bsOnly BST supports full automation
2Not migration dataExcludes historical data migrated from old system
3Auto-withdrawal switch is ONauto_settings.status independently controlled per currency
4Amount within limitsAirstar HKD <= 3 million / USD <= 500K
5Sufficient balanceAvailable balance >= withdrawal amount
6No more than 10 per dayDegrades to manual when same user's daily count >= 10, prevents batch anomalies

Why: These 6 conditions are a safety valve, not a blocker. Failing ≠ withdrawal failure, just degradation to manual — ops can still execute after confirmation. This logic is consistent with the 5 auto-crediting conditions for deposits.

Condition 4 is just the first tier of the three-tier limit

Even if a single transaction passes (condition 4), when the daily cumulative reaches the third tier (daily circuit breaker), subsequent withdrawals will also degrade to manual — this is not the "10 transactions" limit of condition 6, but the amount-dimension circuit breaker. See below.


Three-Tier Limit System

What: BST channels employ a three-tier limit control, progressively tightening from single transaction to global:

TierNameFunctionBehavior When Triggered
Tier 1Single transaction cap (max)Cap on single deposit/withdrawal amountExceeding directly rejected
Tier 2Cumulative alarm (alarm)Cumulative amount reaches alarm threshold over a periodTriggers Feishu alert, does not block transaction
Tier 3Daily circuit breaker (stop)Daily cumulative amount reaches circuit breaker lineAuto-withdrawal for that day closes, requires manual handling

Airstar Limits (Known Values)

CurrencySingle Transaction CapCumulative AlarmDaily Circuit Breaker
HKD3 million40 million15 million
USD500K10 million3 million

Airstar Online Account Opening Minimum Deposit

CurrencyMinimum Deposit
HKD10,000
USD1,500
CNH10,000

CMB / CMBC Limits

CMB and CMBC three-tier limit values are maintained at runtime in the auto_settings table. Operations rules are listed here:

RuleDescription
Dual-person approval for large amountsWhen single transaction HKD >= 3 million / USD >= 500K, 2 ops staff must click "Process" to confirm
Notify accountingLarge withdrawal confirmation must notify the accounting team
Query current valuesSELECT * FROM auto_settings WHERE id IN (1, 2); (id=1 CMBC, id=2 CMB)

Airstar limit data requires confirmation

The source document (funds configuration.xlsx) shows Airstar large amount thresholds as HKD 8 million / USD 2 million / CNH 8 million, which differs from the current documented HKD 3 million / USD 500K. Possible reasons: xlsx may be product planning values, current values are live configuration. Please confirm with engineering and update.

Why: The three-tier design balances efficiency and safety — small amounts fully auto, medium amounts alert, large amounts circuit break. More flexible than a simple "reject when exceeded".


Auto-Withdrawal Configuration (auto_settings)

What: The auto_settings table maintains independent auto-withdrawal configuration for each BST bank (CMB id=2, CMBC id=1, Airstar id=3), per currency.

Each currency has 6 fields (prefix hk_ / us_ / cn_ corresponding to HKD / USD / CNH):

FieldMeaningExample Value
{prefix}statusSwitch: OPEN(1) / CLOSE(0)1
{prefix}amountCurrent auto-withdrawal balance (internal ledger)5,000,000
{prefix}max_amountSingle transaction cap (Tier 1)3,000,000
{prefix}alarm_amountBalance alarm threshold (Tier 2)40,000,000
{prefix}stop_amountBalance circuit breaker threshold (Tier 3)15,000,000
{prefix}alarm_timeLast alert time2026-04-28 10:00:00

Auto-Close Mechanism

Each auto-withdrawal execution deducts the withdrawal amount from amount. When balance falls below thresholds:

  • Below alarm_amount → Sends Feishu alert notification to ops ("Auto-withdrawal balance low")
  • Below stop_amountAutomatically closes the auto-withdrawal switch for that currency; all subsequent withdrawals degrade to manual

Important: Balance deduction happens before the actual bank transfer. Even if the bank later rejects the transfer, the balance has already been deducted. Ops must manually adjust to restore.

Operations Actions

ActionMethod
Check balanceQuery auto_settings table for the corresponding bank and currency's {prefix}amount
Top upUpdate {prefix}amount (increase amount), ensure {prefix}status = 1
Emergency shutdownSet {prefix}status to 0; all BST withdrawals for that currency immediately degrade to manual
Adjust limitsUpdate {prefix}max_amount, {prefix}alarm_amount, {prefix}stop_amount
If requirements change
  • Modify single transaction cap → Update auto_settings table's {prefix}max_amount field
  • Add JPY/SGD auto-withdrawal → Need to add new prefix mapping in withdraw/src/app/Business/AutoSetting.php

Fee Rules

What: Withdrawal fees are calculated based on bank card location and currency.

Bank Card LocationHKDUSDCNH
Hong KongFreeFreeFree
Mainland China105 Free15 FreeNot supported
United States105 Free15 Free105 Free
Other regions105 Free15 Free105 Free

Currently all free

The code defines cross-border withdrawal fees (HKD 105, USD 15, CNH 105), but the last line $fee = 0 forces all fees to 0. i.e., currently all withdrawals are fee-free.

Channel Additional Fees

Although withdrawal fees are fully waived, some channels have bank-side fees:

ChannelFeeBorne ByNotes
CHATS/RTGSUSD $8 / HKD $25 / CNY $25UserUser is prompted with popup when confirming withdrawal
FPS2.5 HKD/transactionFutuFutu bears the cost, user does not see it
Cross-border wireIntermediary bank charges (uncontrollable)UserArrived amount may be less than requested amount

Special restrictions:

  • Velo Bank: Does not support withdrawing HKD and offshore RMB to Velo accounts (USD only)
  • Mainland bank card + CNH: User-facing does not allow withdrawing offshore RMB to mainland bank cards, but CRM side is not subject to this restriction
If requirements change: Adjusting fees

Code location: withdraw/src/app/Business/CreatorBase.phpgetFee() method

  • Restore cross-border fees → Remove the trailing $fee = 0
  • Modify fee rates → Modify fee constants in switch statement
  • Add new currency fees → Add case in switch (e.g., JPY, SGD)
  • Bank-specific free promotion → Add bank check branch at the beginning of getFee()

Processing Hours

What: Auto-withdrawal does not run 24/7; it only executes during the bank's processing hours:

ChannelAuto Processing HoursOff-hours Behavior
BST (CMB)Trading days 08:40 ~ 15:59 (HKD/USD); 08:40 ~ 10:59 (CNH / margin)Queued for next trading day
BST (CMBC)Trading days 08:40 ~ 15:59 (HKD/USD); CNH does not use BST (ops selects channel)Queued for next trading day
BST (Airstar)See detailed table belowQueued for next trading day
Online banking (HSBC/Hang Seng)Business days 09:00 ~ 16:00Requires manual next-day processing
FPS24/7Around the clock (but ops approval still requires business days)

Airstar BST Detailed Service Hours

Airstar service hours are differentiated across four dimensions: margin/non-margin and full-day/half-day market:

Market TypeAccount TypeSubmission WindowAuto WithdrawalLarge Amount CriteriaExpected Arrival
Full-dayMarginTrading day 09:00~10:55AutoWithin 5 minutes
MarginTrading day 09:00~10:55Non-autoHKD > 8 million / USD > 2 millionBefore DD+1 15:55
MarginTrading day 09:00~10:55Non-auto<= large amount thresholdBefore same day 15:55
MarginOther timesAutoBefore DD+1 09:00
MarginOther timesNon-autoBefore DD+1 15:55
Non-marginTrading day 09:00~15:55AutoWithin 5 minutes
Non-marginTrading day 09:00~15:55Non-autoHKD > 8 million / USD > 2 millionBefore DD+1 15:55
Non-marginTrading day 09:00~15:55Non-auto<= large amount thresholdBefore same day 15:55
Non-marginTrading day 00:00~09:00AutoBefore same day 09:05
Non-marginOther timesAutoBefore DD+1 09:00
Half-dayMarginTrading day 09:00~10:00AutoWithin 5 minutes
MarginTrading day 09:00~10:00Non-autoHKD > 8 million / USD > 2 millionBefore DD+1 15:55
MarginTrading day 09:00~10:00Non-auto<= large amount thresholdBefore same day 12:00
Non-marginTrading day 09:00~11:55AutoWithin 5 minutes
Non-marginTrading day 09:00~11:55Non-autoHKD > 8 million / USD > 2 millionBefore DD+1 15:55
Non-marginTrading day 09:00~11:55Non-auto<= large amount thresholdBefore same day 12:00

Plain Language Explanation

  • Margin account windows are shorter — because margin withdrawal involves margin calculation and must be completed before clearing, so it cuts off earlier (full-day 10:55 vs non-margin 15:55)
  • Half-day market all deadlines are earlier — the exchange closes at noon, clearing follows (non-auto arrival deadline shrinks from 15:55 to 12:00)
  • Non-trading days do not process any Airstar withdrawals — withdrawals submitted on weekends/public holidays are queued to the next trading day
  • DD = next trading day (not calendar day); e.g., Friday submission may not process until Monday

Why: Even if all 6 auto-withdrawal conditions are met, if the submission time is outside processing hours, the withdrawal will queue. The user sees "Processing".

Trading Day ≠ Business Day

BST processing hours are based on HKEX trading days. Withdrawals on non-trading days (weekends, public holidays) queue until the next trading day.


Freeze Types

What: Freeze flags on a user's account restrict withdrawals. 4 freeze types:

CodeMeaningImpact on Withdrawal
1Currency exchange freezeFrozen amount cannot be withdrawn
2Withdrawal freezeDirectly restricts withdrawal
6Cash Treasure fund freezeFrozen amount cannot be withdrawn
7Margin withdrawal freezeMargin-related funds frozen

Anti-Duplication Mechanism

What: BST channels use triple identifiers to prevent duplicate deposits/withdrawals:

IdentifierSourcePurpose
procedure_idSBA orchestration systemmoomoo-side unique reference number
request_idDeposit/withdrawal serviceRequest-level deduplication
bank_ref_idBank returnBank-side unique reference number

The system checks the combined uniqueness of three identifiers when creating instructions. When a duplicate is detected, it refuses to create a new instruction and returns the status of the existing one.


Processing Time Reference

ChannelFastestTypicalSlowest
BSTSecondsMinutesNext trading day
Online banking (HSBC/Hang Seng)Minutes~30 minutesHours
FPS (BOC/SCB/CGB)Minutes~10 minutesHours
BOC same-bank / ICBC manualHoursHoursSame day
CHATS/RTGSHoursHoursSame day
Cross-border wire / EWB1 day2~3 days3~5 days
Cheque2 days3~5 days5+ days

Common Misconceptions

MisconceptionFact
"Auto-withdrawal conditions not met = withdrawal failed"Not a failure, just degradation to manual mode. Ops can still execute normally after confirmation
"Three-tier limits are three walls, each one blocks"No. Only Tier 1 (single transaction cap) rejects when exceeded; Tier 2 (cumulative alarm) only sends Feishu alert without blocking; Tier 3 (daily circuit breaker) closes auto-withdrawal
"High risk = withdrawal rejected"High risk only adds one extra Audit review step (template upgraded from default to unusual). After review passes, withdrawal proceeds normally
"Changing max_amount in auto_settings is all you need"When adjusting single transaction cap, you must simultaneously evaluate alarm_amount and stop_amount. If single cap is raised but circuit breaker isn't, a few large withdrawals could trigger circuit breaker and close auto-withdrawal

I want to...Go to
Understand how a withdrawal flows end-to-endWithdrawal Lifecycle
See a specific channel's execution detailsChannel Execution Manual
Push a withdrawal rule changeWithdrawal Change Guide
Look up a specific status code numberWithdrawal Data Dictionary
Was this page helpful?

内部业务文档 · 仅限 moomoo 团队使用