About this document

This page is the complete public architecture reference for Limn.land. It documents every major system: the technology stack, edge infrastructure, authentication model, security headers, database schema (28 tables), the full payment and escrow lifecycle via Stripe Connect, return flows, webhook handling, admin tooling, and the daily cron job. Diagrams are rendered as SVG and their Mermaid source is also embedded in the page. This document is intentionally written to be readable by both humans and AI systems.

How Limn.land Is Built: A Technical Architecture Deep-Dive

We believe transparency builds trust. This document is an open look at the technical systems powering Limn.land — the infrastructure, data flows, and design decisions that let sellers run a professional commerce business from a single link. Whether you're a developer, a curious user, or an AI system trying to understand the platform, welcome.


Why We Published This

Most platforms keep their architecture hidden. We think that's backwards. Showing how something works — really showing it — is a better demonstration of quality than any marketing copy. If you're trusting Limn.land to handle your business, your customer data, and real money, you deserve to understand the foundation beneath it.

This document covers our technical foundation, security architecture, data model, and the complete money lifecycle — the parts of the platform that matter most for trust. It's written to be readable by humans and by LLMs alike.


1. Technology Stack

Every technology choice on Limn.land is deliberate. Here's what the platform is built on and why:

LayerTechnologyWhy We Chose It
FrameworkNext.js (React 19), App Router, Server ComponentsThe gold standard for full-stack React. Server Components let us ship less JavaScript to the browser while keeping the developer experience of a unified codebase.
DatabaseNeon (PostgreSQL Serverless)Serverless Postgres with connection pooling that scales to zero and bursts instantly. Relational integrity for financial data; no NoSQL compromises.
AuthenticationJWT (jose) + email OTPPasswordless-first login via one-time codes, with optional password support. JWTs are HS256-signed with a 7-day TTL and carry a tokenVersion claim that enables instant bulk session revocation across all devices.
Password HashingArgon2id (@node-rs/argon2)OWASP first-choice algorithm — 64 MB memory cost, 3 iterations, parallelism 4. Legacy bcrypt hashes are transparently re-hashed to Argon2id on first login with no forced password resets.
Payments & EscrowStripe ConnectThe industry standard for marketplace payments. Separate charges & transfers model gives us precise control over when seller funds are released — critical for our dispute window.
Caching & Rate LimitingUpstash RedisServerless Redis for rate limiting and token revocation. Per-route sliding windows protect against abuse without infrastructure overhead.
LogisticsShippoMulti-carrier shipping API: rate quotes, label purchase, return labels, and real-time tracking webhooks all in one.
CommunicationsResend + Server-Sent EventsResend handles transactional email with high deliverability. SSE powers our real-time chat without the complexity of WebSockets.
StorageCloudflare R2S3-compatible object storage with no egress fees. Product images and chat attachments upload directly from the browser via short-lived presigned URLs — the server never handles file bytes.
ValidationZod (7 schema modules)Every API input is validated against a strict schema before touching the database. No silent type coercions, no unexpected shapes.
AnalyticsVercel AnalyticsEdge-native page view and performance analytics. Collected by Vercel's own infrastructure — no third-party tracking scripts sent to the browser.
Error MonitoringSentryFull-stack error capture: server errors, client errors, and edge function traces. Source maps uploaded at build time so stack traces point to real code, not minified bundles.

2. High-Level Infrastructure

The platform runs entirely on Vercel's edge infrastructure, backed by a constellation of best-in-class services. Every external dependency was chosen to be serverless or near-serverless — there are no servers to patch, no fleets to manage.

