Work/03EVÖQ/Forge

One platform for the entire creative transaction — from discovery to payout.

A dual-sided marketplace where creative professionals manage their entire business — and clients find exactly who they need.

98%

of every booking routed straight to the creator

evoq.zone ↗
EVÖQ creative marketplace platform screenshot
Client
EVÖQ
Industry
Creative Services / Marketplace
Region
United Kingdom
Stack
Next.jsReactTypeScriptSupabaseStripe ConnectVercel

The Challenge

Finding the right creative professional is still a fragmented process. Clients search across Instagram, word-of-mouth, and outdated directories. Creators lose time managing bookings through DMs, chasing invoices, and trying to stand out on platforms designed for commodity gig work. EVÖQ set out to solve both sides at once.

The platform needed to:

  • Support a dual-sided marketplace with distinct creator and client experiences
  • Handle service bookings with full lifecycle management — from enquiry to completion
  • Process payments securely with creator payouts via Stripe Connect
  • Provide a separate marketplace for physical creative spaces

Generic marketplace builders couldn't handle the complexity. Freelancer platforms like Fiverr or Upwork are built around deliverable-based gig work, not service-based creative bookings with date/time scheduling, physical space rentals, and portfolio-first discovery. A custom build was the only way to get the experience right.

The Solution

01

Dual-Sided Marketplace Architecture

Marketplace data leaks kill trust. When a creator can see another creator's earnings, or a client's conversation leaks to the wrong user, the platform is dead. The challenge isn't building two interfaces — it's ensuring they share a transactional layer (bookings, payments, messaging) without leaking data across role boundaries.

This is enforced at the database level with row-level security policies scoped per role. Creators can only see their own bookings and earnings. Clients can only access their own orders and conversations. Admin operations use a separate service role with elevated permissions, verified on every request. Even a compromised API layer can't leak data across users.

The platform also maintains a deliberate separation between auth provider IDs and application user IDs via a mapping column. Messaging, bookings, and payments all reference the application user ID — not the auth provider's UID. This means the entire authentication provider could be swapped out without migrating a single row in the core business tables.

Both sides share a unified booking engine, real-time messaging, and notification layer — built once, with access controlled by RLS rather than duplicated application logic.

Even a compromised API can't leak data across users.

Two-sided marketplace
Creators
Portfolio Services Earnings Bookings
Shared layer
Messaging & notifications
Messaging Notifications
Clients
Discovery Booking Payments Reviews
Distinct experiences sharing a unified booking, messaging, and notification layer.
02

Unified Booking System

A broken booking means a creator doesn't get paid and a client doesn't get the service they need. The system handles two fundamentally different booking types — services and spaces — through a unified architecture, because splitting them would mean splitting every downstream system too.

Service bookings follow a structured lifecycle:

  1. 01

    Request

    The client submits a booking request with date, time, and details.

  2. 02

    Review

    The creator receives the request and can accept or reject it from their dashboard.

  3. 03

    Payment

    On acceptance, the client is prompted to pay via Stripe.

  4. 04

    Completion

    After the service is delivered, the booking is marked complete and the client can leave a review.

  5. 05

    Dispute

    If something goes wrong, either party can raise a dispute, triggering multi-channel admin notification with graceful degradation — if the email fails, the dispute is still created and the in-app notification still fires.

Space bookings support two distinct pricing models — hourly and daily — enforced at the database level with PostgreSQL CHECK constraints. If a booking is hourly, start and end times are required; if daily, a daily rate and date range are required. Duration fields are generated columns, computed automatically from the date range. Invalid booking states can't exist in the database, regardless of what the application code does.

Reviews are locked to completed bookings — you can only review a creator after a real, paid transaction. This eliminates fake reviews, competitor sabotage, and reputation gaming at the database level.

Invalid booking states can't exist in the database.

Booking lifecycle
Happy pathRejectDispute
01
Request
Client submits booking
02
Accept
Creator reviews
03
Payment
Stripe processes
04
Complete
Service delivered
05
Review
Client rates creator
Reject
or
Dispute
— at any stage after acceptance
03

Stripe Connect Payment Platform

If creators don't get paid quickly and transparently, they leave. Handling money in a marketplace is fundamentally different from processing a simple checkout — the platform doesn't just collect payments, it splits them, routing funds to creators while retaining a platform commission.

This is built on Stripe Connect with a managed account model:

  1. 01

    Creator onboarding

    Creators connect their Stripe account through an OAuth flow directly from their dashboard. The platform handles identity verification and payout setup without the creator needing to manage Stripe independently.

  2. 02

    Payment intent creation

    When a booking is confirmed, a Stripe payment intent is created with the platform's 2% fee built in. The remaining 98% is routed directly to the creator's connected account.

  3. 03

    Webhook-driven state management

    Payment events (success, failure, refund) are processed through Stripe webhooks, keeping booking state in sync with payment state at all times.

  4. 04

    Full audit schema

    Payment intents tracked across eight statuses, a transactions table recording completed payments with fee breakdowns, and a separate refunds table for refund history — giving both the platform and creators complete financial visibility.

Creators get paid directly — the platform never holds their funds.

