CCFish leverages a Cloudflare Workers-based dynamic pricing engine to serve time-limited flash sales, daily deals, and personalized discount offers that convert free players into paying users at 3-4x the rate of standard in-app purchase (IAP) offers. By combining serverless compute with distributed data stores, the engine delivers urgency-driven pricing without the operational overhead of traditional backend infrastructure.
The Problem
Mobile game IAP conversion follows a steep curve. Most free-to-play players never make a first purchase. Static pricing — a fixed gem pack or coin bundle sitting in the shop — lacks the psychological triggers needed to overcome hesitation. Players browse, compare value-per-dollar in their heads, and close the shop. The friction is inertia.
CCFish faced a specific challenge: how do you get a casual fishing game player — who’s perfectly happy grinding for virtual bait — to pull out their wallet? The answer was urgency. But engineering urgency at scale, with personalized offers that don’t feel spammy, requires a pricing engine that can compute, cache, and serve thousands of unique offers per minute without a dedicated backend team managing servers.
The Solution
CCFish built a serverless flash sale engine on Cloudflare Workers. The architecture uses Workers for request handling and business logic, D1 as the SQLite-based relational store for offer rules and player segmentation, KV for rate limiting and cooldown tracking, and Cron Triggers for automated offer scheduling. The result is a zero-maintenance pricing layer that runs at the edge, close to players worldwide, with sub-50ms latency on offer resolution.
When a player opens the CCFish shop, the client calls a Worker endpoint that evaluates the player’s cohort, checks active flash sales, applies any personalized discount multipliers, and returns a curated offer — all in a single request that costs fractions of a cent to execute.
Architecture Overview
The engine is organized into four functional layers:
**Offer Scheduling (Cron Triggers):** Cloudflare Cron Triggers fire every 15 minutes and evaluate a schedule table in D1. The schedule defines flash sale windows, daily deal rotations, and seasonal events. When a sale window opens, the trigger writes active offer rules into a dedicated KV namespace for fast lookup, effectively warming the cache before players hit the shop endpoint.
**Offer Resolution (Workers + D1):** The primary shop endpoint is a Worker that accepts a player ID and session token. It queries D1 for the player’s segmentation cohort (whale, mid-spender, free player, churn-risk), fetches active offers from KV, and computes the final price by applying cohort-specific discount rules. D1 stores the rule definitions; KV holds the hot cache of currently-active offers.
**Rate Limiting & Cooldowns (KV):** Flash sales rely on scarcity, but they also risk overwhelming the store. KV handles atomic counters for per-player rate limits (max 3 flash sale purchases per day) and cooldown timers (a player who just bought a starter pack won’t see the same offer for 72 hours). KV’s global read performance keeps these checks fast.
**Analytics Pipeline (D1 + Logpush):** Each offer impression, click, and conversion writes to an analytics table in D1. Logpush streams this data to the team’s data warehouse for A/B test analysis and cohort performance monitoring.
Implementation
Here’s a simplified version of the core offer resolution logic running on Workers:
```javascript
// Offer resolution endpoint — called when player opens shop
async function resolveOffer(request, env) {
const { playerId, sessionId } = await request.json();
// 1. Get player segment from D1
const segment = await env.DB.prepare(
"SELECT cohort, total_spend, last_purchase_date FROM players WHERE id = ?",
[playerId]
).first();
// 2. Check flash sale eligibility
const cooldownKey = `cooldown:${playerId}:flash_sale`;
const cooldownActive = await env.KV.get(cooldownKey);
if (cooldownActive) {
return new Response(JSON.stringify({
offer: null,
message: "Flash sale not available — check back later",
cooldown_remaining: parseInt(cooldownActive)
}));
}
// 3. Get active flash sale offers from KV (pre-warmed by Cron Trigger)
const activeOffers = await env.KV.get("active_flash_sales", "json");
// 4. Match player segment to offer type
const matchedOffer = matchOfferToSegment(activeOffers, segment);
// 5. Apply personalized discount multiplier
const finalPrice = applyDynamicPricing(matchedOffer, segment);
// 6. Log impression for analytics
await env.DB.prepare(
"INSERT INTO offer_impressions (player_id, offer_id, cohort, price_shown, timestamp) VALUES (?, ?, ?, ?, ?)",
[playerId, matchedOffer.id, segment.cohort, finalPrice, Date.now()]
).run();
return new Response(JSON.stringify({
offer: {
...matchedOffer,
price: finalPrice,
expires_at: matchedOffer.window_end
},
segment: segment.cohort
}));
}
function matchOfferToSegment(offers, segment) {
// Whales see premium bundles with high anchor prices
// Free players see low-friction starter packs
// Churn-risk players see re-engagement offers
const segmentMap = {
'whale': 'premium_bundle',
'mid_spender': 'value_pack',
'free_player': 'starter_pack',
'churn_risk': 'reengagement_offer'
};
const offerType = segmentMap[segment.cohort] || 'generic';
return offers.find(o => o.type === offerType) || offers[0];
}
function applyDynamicPricing(offer, segment) {
// Base price adjusted by segment multiplier + engagement score
const multipliers = {
'whale': 1.0, // full price, premium anchor
'mid_spender': 0.8, // 20% off
'free_player': 0.5, // 50% off starter pack
'churn_risk': 0.4 // 60% off re-engagement
};
const basePrice = offer.basePrice;
return Math.round(basePrice * (multipliers[segment.cohort] || 1.0) * 100) / 100;
}
```
**A/B Testing Framework:** Every offer type supports A/B testing by segment. The Worker randomly assigns players to control (standard price) or variant (flash sale price) within each cohort. Results are logged to D1 analytics tables and surfaced through a simple dashboard. The Cron Trigger auto-promotes variants that show statistically significant lift at 95% confidence after 500 conversions per cell.
**Cron Trigger — Offer Scheduler:**
```javascript
// Runs every 15 minutes to refresh active sales
async function scheduledOfferRefresh(controller, env) {
const now = Date.now();
// Fetch all currently-active scheduled sales from D1
const activeSales = await env.DB.prepare(
"SELECT * FROM sale_schedule WHERE start_time <= ? AND end_time > ?",
[now, now]
).all();
// Write active offers to KV for ultra-fast shop resolution
for (const sale of activeSales.results) {
const offers = await env.DB.prepare(
"SELECT * FROM offers WHERE sale_id = ?",
[sale.id]
).all();
await env.KV.put("active_flash_sales", JSON.stringify(offers.results), {
expirationTtl: Math.min(900, Math.ceil((sale.end_time - now) / 1000))
});
}
}
```
Results
After deploying the flash sale engine, CCFish’s team ran a four-week A/B test comparing dynamic flash sale pricing against their baseline static shop. The results were definitive:
- **Flash sale conversion rates were 3.4x higher** than baseline IAP offers across all player segments.
- **Free player first-purchase rate** increased from 1.8% to 6.2% when shown a time-limited starter pack at 50% off.
- **Whale average revenue per user (ARPU)** increased 22% through premium anchor bundles with exclusive limited-time legendary fishing rods.
- **Churn-risk re-engagement offers** recovered 11% of players who had been inactive for 14+ days.
- **Infrastructure cost** averaged $47/month for the entire Workers + D1 + KV stack, serving 2.3 million offer resolutions daily.
The urgency mechanism mattered: offers with a visible countdown timer ("ends in 4h 23m") converted 2.1x better than offers showing only a date. Transparent timers with live countdowns outperformed vague "limited time" messaging, confirming that players respond to genuine scarcity, not dark patterns.
Key Takeaways
1. **Serverless is a natural fit for dynamic pricing.** Low latency, global edge distribution, and pay-per-request economics make Workers + D1 an ideal stack for compute-heavy personalization that would be cost-prohibitive with traditional servers at scale.
2. **Segment-aware pricing beats one-size-fits-all.** Whales, free players, and churn-risk users respond to fundamentally different offers. A single flash sale with a flat discount leaves money on the table — or worse, cannibalizes whale revenue.
3. **Urgency must be real to be effective.** CCFish found that transparent countdown timers with server-enforced expiry outperformed vague urgency signals. Players spotted fake urgency and punished it with lower trust and conversion.
4. **KV + D1 is a winning hot-cold data pattern.** Using D1 as the source of truth for rules and KV as the hot cache for active offers keeps shop resolution fast while maintaining a flexible scheduling system that non-technical product managers can update through the D1-backed admin panel.
5. **Ethical urgency wins long-term.** CCFish avoids dark patterns: no misleading countdowns that reset when the page refreshes, no fake stock counters, no opt-out buried in settings. Every flash sale is a real, time-bound opportunity. Players trust the store, and that trust translates to sustained conversion lift over repeated sale cycles.