Sub-5ms Subscription Gating with Supabase Edge Functions: Poko Stories Engineering Teardown
How we built a serialized fiction platform with imperceptible chapter access checks, paragraph-level bookmarking, and dual Indian + international billing rails
Most subscription-content platforms gate content with database-roundtrip access checks that take 50–200ms per chapter load. Multiply by typical reader behaviour (scrolling, swiping between chapters) and the lag is felt as a sluggish reading experience. Poko Stories needed gating that was imperceptible — under 5ms per check — across thousands of concurrent readers.
This is the engineering teardown of Poko Stories — how Supabase Edge Functions hit the latency target, the paragraph-level bookmarking trick, and how we synchronised dual Razorpay + PayPal billing rails.
The Problem
Serialized fiction has a specific consumption pattern. Readers consume in 10–40 minute sessions, swipe between chapters frequently, and re-read previous content often. Friction at any of these moments breaks the immersion that makes serialized fiction work as a paid subscription.
Poko Stories' brief had three exacting constraints:
- Sub-5ms subscription access checks so chapter swipes feel instant
- Paragraph-level bookmarking so a reader closing the app mid-chapter resumes at the exact paragraph, not just the chapter start
- Dual billing across India (Razorpay) and international (PayPal) because no single gateway covers both well, and the platform's audience spans both
The Architecture
Next.js + Supabase with edge-first design:
- Next.js + TypeScript — Reader experience, hydrated client-side after server-rendered initial HTML
- Supabase Postgres — Stories, chapters, users, subscriptions, bookmarks
- Supabase Storage — Encrypted draft chapters before editorial publish; published chapters in public-cached storage
- Supabase Edge Functions — Sub-5ms access checks at the edge
- Supabase Realtime — Real-time subscription state updates after billing webhooks
- Razorpay Subscriptions — Indian readers (UPI, cards, wallets)
- PayPal Billing Agreements — International readers
- Vercel — Frontend hosting with edge caching
Key Technical Decisions
Sub-5ms Gating via Supabase Edge Functions
The naive subscription-gating flow — frontend asks backend "can this user read this chapter?", backend hits the database, returns yes/no — is 50–200ms in best case. At chapter-swipe scale, this is felt.
We moved the check to a Supabase Edge Function that runs at the geographic edge closest to the reader. The function does:
- Reads the JWT from the request (no DB call)
- Verifies the user has an active subscription for the story tier (lookup against a Redis cache replicated to the edge)
- Returns a signed access token, or 403
p50 latency: ~3ms. p99: ~8ms. Functionally imperceptible.
The trick was the Redis-cached subscription state. Instead of every check hitting Supabase Postgres, the subscription state for active users is replicated to a per-region Redis instance. Cache invalidation happens on billing webhook (within 1–2 seconds of payment success). Subscription state checks read from cache; database is only the source of truth on write.
For the broader pattern of building edge-rendered SaaS, see B2B SaaS Multi-Tenant Architecture. For the auth layer that intersects, Authentication for SaaS Startups.
Paragraph-Level Bookmarking via Intersection Observer
Standard reading apps bookmark at chapter level. Poko bookmarks at paragraph level — return readers resume at the exact paragraph they left, not just the chapter top.
The implementation uses the Intersection Observer API. Every paragraph in the chapter is observed. As the reader scrolls, the topmost-visible paragraph's index gets debounced-written to a reader_progress table (200ms debounce). On chapter open, we check reader_progress and scroll to the saved paragraph.
The debounce matters. Without it, scrolling continuously would flood the database with writes. With 200ms debounce, a typical 30-minute reading session generates ~60 writes — manageable.
Dual Billing with Webhook-Driven State Reconciliation
Billing across two gateways is operationally complex. The naive approach — store subscription state per gateway, check both on access — creates inconsistency windows where the user has paid via Razorpay but the system shows them as expired (and vice versa).
We use a unified subscriptions table that's the source of truth. Both Razorpay and PayPal webhook handlers update this single table. Each webhook is idempotent (deduplicated by event ID). State reconciliation jobs run every 6 hours to catch any drift between gateway state and our state.
The result: reader accesses are checked against one table, not two. Either gateway's events keep that table fresh.
We also handle the failure case: if either webhook fails, the reconciliation job catches it within 6 hours. For renewal failures, we explicitly retry payment up to 3 times before marking the subscription as canceled — Indian payments fail more often than international due to bank rejection patterns.
For the broader payment-gateway decision space, see AI Automation for Indian SMBs (covers Razorpay specifically) and How to write a software RFP which addresses payment integration scoping.
OLED-Optimised Dark Theme as Genuine Differentiator
Mobile reading apps are used heavily late at night, often on OLED phone screens. OLED displays render true black (each pixel literally off) with zero light emission. A "dark mode" using #1a1a1a vs true #000000 isn't dark mode — it's grey mode that drains battery and strains eyes more than necessary.
We designed the dark theme around true #000000 background, with carefully chosen text colours (off-white #f5f5f5, not pure white #ffffff to avoid harsh contrast). The result: noticeably more comfortable late-night reading, measurable battery savings on OLED devices, and a passionate response from early users that actually drove word-of-mouth growth.
Why This Matters for SaaS Builders
Poko's architecture is the template for edge-first content SaaS. The pattern:
- Push gating decisions to the edge with cached state
- Use Postgres as source of truth, Redis-at-edge as access cache
- Webhook-driven reconciliation across multiple billing rails
- Granular UX details (true black, paragraph bookmarks) that compound into noticeable feel
For SaaS builders dealing with multiple payment gateways, the dual-rail pattern with webhook reconciliation is reusable. For more on payment architecture in Indian SaaS, see SaaS Product Development.
What We'd Do Differently
- Use Cloudflare Workers + KV instead of Supabase Edge Functions + Redis for the gating layer. Latency would be similar but Workers' global KV storage is conceptually simpler than maintaining a separate Redis cache.
- Build the editorial CMS with stricter draft/publish workflows from day one. We had to add multi-stage review later.
- Add reader analytics (where readers drop off in chapters) earlier. This drives editorial decisions and we shipped it in v1.5.
Where Nexolve Fits
We build edge-first SaaS and content platforms via our SaaS & Web Apps service. For the full project context, see the Poko Stories case study.
Working on something similar?
Nexolve scopes, designs, and ships production software for startups and growing businesses. Tell us what you're building — we come back with a scoped plan within 48 hours.
Related reading
SaaS Product Development
Building Scalable Software Solutions for the Modern Enterprise
B2B SaaS Multi-Tenant Architecture: Patterns That Actually Scale
Pool, silo, and hybrid tenancy models — when each wins, what breaks at scale, and the design decisions that matter most before your first paying customer
Authentication for SaaS Startups: Auth0 vs Clerk vs Supabase Cost Analysis
A real cost comparison at 100, 1000, and 10000 users — plus the feature differences that matter and when rolling your own actually makes sense