Stripe Connect payout
Client
Initiates payment
Stripe
Processes transaction
Fee Split
Automatic routing
Paid out
Creator
98% direct
Platform
2% commission
Creators get paid directly by Stripe — the platform never holds their funds.
04

Full-Text Search & Discovery

Discovery is the lifeblood of a marketplace. If clients can't find the right creator quickly, nothing else matters.

The search system runs entirely at the database level using PostgreSQL's native full-text search. A trigger builds search indexes dynamically whenever a service is created or updated, combining core service fields, creator information, and the full category hierarchy — resolved via a recursive query that walks from the service's category up through every parent category.

This means a search for “video” will surface services categorised under “music video,” “corporate video,” or any other subcategory in the hierarchy — without the application needing to know the category tree at query time.

Search results are ranked using PostgreSQL's built-in relevance scoring. On top of this, a dedicated search suggestions function powers the search bar's autocomplete with three-tiered results: Categories → Services → Creators, each with independent relevance scoring and dynamic result limits. The entire search pipeline — indexing, ranking, and suggestions — runs in the database, not in application code.

Search pipeline
Input
User searches “video”
Autocomplete triggers as they type
In the database
Full-Text Index
Title, description, tools
Category Hierarchy
Recursive parent resolution
Relevance Ranking
Built-in scoring
Three-tier suggestions
Categories
Services
Creators
Entire search pipeline — indexing, hierarchy resolution, ranking, and suggestions — runs in the database.
05

Real-Time Messaging & Notifications

A booking request that sits unseen for hours is a booking that never happens. In a marketplace where bookings require back-and-forth between creators and clients, delayed notifications mean lost revenue. The platform uses Supabase's realtime channel subscriptions to push notifications instantly via PostgreSQL's change data capture — no polling, no external pub/sub service.

When a row is inserted into the notifications table — a new booking request, a payment confirmation, an accepted booking — the client receives it immediately through an open WebSocket connection. Notification types carry structured metadata (booking ID, amount, checkout URL) so the frontend can render contextual actions without additional API calls.

The notification provider includes in-flight fetch protection using React refs to prevent duplicate requests during rapid state changes, and computes unread counts locally from the notification array rather than making separate count queries.

The built-in messaging system keeps all communication — from initial enquiry to post-booking follow-up — in one auditable place, with unread counts surfaced in the navigation bar and threaded conversations with full history.

Instant delivery via WebSocket — no polling, no external pub/sub.

06

Webhook Idempotency & Event Auditing

A duplicated webhook means a creator gets paid twice. A dropped one means they don't get paid at all. Payment webhooks are inherently unreliable — Stripe may send the same event multiple times, network failures cause retries, and out-of-order delivery is common. In a marketplace handling real money between two parties, every edge case is a financial risk.

The platform handles this with a dedicated webhook events table that logs every incoming webhook with its Stripe event ID, event type, and a processed flag. A unique constraint on the event ID means duplicate deliveries are silently ignored via the database, not application logic.

Events are marked as processed only after the associated database operations (booking status update, transaction record creation) complete successfully. If processing fails, the event remains unprocessed with the error recorded alongside it — making failed webhooks visible and replayable without re-triggering them from Stripe.

The webhook endpoint preserves the raw request body as text rather than parsing it as JSON for Stripe's signature verification — a subtle but critical detail, since JSON parsing and re-serialisation can alter whitespace and break the HMAC check.

Duplicate webhooks are silently ignored — failed ones are visible and replayable.

Technical Architecture

Frontend
Next.js 15 / React 19 / TypeScript / Tailwind CSS / Radix UI
Animations
Framer Motion
Backend
Next.js API Routes / React Server Components
Database
Supabase (PostgreSQL + Row-Level Security)
Auth
Supabase Auth (JWT-based, magic link + password)
Payments
Stripe Connect + Payment Intents + Webhooks
Email
Resend API + React Email
Storage
Supabase Storage (S3-backed CDN)
Hosting
Vercel (Edge Network)

Key Architecture Decisions

PostgreSQL views denormalise creator information, category labels, and computed fields into a single query shape. A mapping function reshapes flat view rows into nested objects the frontend expects, keeping the transformation in one place. If the underlying schema changes, only the view definition needs updating — not every API function.

Key Features

Verified reviews

Reviews locked to completed bookings — no fake ratings, no reputation gaming

Dispute resolution

Structured disputes with multi-channel admin notification and graceful degradation

Spaces marketplace

Physical venue discovery with hourly and daily booking through the same payment system

Portfolio galleries

Drag-and-drop image management with CDN-backed delivery

Automated email lifecycle

Booking confirmations, notifications, and lifecycle communications via React Email

Creator dashboards

Bookings, earnings, services, and portfolio management in one place

Real-time messaging

In-platform communication with unread tracking and full history

Automated booking expiry

Cron-based edge function cancels stale bookings after 7 days

Mobile-first UI

Dedicated mobile navigation and touch-optimised responsive design

Need a platform where money moves between users?

Dual-sided workflows, payment splitting, real-time features, or marketplace-grade security — we've built it before and we'd love to talk about yours.

Start a project
Next project
LOCKD
Productivity · File transfer
→