Back to Home

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.

EVÖQ creative marketplace platform screenshot
ClientEVÖQ
IndustryCreative Services / Marketplace
RegionUnited 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.

Creators
Portfolio
Services
Earnings
Bookings
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:

01

Request

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

02

Review

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

03

Payment

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

04

Completion

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

05

Dispute

If something goes wrong, either party can raise a dispute, triggering multi-channel admin notification with graceful degradation.

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.

RequestClient submits booking
AcceptCreator reviews
PaymentStripe processes
CompleteService delivered
ReviewClient 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:

01

Creator onboarding

Creators connect their Stripe account through an OAuth flow directly from their dashboard. The platform handles identity verification and payout setup.

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.

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.

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.

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

Client

Initiates payment

Stripe

Processes transaction

Fee Split

Automatic routing

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.

User searches “video”

Autocomplete triggers as they type

Full-Text IndexTitle, description, tools
Category HierarchyRecursive parent resolution
Relevance RankingBuilt-in scoring
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

Swipe for more →

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

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.

Get in Touch →