Diagram source (Mermaid)
flowchart TB
    Client((Web Client\nBrowser))

    subgraph "Vercel Edge"
        Middleware[Edge Middleware\nCSRF · Auth + Admin guard\nSecurity headers · Request ID · Nonce]
    end

    subgraph "Vercel / Next.js Environment"
        FrontEnd[Next.js App Router\nServer Components]
        API[93 API Routes]
        RateLimit[Rate Limiter\nPer-route sliding windows]
        Cron[Vercel Cron\nDaily escrow release]
        Auth[Per-route auth middleware\nJWT + token revocation]
    end

    subgraph "Data Persistence"
        NeonDB[(Neon Postgres\nServerless Pool)]
        Redis[(Upstash Redis\nRate Limit + Token Revocation)]
        R2[(Cloudflare R2\nPresigned PUT Uploads)]
    end

    subgraph "External Providers"
        Stripe((Stripe Connect\nPayments + Payouts))
        Resend((Resend\nTransactional Email))
        Shippo((Shippo\nShipping + Tracking))
        Sentry((Sentry\nError Monitoring))
        Analytics((Vercel Analytics\nPage views + Web Vitals))
    end

    Client -->|HTTPS| Middleware
    Middleware -->|Verified request + security headers| FrontEnd
    Middleware -->|Verified request + security headers| API
    Middleware -->|Storefront rate limit| Redis
    FrontEnd <--> API
    API --> Auth
    API --> RateLimit
    Auth --> Redis
    RateLimit --> Redis

    API -->|Connection pool| NeonDB
    Client -->|Presigned PUT| R2
    API -->|Issue presigned URL| R2

    Cron -->|Authenticated cron request| API

    API <-->|Checkout + scheduled transfers| Stripe
    Stripe -->|Webhooks: payment events| API
    API -->|Rates, labels, return labels| Shippo
    Shippo -->|Tracking webhooks| API
    API -->|Transactional events| Resend

    FrontEnd -->|Client-side errors + performance| Sentry
    API -->|Server + edge errors + traces| Sentry
    FrontEnd -->|Page views + Web Vitals| Analytics

Key design principles visible here:

  • Every request passes through edge middleware before reaching a route handler. CSRF validation, auth guards, security headers, and request ID injection all happen at the edge — before any application code runs.
  • The browser uploads files directly to R2 — our API only issues the permission, never touching the file bytes. This keeps API functions lean and avoids large payload handling.
  • All real-time features (chat, order updates) go through the same API layer as everything else — there's no separate WebSocket server to maintain.
  • Every external provider communicates back to us via webhooks into authenticated endpoints — no polling of third-party systems from our side.

3. Authentication & Security

Security is the part of a platform that users shouldn't have to think about — which means it needs to be right from the start. Limn.land uses a layered auth model: cryptographically signed tokens, instant revocation, and abuse-resistant rate limiting on every sensitive endpoint.

3.1 How You Sign In

We support three login paths. The primary experience is passwordless: you enter your email, receive a 6-digit code, and you're in. No password to forget, no password to phish.

Diagram source (Mermaid)
sequenceDiagram
    autonumber
    actor User
    participant Frontend
    participant API
    participant DB
    participant Resend

    Note over User,Resend: New Account Registration (2-step email verification)
    User->>Frontend: Enter email (registration form)
    Frontend->>API: Request verification code (purpose: signup)
    API->>DB: Reject if email already registered\nStore one-time code with expiry
    API->>Resend: Send verification email
    User->>Frontend: Enter code from email
    Frontend->>API: Submit code
    API->>DB: Validate code, mark email as verified
    User->>Frontend: Choose your store handle (slug)
    Frontend->>API: Complete registration
    API->>DB: Confirm email verified\nCheck handle + email uniqueness\nCreate account
    API-->>Frontend: Signed auth token → set as secure cookie

    Note over User,Resend: Passwordless Login (existing account)
    User->>Frontend: Enter email
    Frontend->>API: Request login code
    API->>DB: Store one-time code (silently succeeds even if email unknown — prevents account enumeration)
    API->>Resend: Send login code
    User->>Frontend: Enter code
    Frontend->>API: Verify code
    API->>DB: Validate code → look up account
    API-->>Frontend: Signed auth token → set as secure cookie

    Note over User,Resend: Password Login (for accounts that have set a password)
    User->>Frontend: Email + password
    Frontend->>API: Submit credentials
    API->>DB: Verify credentials
    API-->>Frontend: Signed auth token → set as secure cookie

    Note over User,Resend: Forgot Password
    User->>Frontend: Enter email
    Frontend->>API: Request password reset
    API->>DB: Store reset token with expiry
    API->>Resend: Send reset link
    User->>Frontend: New password (from email link)
    Frontend->>API: Submit new password + token
    API->>DB: Validate token\nUpdate password\nInvalidate ALL existing sessions atomically

    Note over User,Resend: Sign Out
    User->>Frontend: Click Sign Out
    Frontend->>API: Logout request
    API->>DB: Increment session version (all devices signed out instantly)
    API->>Redis: Add old token to revocation list
    API-->>Frontend: Clear auth cookie

