❯ E-commerce · Platform · 2025
ShopHive
A serverless multi-tenant e-commerce-as-a-service platform — sellers launch branded storefronts on subdomains with unified checkout, chat, and search.
The problem
Launching an online store means buying infrastructure, managing a domain, and building checkout — a wall of work before the first sale. ShopHive's promise is that a seller gets a branded storefront on their own subdomain, with checkout, chat, and search included, in minutes, without running a server or paying per-store infrastructure.
The approach
Go fully serverless so the per-store marginal cost trends to zero. AWS Lambda services in TypeScript + Hono sit behind API Gateway (REST + WebSocket), DynamoDB's 16 entities run on on-demand capacity, storefronts are static HTML at the edge via S3 + CloudFront, and Cognito handles auth, all wired together with SNS/SQS/EventBridge async messaging.
The storefront is a static export, not a server: a Next.js build produces HTML per store, so a storefront never waits on a cold start. A Go Lambda@Edge router maps {store}.shophive.net to the right S3 directory with no per-store DNS, at about 50ms cold start, and custom domains work via ACM with CNAME polling.
Architecture
- Lambda@Edge Router (Go)
- CloudFront CDN
- Hono Services (Lambda)
- DynamoDB (16 entities)
- Storefront Build Pipeline
- Order Escrow (Paystack)
- Semantic Search Index
- WebSocket Chat
FLOWS
- Lambda@Edge Router (Go)subdomain routeCloudFront CDN
- Storefront Build Pipelinepublish + invalidateCloudFront CDN
- Hono Services (Lambda)entitiesDynamoDB (16 entities)
- DynamoDB (16 entities)streamsSemantic Search Index
- Hono Services (Lambda)paymentsOrder Escrow (Paystack)
- Hono Services (Lambda)real-timeWebSocket Chat
- Hono Services (Lambda)triggerStorefront Build Pipeline
The storefront pipeline is event-driven: an SNS-triggered Build Lambda runs the Next.js static export with per-product generateStaticParams, uploads to S3, and invalidates CloudFront. A store update is live in under five minutes, and a failed build leaves the previous storefront serving — zero downtime on failure.
Order escrow runs a state machine on Paystack — card, bank, USSD, NUBAN payouts, subaccounts, HMAC-SHA512 webhook verification with an SQS dead-letter queue — with Flutterwave as fallback, 14-day auto-release, and dispute and refund flows. Money is integer kobo throughout, so escrow math is auditable with no floating-point drift.
Search is hybrid semantic: OpenAI text-embedding-3-small or Bedrock Titan embeddings land in an S3 vector index, synced automatically through DynamoDB Streams, and power similar-product recommendations. The commerce layer covers unified cart, per-store checkout, real-time WebSocket buyer↔seller chat with WhatsApp/SES offline notifications, discount codes, inventory, variants, digital products, and reviews.
Hardening: WAF, PCI-scope isolation (the checkout Lambda runs in a VPC with egress only to Paystack), KMS encryption, Secrets Manager, PITR backups, and rate limits at 1,000 req/s platform-wide and 100/s per IP.
Security & PCI scope
The checkout path is the only place card data is even conceivable, so it gets the strictest isolation: the checkout Lambda lives in a private VPC whose only egress is to Paystack — no route to the rest of the platform, no lateral movement from a compromised payment handler. Webhook signatures (HMAC-SHA512) and the SQS dead-letter queue make replay and loss of payment events explicit failure modes. Everything else — keys, secrets, at-rest data — is KMS-encrypted and Secrets Manager-backed, and platform rate limits are enforced at the edge before anything reaches a Lambda.
Key decisions
Static storefronts as the default
HTML at the edge is fast, cheap, and immune to API cold starts; the trade is that dynamic per-store features have to be serverless API calls, which the WebSocket chat and checkout are.
Serverless escrow with a DLQ
Payment webhooks are exactly-once-ish by design: signature verification plus a dead-letter queue plus idempotent state transitions.
The subdomain router in Go
A 50ms-cold-start Lambda@Edge beats a per-store DNS setup on both cost and latency.
Integer money
Kobo-denominated integers make the escrow math auditable across the dispute and refund lifecycle.
Event-driven everything
SNS/SQS/EventBridge decouple storefront builds, search sync, and notifications so one slow subsystem never blocks checkout.
What I'd do differently
The semantic index was added after the first keyword-search version shipped. The stream-sync pattern is clean, but the initial data backfill for existing products was a one-off script I'd rather not repeat. Subdomain routing is easy to get right; auditing every service for cross-store access is the slow part, and that would have been faster with an explicit multi-tenant isolation checklist written up front.