DEV Community

Anand Rathnas
Anand Rathnas

Posted on • Originally published at jo4.io

Why We Killed Hold Windows in Our Affiliate Marketplace

This article was originally published on Jo4 Blog.

We spent weeks building a settlement system for our affiliate marketplace. Hold windows. Clawbacks. Carry-forwards. Commission auto-approval schedulers. The works.

Then we deleted it all.

What We Built (and Why)

The idea was straightforward: when an affiliate drives a conversion, don't pay them immediately. Hold the commission for X days. If the customer refunds, claw back the commission. If there's a remainder below the payout threshold, carry it forward to next month.

Sounds reasonable, right? Every major affiliate network does something like this.

So we built:

  • Hold windows — configurable per campaign (7, 14, 30 days)
  • Clawback logic — refunds during hold period reduce the affiliate's balance
  • Carry-forwards — sub-threshold amounts roll to next settlement period
  • Auto-approval scheduler — commissions move from HELD → APPROVED after the hold window

What Went Wrong

Legal review flagged it.

The problem wasn't technical — it was regulatory. Holding affiliate funds creates obligations:

  1. Money transmission concerns — holding and releasing funds on a schedule starts to look like money transmission in some jurisdictions
  2. Dispute resolution requirements — clawbacks need a formal dispute process, not just an automatic deduction
  3. Accounting complexity — carry-forwards create accrued liabilities that need proper bookkeeping
  4. Tax reporting — when was the income earned? When the conversion happened, when the hold expired, or when the payout was made?

We're a URL shortener that added an affiliate marketplace. We're not a payment processor. Building the compliance infrastructure for hold windows was going to cost more than the feature was worth.

What We Did Instead

Deleted it. All of it.

- CommissionAutoApprovalScheduler.java (deleted)
- holdWindowDays field (removed from campaigns)
- clawbackAmount, previousCarryForward (removed from settlements)
- HELD commission status (removed)
Enter fullscreen mode Exit fullscreen mode

Replaced with:

  1. Firm offers — brands mark campaigns as non-negotiable. Publishers accept the commission as-is. No back-and-forth.
  2. Immediate settlement — conversions are confirmed by Stripe webhooks. When Stripe says the charge succeeded, the commission is earned. Period.
  3. Monthly payouts — simple monthly settlement with no holds. If there's a refund, the brand eats it (they can adjust their commission rates accordingly).

What We Added

The simplification freed up time for features that actually matter:

  • Partnership lifecycle — pause, resume, terminate partnerships with full event tracking
  • Multi-channel notifications — email, in-app, and push notifications for partnership events
  • Campaign budgets and expiry — brands set a maximum spend and end date, campaigns auto-pause when limits are hit
  • Firm offers — skip the negotiation dance when the brand knows what they want to pay

The Numbers

Metric Before After
Settlement-related DB tables 5 2
Commission statuses 6 (PENDING, HELD, APPROVED, CLAWED_BACK, PAID, FAILED) 3 (PENDING, APPROVED, PAID)
Settlement logic (lines) ~800 ~200
Legal questions Many Few

Lessons Learned

  • Legal review before building, not after — we should have asked "can we hold affiliate funds?" before writing a single line of code. Would have saved weeks.
  • Complexity is a liability — every line of settlement logic was a potential bug, a potential legal issue, and a potential support ticket. Less code = less risk.
  • Copy the leader carefully — "Amazon Associates does hold windows" doesn't mean you should. Amazon has a legal team. You have a Notion doc.
  • Simpler products attract more users — publishers don't want to learn about hold windows and carry-forwards. They want to drive traffic and get paid.
  • KISS isn't lazy, it's strategic — deleting working code feels wrong. It's not. It's the highest-ROI engineering decision you can make.

Ever deleted a feature you spent weeks building? What was the hardest "kill your darlings" moment in your product? Share below.

Building jo4.io — a URL shortener with an affiliate marketplace that pays publishers without the complexity.

Top comments (0)