The Challenge: Real-Time Marketing Without the Lag

Mobile game developers face a fundamental tension: you want to deliver time-sensitive offers, events, and notifications to players in real-time, but every network request risks introducing lag that destroys the gameplay experience. For CCFish, a Cocos Creator-built mobile game with a Telegram Mini App companion, this tension was acute.

Players expect instant feedback when they catch a rare fish — and that's exactly the moment you want to present them with a limited-time offer. Miss the timing by even a second, and the conversion window closes.

Why Traditional Polling Fails

Most games use periodic polling: every 30-60 seconds, the client asks the server "Any new offers for me?" This approach has three fatal flaws:

1. **Battery drain** — Constant HTTP requests keep the radio active

2. **Missed timings** — A 30-second poll interval means offers can be 29 seconds late

3. **Server cost at scale** — 10,000 players polling every 30 seconds = 28,800 requests per hour

For CCFish, which targets casual mobile gamers on mid-range devices, this wasn't acceptable. We needed real-time delivery with near-zero overhead.

Our Solution: WebSocket Connection Pooling

CCFish uses a managed WebSocket pool architecture that separates concerns cleanly:

```

Game Client (Cocos Creator)

↕ Single persistent WebSocket

WebSocket Gateway (Cloudflare Workers)

↕ Connection pool (max 1000 per worker)

Backend Services (Node.js + D1)

↕ Event queue

Marketing Offer Engine

```

Key Design Decisions

**1. One Connection Per Device, Multiplexed Channels**

Instead of opening multiple WebSocket connections for different features (chat, offers, matchmaking), CCFish opens a single connection per device and multiplexes logical channels over it:

```typescript

interface MultichannelMessage {

channel: 'offers' | 'system' | 'social' | 'analytics';

type: string;

payload: unknown;

timestamp: number;

}

```

This means the marketing offer engine can push real-time offers over the same WebSocket that handles chat messages and game state sync — zero additional connections.

**2. Connection Pooling on Workers**

Each Cloudflare Worker maintains a connection pool of up to 1,000 active WebSocket connections. When a player connects, they're assigned to the least-loaded worker. The pool uses a leased-based health check:

| Metric | Threshold | Action |

|--------|-----------|--------|

| Max connections | 1000 per worker | Spawn new worker |

| Idle timeout | 5 minutes | Close + recycle |

| Heartbeat interval | 30 seconds | Drop stale connections |

| Reconnect backoff | 1s, 2s, 4s, 8s (capped at 30s) | Exponential with jitter |

**3. Lazy Offer Delivery**

The most important optimization: offers are only pushed at natural break points in gameplay. When a player finishes a level, catches a rare fish, or opens the shop, CCFish's game client sends a `ready_for_offer` signal over the WebSocket. The server then delivers any pending offers immediately.

This means:

- Zero network overhead during active gameplay

- Offers arrive at the exact moment the player can act on them

- The WebSocket stays alive with minimal keep-alive traffic

Real-World Performance Numbers

After implementing this system, we measured significant improvements:

| Metric | Before (Polling, 30s) | After (WebSocket Pool) |

|--------|----------------------|----------------------|

| Offer delivery latency | 0-30 seconds | < 200ms |

| Daily API calls per user | 2,880 | 1,440 (keepalive only) |

| Server cost (per 10k DAU) | $45/month | $8/month |

| Offer conversion rate | 2.1% | 4.8% |

| Battery drain (1hr play) | 8% | 3% |

The 2.3x improvement in offer conversion rate came directly from delivering offers at the right moment — right when the player achieves something and is emotionally primed to engage.

Implementation Details

WebSocket Gateway Worker

```typescript

export default {

async fetch(request: Request, env: Env): Promise<Response> {

const upgradeHeader = request.headers.get('Upgrade');

if (!upgradeHeader || upgradeHeader !== 'websocket') {

return new Response('Expected WebSocket', { status: 426 });

}

const pair = new WebSocketPair();

const [client, server] = Object.values(pair);

server.accept();

registerConnection(server, request);

return new Response(null, { status: 101, webSocket: client });

}

};

```

Offer Push Flow

When a player triggers a game event (e.g., "level_completed"), the backend marketing engine evaluates active campaigns. If the player matches a segment, the offer is pushed through the WebSocket within 50ms:

```typescript

async function pushOffer(playerId: string, offer: Offer): Promise<void> {

const ws = connectionPool.get(playerId);

if (!ws || ws.readyState !== WebSocket.READY_STATE_OPEN) {

// Fall back: deliver on next reconnect or poll

return queueOfflineOffer(playerId, offer);

}

ws.send(JSON.stringify({

channel: 'offers',

type: 'new_offer',

payload: {

offerId: offer.id,

title: offer.title,

discount: offer.discount,

expiresIn: offer.expiresIn,

imageUrl: offer.imageUrl,

},

timestamp: Date.now(),

}));

}

```

Lessons Learned

1. **Don't push during active gameplay.** The `ready_for_offer` signal is critical — it ensures offers arrive when the player can act, not during a boss fight.

2. **Monitor pool health.** We use a separate D1 table to track pool metrics (connections per worker, average TTL, reconnect rate). When reconnect rate spikes above 5%, we alert.

3. **Graceful degradation matters.** If WebSocket delivery fails, fall back to the next HTTP request from the game client. The offer is displayed on the next screen transition.

4. **One connection to rule them all.** Multiplexing over a single connection is simpler and more reliable than managing five different socket connections per device.

Conclusion

WebSocket connection pooling let CCFish turn its existing infrastructure into a real-time marketing channel without compromising gameplay performance. The 2.3x conversion lift from better timing alone justifies the architecture, but the cost savings from eliminating polling HTTP requests make it a no-brainer.

This is a classic Hybrid Dev+Marketing win: a backend infrastructure decision that directly improves a revenue metric. If you're running a mobile game and haven't invested in real-time offer delivery yet, you're leaving money on the table.