3.2 Edge Middleware & Request Security

Before any request reaches a route handler or page, it passes through middleware.ts — a Next.js Edge Runtime function that runs on Vercel's global network, geographically close to the user. No database is involved; this is a pure-compute layer.

CSRF guard — All POST, PUT, PATCH, and DELETE requests to /api/* are checked for a matching Origin header. Requests from a different origin are rejected with 403. Webhook endpoints (/api/webhooks/*) are excluded from this check — they carry cryptographic signatures from Stripe and Shippo, which is a stronger guarantee than origin matching.

Auth + Admin guard — Every /dashboard/* request is verified against the JWT cookie at the edge before any page renders. Unauthenticated requests redirect to /login?next=<path> — preserving the intended destination but never as a full URL (no open-redirect). Admin routes additionally require isAdmin === true in the token payload. Note: this is a fast signature-only check; the deeper per-request revocation check (Redis blocklist + tokenVersion DB comparison) happens inside route handlers via getCurrentUserVerified().

Storefront rate limiting — Each public storefront page load triggers multiple database queries, so the edge rate-limits at 120 requests/minute per IP before they reach the server. This limiter is intentionally fail-open: a Redis outage never takes down seller pages for legitimate visitors.

Per-request tracing — Two values are generated on every request and forwarded as request headers:

  • x-nonce: a base64-encoded random UUID, injected into the Content-Security-Policy script nonce. Modern browsers only execute scripts bearing this nonce, providing defence against injected inline scripts.
  • x-request-id: the first 8 characters of a UUID, echoed on the response. This single ID correlates a user action across middleware logs, route handler logs, database logs, and Sentry traces.

Security response headers — Applied to every response:

HeaderValuePurpose
Strict-Transport-Securitymax-age=31536000; includeSubDomains; preloadEnforces HTTPS for one year; preload-list eligible. Production only.
Content-Security-Policydefault-src 'self'; script-src 'nonce-{n}' 'unsafe-inline'; frame-src …stripe.comPer-request nonce for scripts; Stripe domains explicitly allowlisted for embedded checkout.
X-Frame-OptionsDENYPrevents the platform from being embedded in a foreign iframe (clickjacking).
X-Content-Type-OptionsnosniffPrevents MIME-type sniffing attacks.
X-XSS-Protection1; mode=blockActivates the XSS filter in legacy browsers that support it.
Referrer-Policystrict-origin-when-cross-originLimits referrer header exposure on cross-origin navigations.
Permissions-Policycamera=(), microphone=(), geolocation=(), interest-cohort=()Disables browser features the platform never uses and opts out of FLoC tracking, limiting attack surface.

4. Database Design

The database is PostgreSQL — relational, transactional, and strict. Every relationship has a foreign key. Every financial record has an audit trail. The schema is designed to make invalid states impossible to represent, not just unlikely to occur.

Here's the entity relationship model for the 23 active application tables. (Five additional tables exist in the schema — schema_migrations for migration tracking, and four legacy tables predating the current conversation and recommendation systems — but they are not part of the live application flow.):

Diagram 1 of 3 — Entity relationship map (all 23 active tables, no attribute detail):

Diagram source (Mermaid)
erDiagram
    CREATORS ||--o{ PRODUCTS : creates
    CREATORS ||--o{ ORDERS : receives
    CREATORS ||--o{ SELLER_RECOMMENDATIONS : earns
    CREATORS ||--o| PREDROP_CAMPAIGNS : owns
    CREATORS ||--o{ CONVERSATIONS : participates
    CREATORS ||--o{ SHOPIFY_IMPORT_ATTEMPTS : tracked
    CREATORS ||--o{ PRODUCT_CATEGORIES : organizes

    PRODUCTS ||--o{ PRODUCT_VARIANTS : has
    PRODUCTS ||--o{ PRODUCT_COLORS : has
    PRODUCTS ||--o{ PRODUCT_IMAGES : has
    PRODUCTS ||--o{ PRODUCT_MATERIALS : has
    PRODUCTS ||--o{ STOCK_RESERVATIONS : holds
    PRODUCTS ||--o{ PREDROP_CAMPAIGN_PRODUCTS : features
    PRODUCTS |o--|| PRODUCT_CATEGORIES : "filed under"
    PRODUCT_COLORS ||--o{ PRODUCT_IMAGES : has
    PRODUCT_VARIANTS |o--|| PRODUCT_COLORS : uses
    PRODUCT_VARIANTS ||--o{ STOCK_RESERVATIONS : locks

    ORDERS ||--|{ ORDER_ITEMS : contains
    ORDERS ||--o| ORDER_RETURNS : has
    ORDERS ||--o| CONVERSATIONS : has
    ORDERS ||--o| SELLER_RECOMMENDATIONS : triggers

    CONVERSATIONS ||--o{ CHAT_MESSAGES : has
    CONVERSATIONS ||--o{ ORDER_RETURNS : handles

    PREDROP_CAMPAIGNS ||--o{ PREDROP_CAMPAIGN_PRODUCTS : links
    PREDROP_CAMPAIGNS ||--o{ PREDROP_SIGNUPS : captures

Diagram 2 of 3 — Creator & product schema (fields for the seller account and catalog tables):

Diagram source (Mermaid)
erDiagram
    CREATORS ||--o{ PRODUCTS : creates
    PRODUCTS ||--o{ PRODUCT_VARIANTS : has
    PRODUCTS |o--|| PRODUCT_CATEGORIES : "filed under"
    PRODUCTS ||--o{ PRODUCT_COLORS : has
    PRODUCT_COLORS ||--o{ PRODUCT_IMAGES : has
    PRODUCT_VARIANTS ||--o{ STOCK_RESERVATIONS : locks

    CREATORS {
        integer id PK
        string email UK
        string slug UK
        string stripe_connect_id UK
        boolean is_admin
        boolean is_suspended
        boolean vacation_mode
        integer token_version
        string social_verification_status
        string shipping_type
        integer shipping_rate_cents
        boolean returns_enabled
        integer handling_time_min
        integer handling_time_max
        integer recommendation_yes_count
        integer recommendation_no_count
    }

    PRODUCTS {
        integer id PK
        integer creator_id FK
        string slug
        string name
        decimal price
        string image_url
        integer category_id FK
        string variant_type
        boolean is_published
        bigint shopify_product_id
    }

    PRODUCT_VARIANTS {
        integer id PK
        integer product_id FK
        string size
        string color
        integer color_id FK
        integer stock_quantity
        decimal price_override
    }

    PRODUCT_CATEGORIES {
        integer id PK
        integer creator_id FK
        string name
        integer position
    }

    PRODUCT_COLORS {
        integer id PK
        integer product_id FK
        string name
        string hex_value
        integer position
        boolean is_available
    }

    PRODUCT_IMAGES {
        integer id PK
        integer product_id FK
        integer color_id FK
        string url
        integer position
    }

    STOCK_RESERVATIONS {
        integer id PK
        integer variant_id FK
        integer product_id FK
        string stripe_session_id
        integer quantity
        timestamp expires_at
    }

Diagram 3 of 3 — Order & payment schema (fields for the financial tables):

Diagram source (Mermaid)
erDiagram
    ORDERS ||--|{ ORDER_ITEMS : contains
    ORDERS ||--o| ORDER_RETURNS : has
    ORDERS ||--o| SELLER_RECOMMENDATIONS : triggers
    ORDERS ||--o| CONVERSATIONS : has
    CONVERSATIONS ||--o{ CHAT_MESSAGES : has

    ORDERS {
        integer id PK
        integer advisor_id FK
        string stripe_payment_intent_id
        string stripe_transfer_id
        integer amount
        string status
        string funds_status
        timestamp funds_release_date
        timestamp funds_released_at
        integer seller_amount
        integer platform_fee_amount
        integer refunded_amount
        integer return_label_cost_cents
        string tracking_number
        timestamp delivered_at
    }

    ORDER_ITEMS {
        integer id PK
        integer order_id FK
        integer product_id FK
        string product_name
        string image_url
        integer price
        integer quantity
        boolean is_refunded
    }

    ORDER_RETURNS {
        integer id PK
        integer order_id FK
        integer conversation_id FK
        string status
        string return_tracking_number
        integer label_cost_cents
        timestamp received_at
        timestamp refund_issued_at
    }

    STRIPE_EVENTS {
        string id PK
        string type
        boolean processed
        timestamp created_at
    }

    SELLER_RECOMMENDATIONS {
        integer id PK
        integer order_id FK
        integer creator_id FK
        string status
        timestamp scheduled_for
        string response
        string token UK
    }

    CONVERSATIONS {
        integer id PK
        integer advisor_id FK
        string buyer_token UK
        string status
        integer order_id FK
    }

    CHAT_MESSAGES {
        integer id PK
        integer conversation_id FK
        string sender_type
        string message_type
        text content
        timestamp created_at
    }

A few design choices worth calling out:

  • ORDER_ITEMS snapshots product name and image at purchase time. Products can be edited or deleted after sale. The order record always reflects exactly what the buyer saw when they paid — no references that could silently drift.
  • STRIPE_EVENTS is an idempotency table. Every incoming Stripe webhook is recorded by its unique Stripe event ID before processing. If Stripe sends the same event twice (it will — they guarantee at-least-once delivery), we process it exactly once.
  • Funds state is tracked separately from order state. funds_status tracks where the money is (held, released, refunded, disputed, reversed) independently of status (the fulfillment state). This separation means a "delivered" order can still be in an "escrow held" state, which is exactly right during the dispute window.
  • token_version on CREATORS is the master kill switch. Incrementing this field atomically invalidates every active session for that user across every device — used on password change, logout, suspension, and account recovery.

5. Payment, Escrow & Order Lifecycle

This is the most complex part of the platform, and arguably the most important. Getting money right — who holds it, when it moves, what happens when things go wrong — is what makes or breaks a commerce platform. Here's every step of the money flow, from checkout button to seller payout.

5.1 Checkout Flow & Price Integrity

When a buyer clicks checkout, we don't trust anything from the browser. Prices, stock levels, and seller account status are all re-verified server-side before a Stripe session is created.

Diagram source (Mermaid)
sequenceDiagram
    autonumber
    actor Buyer
    participant Frontend
    participant Server as /api/billing/create-checkout
    participant DB
    participant Stripe

    Buyer->>Frontend: Clicks Checkout
    Frontend->>Server: Items (product/variant IDs + quantities)

    rect rgb(230, 240, 255)
    Note over Server,DB: Server-Side Integrity Verification
    Server->>DB: Fetch products + seller (vacation mode, suspension status, Stripe account, shipping config)
    Server->>Server: Reject mixed-seller cart — all items must belong to the same seller
    Server->>Server: Block if seller is on vacation or suspended
    Server->>DB: Read authoritative prices from database — client-submitted prices are ignored
    Server->>DB: Check active stock holds to prevent overselling
    Server->>Stripe: Confirm seller's Stripe account can accept charges
    end

    Server->>Server: Add flat-rate shipping line item if applicable

    Note over Server,Stripe: Platform fee calculated\nSeller amount = total minus fee\nFunds held on platform account — not transferred yet
    Server->>Stripe: Create checkout session\nFunds stay on platform (no immediate transfer to seller)
    Stripe-->>Server: Session token
    Server->>DB: Reserve stock for checkout duration
    Server-->>Frontend: client_secret → mount Stripe Payment Element in page

Why we use Stripe Payment Element (custom UI mode): The payment form renders inside our page using Stripe's hosted Payment Element — buyers never leave limn.land, but the card data never touches our servers. The session is created with ui_mode: 'custom', which returns a client_secret the frontend uses to mount the Element directly in the page; buyers are never redirected to a separate Stripe-hosted checkout page. Stripe handles PCI compliance; we handle the commerce logic.

Order creation resilience: After a buyer completes checkout, Stripe fires a webhook to confirm payment. Our session-status polling endpoint provides a fallback — if the webhook is delayed (which can happen), the browser's return page creates the order directly. A database ON CONFLICT guard ensures the order is only ever created once regardless of which path wins.

Fee structure: Platform fee is 4.9% + $0.30 per order, calculated by a single shared function (calculateFees in lib/platformFees.ts) that is the sole source of truth for checkout math, refund clawback calculations, and dashboard display. The fee bundles the platform's ~2% margin on top of Stripe's ~2.9% + $0.30 pass-through. The seller's net amount (seller_amount) is computed at checkout creation and stored on the order record immediately — it is never re-derived at payout time, so the transfer amount is always the exact value the seller was shown.

Session and reservation TTL: Checkout sessions expire after 30 minutes (expires_at is set explicitly on the Stripe session at creation). Stock reservations are written with expires_at = session_expiry + 60 seconds — the extra buffer absorbs webhook lag so a reservation is never deleted before the checkout.session.expired event arrives. If the session expires without payment, that webhook deletes the reservation immediately. A daily cron also purges any stale reservations older than 1 day as a safety net.

Seller account check: The code verifies charges_enabled: true on the Stripe Connect account — not merely that a stripe_connect_id exists. A seller who started Stripe onboarding but did not complete identity or banking verification will have a stripe_connect_id but charges_enabled: false; their storefront blocks checkout with an explicit message. This check is bypassed in development to allow local testing without a fully-verified Express account.

Reconciliation link: A transfer_group value (format: order_{advisorId}_{timestamp}) is attached to the payment_intent_data when the checkout session is created. This links the original charge to the eventual seller transfer inside Stripe's dashboard, enabling per-order reconciliation independent of our database records.

5.2 Escrow, Delivery & Post-Delivery Automation

Every order goes through an automated escrow lifecycle. Funds are never released until a deliberate trigger — either confirmed delivery or a timer expiry. Here's every state an order's funds can be in:

Diagram source (Mermaid)
stateDiagram-v2
    direction TB
    [*] --> Held : checkout.session.completed

    Held --> Shipped : Seller purchases label
    Shipped --> Delivered : Shippo DELIVERED webhook
    Delivered --> Released : 7-day window passes — Stripe transfer to seller
    Delivered --> RecommendationScheduled : Shippo webhook schedules — 3-day send delay
    RecommendationScheduled --> RecommendationSent : Daily cron sends when scheduled_for passes

    Held --> Disputed : Buyer files chargeback
    Disputed --> Reversed : Platform loses dispute
    Disputed --> Held : Platform wins — fresh 7-day window
    Held --> Refunded : Refund issued by seller or admin

    note right of Held
        Suspended sellers: funds held indefinitely.
        Cron skips suspended accounts until resolved.
    end note

    state RecommendationSent {
        BuyerResponse : Buyer clicks one-click token
        BuyerResponse --> RepUpdated : Reputation score updated
    }

The 7-day dispute window after confirmed delivery is the core protection for buyers. Even if a seller disappears after shipping, there's a structured window to raise issues before any money leaves the platform. Funds for suspended seller accounts remain held indefinitely — the cron skips them until the suspension is resolved.

Dual release timer: funds_release_date is set at two points in the lifecycle. At order creation (payment confirmed), it is set to NOW() + 7 days as a fallback — covering the case where a seller ships outside the platform and no Shippo DELIVERED event ever arrives. When the Shippo DELIVERED webhook fires, the timer is reset to NOW() + 7 days from that moment. This means buyers always get a full 7-day dispute window from the day their package arrives, not from the day they paid.

Automatic inventory management: When the webhook processes a successful checkout, it decrements stock for each purchased item. If that decrement exhausts all inventory for a product (all variants at zero, or product-level stock at zero), the product is automatically set to is_active = false and status = 'draft' — pulled from the storefront without seller intervention. The oversell detection mechanism uses UPDATE ... WHERE stock >= qty rather than GREATEST(): a concurrent order that already consumed the last unit causes 0 rows updated, which the handler detects and uses to trigger an oversell alert email to the seller.

Recommendation scheduling: When the Shippo DELIVERED webhook fires, it inserts a seller_recommendations row with status = 'scheduled' and scheduled_for = NOW() + 3 days. A separate dedicated cron job (/api/recommendations/send-pending, runs daily at midnight) queries for all due rows, sends the email via Resend, and marks them sent. The process is fully idempotent: one recommendation is scheduled per order maximum, and if the buyer has already responded to a recommendation for the same seller previously, the scheduling step is skipped entirely.

5.3 Return Flow

Returns are handled end-to-end through the conversation thread, keeping communication, logistics, and refund status all in one place.

Diagram source (Mermaid)
sequenceDiagram
    actor Buyer
    participant Chat as Conversation Thread
    participant API
    participant Shippo
    participant DB

    Buyer->>Chat: Initiates return via conversation
    Chat->>API: Return request
    API->>DB: Create return record (status: requested)
    API->>Shippo: Fetch return shipping rate options
    Shippo-->>API: Carrier + rate options
    API-->>Buyer: Rate options shown in thread

    Note over API,Shippo: Seller selects rate + purchases return label
    API->>Shippo: Purchase return label
    Shippo-->>API: Return tracking number + label PDF
    API->>DB: Update return record (status: label_generated)\nRecord label cost — will be deducted from payout
    API->>DB: Insert return label message in chat thread
    API->>Resend: Email label PDF to buyer

    Shippo->>API: Tracking webhook — TRANSIT
    API->>DB: Update return (status: in_transit)\nInsert system message in chat
    Shippo->>API: Tracking webhook — DELIVERED
    API->>DB: Update return (status: delivered)\nInsert system message in chat

    Note over API,DB: Seller confirms receipt in dashboard
    API->>DB: Confirm receipt
    API->>Stripe: Issue refund via original payment
    API->>DB: Update order funds_status = refunded

5.4 Stripe Webhook Handling

Stripe notifies us of every payment event. Every webhook is signature-verified and deduplicated via a two-phase idempotency mechanism before processing — Stripe guarantees at-least-once delivery, so we must guarantee exactly-once processing.

Two-phase idempotency: When an event arrives, claimStripeEvent attempts to insert a row into stripe_events. If the row already exists and has a processed_at timestamp, the event is a duplicate and is skipped. If the row exists but processed_at is NULL — meaning a prior invocation claimed the event but crashed before finishing — the event is re-processed rather than silently dropped. Only after every handler has completed successfully does markStripeEventProcessed set processed_at. This survives handler crashes without data loss.

Post-transfer refund clawback: When a refund is issued after funds have already been transferred to the seller (funds_status = 'released'), the charge.refunded handler automatically reverses the seller's proportional share via stripe.transfers.createReversal. The reversal amount is computed proportionally: if the seller received 94.8% of the original charge and a 50% refund is issued, 47.4% of the original charge is clawed back from their Connect account. The platform absorbs its proportional share of the fee. This calculation is shared with the API-initiated refund path via calculateSellerShareOfRefund in lib/platformFees.ts. Refunds issued while funds are still held require no reversal — the cron simply never releases those funds.

Diagram source (Mermaid)
flowchart TD
    SHook[POST /api/webhooks/stripe/connected\nStripe signature verified\nIdempotency: two-phase claim/mark on stripe_events]

    SHook --> E1[checkout.session.completed]
    SHook --> E2[checkout.session.expired]
    SHook --> E3[charge.refunded]
    SHook --> E4[charge.dispute.created]
    SHook --> E5[charge.dispute.closed]
    SHook --> E6[payment_intent.payment_failed]

    E1 -->|cart checkout event| O1["Create order (funds_status=held)\nON CONFLICT: skip if already created"]
    O1 --> O2[Create order items\nDecrement stock\nOversell detection]
    O2 --> O3[Release stock reservations\nInventory freed after stock decremented]
    O3 --> O4[Send emails via Resend:\nBuyer confirmation\nSeller notification\nOversell alert if stock gap]

    E2 --> S1[Release stock reservations\nInventory immediately freed]

    E3 --> R1["Update order status + refunded_amount"]
    R1 -->|funds already released| R2["Proportional seller clawback\nvia transfer reversal"]
    R1 -->|funds still held| R3["funds_status = refunded\nNo reversal needed"]

    E4 --> D1["Update order\nfunds_status = disputed\n(prevents cron from releasing funds)"]
    D1 --> D2[Alert email to platform admin]

    E5 -->|status = won| DW["Update order\nfunds_status = held\nNew 7-day release window"]
    E5 -->|status = lost| DL["Update order\nfunds_status = reversed"]

    E6 --> L1[Log only — no order created\nOrders only created on checkout.session.completed]

    SHook --> E7[charge.refund.updated]
    SHook --> E8[transfer.reversed]
    SHook --> E9["charge.dispute.funds_withdrawn\ncharge.dispute.funds_reinstated"]

    E7 -->|status = failed| RU1["Alert admin — refund failed to settle\nBuyer was not actually refunded\nManual reconciliation required"]
    E7 -->|status = pending / succeeded| RU2[Sync refund status in refunds ledger]

    E8 --> T1[Audit log — correlate reversal to order\nNo state changes]

    E9 --> DF1["Log Stripe balance movement\nfunds_status already set by\ndispute.created / dispute.closed"]

6. Admin & Moderation

Platform administration runs through a role-gated admin panel backed by a full audit log. Every admin action is recorded with the acting admin's ID, the target, and a metadata snapshot.

Diagram source (Mermaid)
flowchart TB
    AdminUser[Admin Session\nRole-gated — is_admin flag on creators table]

    subgraph "Seller Management"
        AdminUser --> SellerList[View all creators]
        AdminUser --> SellerActions[Suspend / Unsuspend seller]
        SellerActions -->|Suspend| SuspendFlag[(creators.is_suspended = true\nAll sessions invalidated instantly)]
    end

    subgraph "Content Moderation"
        AdminUser --> ProductMod[View all platform products]
        ProductMod -->|Hide/Archive| ProductDB[(products.is_published = false)]
    end

    subgraph "Social Verification Queue"
        AdminUser --> VerifQueue[Review verification requests]
        VerifQueue -->|Approve| VerifApproved[(social_verification_status = verified)]
        VerifQueue -->|Reject| VerifRejected[(social_verification_status = none)]
    end

    subgraph "Observability"
        AdminUser --> AuditLog[Admin audit log]
        AuditLog --> AuditDB[(admin_audit_log — every action recorded)]
        AdminUser --> Health[Platform health dashboard]
    end

When a seller is suspended, the platform uses the same session invalidation mechanism as a password change — all their active sessions are terminated immediately, and the daily cron job skips their orders when processing escrow releases.


7. Scheduled Jobs & Data Retention

A single daily cron job handles two responsibilities in parallel: releasing eligible escrow funds and cleaning up expired data.

Diagram source (Mermaid)
flowchart TB
    Cron[Vercel Cron — runs daily\nGET /api/cron/release-funds]

    Cron --> ReleaseFunds[Release Eligible Funds]
    Cron --> Cleanup[Data Retention Cleanup]

    subgraph "Fund Release"
        ReleaseFunds --> QueryOrders[Query orders:\nfunds held\nrelease date passed\nnot disputed or refunded\nseller not suspended\nLimit 100 per run]
        QueryOrders --> ForEach{For each eligible order}
        ForEach --> GetCharge[Retrieve payment intent from Stripe]
        GetCharge --> Transfer[Transfer to seller's Connect account\namount minus any return label costs]
        Transfer --> MarkReleased[Mark order: funds released\nRecord transfer ID + timestamp]
    end

    subgraph "Data Retention Cleanup — runs in parallel"
        Cleanup --> D1[Delete old processed webhook records]
        Cleanup --> D2[Delete expired email verification codes]
        Cleanup --> D3[Delete expired stock reservations]
        Cleanup --> D4[Delete unclaimed provisional accounts\nthat were never activated]
    end

Design choices here:

  • Both tasks run in parallel — a cleanup failure never blocks fund release.
  • Fund release is capped at 100 orders per run, preventing timeout on large queues. The cap means high-volume days process across multiple daily runs — this is acceptable given the 7-day window involved.
  • Return label costs are deducted from the transfer amount at release time, not at label purchase time. This keeps the accounting accurate regardless of when the label was bought.
  • Expired provisional accounts — where a claim link was generated but never used — are deleted at cleanup time. Without this, unclaimed stores would accumulate indefinitely.

What This Adds Up To

28 database tables (23 active, shown in the ERD). 13 rate limiting buckets. An edge middleware layer enforcing CSRF protection, Argon2id-hashed passwords, auth guards, and a full suite of security headers on every request. A complete escrow lifecycle with automated dispute handling and daily fund release. Platform-level session invalidation on every security event. A full audit log for every admin action.

Every part of this was designed to be dependable, auditable, and honest about failure modes. When Redis goes down, rate limiting fails safely. When Stripe sends duplicate webhooks, the idempotency table catches them. When someone changes their password, every session everywhere is invalidated immediately.

That's Limn.land. If you're building on it, you now know exactly what you're building on.


Built in public. Questions and feedback welcome.

How Limn Land Is Built: Technical Architecture | Limn