The IB Portal Challenge
Introducing Broker (IB) portals are the revenue engine of forex brokerages. A well-built IB portal directly impacts partner acquisition, retention, and ultimately trading volume. Yet most implementations I've encountered treat IB portals as an afterthought — a basic dashboard bolted onto the CRM.
The reality is that a production IB portal needs to handle deep partner hierarchies, real-time commission visibility, flexible rebate structures, and self-service partner management. Here's how to build one that scales.
Hierarchy Architecture
The foundation of any IB portal is the partner hierarchy. A typical structure:
Master IB (Level 1)
├── Sub-IB A (Level 2)
│ ├── Sub-IB A1 (Level 3)
│ │ └── Clients
│ └── Clients
├── Sub-IB B (Level 2)
│ └── Clients
└── Direct Clients
The depth varies — I've seen hierarchies go 8 levels deep. The naive approach of recursive queries fails at scale. Use a closure table pattern:
CREATE TABLE ib_closure (
ancestor_id INT NOT NULL,
descendant_id INT NOT NULL,
depth INT NOT NULL,
PRIMARY KEY (ancestor_id, descendant_id),
INDEX idx_descendant (descendant_id, depth)
);
This lets you query any IB's entire downline — or any client's upline — in a single indexed query. When a new sub-IB registers under a parent, insert rows for every ancestor:
public function addToHierarchy(int $parentId, int $childId): void
{
// Copy all ancestor relationships from parent
DB::insert("
INSERT INTO ib_closure (ancestor_id, descendant_id, depth)
SELECT ancestor_id, ?, depth + 1
FROM ib_closure
WHERE descendant_id = ?
", [$childId, $parentId]);
// Self-referencing row
DB::insert("
INSERT INTO ib_closure (ancestor_id, descendant_id, depth)
VALUES (?, ?, 0)
", [$childId, $childId]);
}
Commission Structures
Every brokerage has its own commission model, and IBs negotiate custom rates. The system needs to support:
- Per-lot rebates — fixed amount per lot traded
- Spread markup — percentage of the spread
- Revenue share — percentage of brokerage revenue from the client
- Tiered volumes — rates that change based on monthly volume
I model this as a flexible scheme system:
class CommissionScheme
{
public function calculate(Trade $trade, IB $ib): float
{
return match($this->type) {
'per_lot' => $trade->volume * $this->rate,
'spread_markup' => $trade->spread * $this->rate / 100,
'revenue_share' => $trade->brokerRevenue * $this->rate / 100,
'tiered' => $this->calculateTiered($trade, $ib),
};
}
private function calculateTiered(Trade $trade, IB $ib): float
{
$monthlyVolume = $ib->getMonthlyVolume();
$tier = $this->tiers
->where('min_volume', '<=', $monthlyVolume)
->sortByDesc('min_volume')
->first();
return $trade->volume * $tier->rate;
}
}
The critical insight: commission schemes are per-IB, per-level. A Master IB might earn $7/lot, their sub-IB $5/lot, and the sub-sub-IB $3/lot. Each level's rate is independently configurable.
Real-Time Dashboard
IBs check their dashboards constantly. They want to see:
- Today's commissions updating in real-time
- Active clients currently trading
- New registrations under their network
- Withdrawal requests from their clients
WebSockets are essential here. When a trade executes, push the commission update to the IB's dashboard immediately:
// Client-side WebSocket listener
socket.on('commission.earned', (data) => {
updateTodayTotal(data.amount);
addToActivityFeed({
type: 'commission',
client: data.clientName,
amount: data.amount,
symbol: data.symbol,
});
});
For the backend, use Redis pub/sub to broadcast trade events to connected IB sessions. Each IB subscribes to their own channel, and the trade processor publishes to every ancestor IB's channel when a trade closes.
Self-Service Partner Management
The best IB portals let partners manage their own sub-networks:
- Generate referral links with tracking codes
- Set sub-IB commission rates (within their own margin)
- View sub-partner performance at every level
- Export reports for their own accounting
The key constraint is that an IB can only set sub-IB rates up to their own rate. If a Master IB earns $7/lot, they can offer their sub-IB up to $7/lot (though they'd earn nothing). This creates a natural margin cascade.
public function setSubIBRate(IB $subIB, float $rate): void
{
if ($rate > $this->getOwnRate()) {
throw new ExceedsMarginException(
"Rate {$rate} exceeds your rate of {$this->getOwnRate()}"
);
}
$subIB->updateCommissionRate($rate);
}
Reporting and Payouts
IB payouts happen on a schedule — daily, weekly, or monthly depending on the agreement. The reporting system needs to:
- Aggregate commissions by period, grouped by IB
- Apply minimum thresholds — many brokerages have a $50 minimum payout
- Handle currency conversion — IBs might earn in USD but withdraw in EUR
- Generate statements — PDFs with trade-by-trade breakdowns
- Integrate with payment — auto-transfer to IB wallet or bank
Batch processing runs during off-peak hours. Laravel's scheduled commands handle this cleanly:
// Run nightly at 2 AM
$schedule->command('ib:calculate-daily-payouts')
->dailyAt('02:00')
->withoutOverlapping()
->onOneServer();
Key Takeaways
- Closure tables are non-negotiable for deep IB hierarchies — recursive queries will not scale
- Flexible commission schemes that support per-lot, spread markup, and revenue share cover 95% of broker requirements
- Real-time dashboards via WebSockets keep IBs engaged and reduce support tickets
- Self-service tools for sub-partner management reduce operations overhead dramatically
- Automated payouts with proper reconciliation prevent financial discrepancies
A well-built IB portal is a competitive advantage. IBs choose brokerages partly based on the tools they provide — transparency, real-time data, and self-service capabilities win partnerships.
