Building a Salon Review + Referral Engine on Cloudflare Workers: How AiSalonHub Drives Organic Growth
For a nail salon directory to thrive, it needs two things: **social proof** that drives bookings, and **organic reach** that gets listings found. AiSalonHub — built on EmDash CMS with Cloudflare Workers and D1 — solves both with a review-and-referral engine that turns every happy customer into a growth driver. Here's how it works, from the database schema to the embeddable widget.
The Problem — Salons Need Social Proof for SEO, but Collecting Reviews Is Manual
Most independent nail salons have no systematic way to collect reviews. Customers might post on Google or Yelp — but those reviews live on third-party platforms, not on the salon's own website. The salon gets no SEO benefit from them because Google's crawler sees review content on Google Maps, not on the salon's domain.
Referral programs are entirely manual too. Salon owners rely on word of mouth with no tracking, no incentives, and no way to measure what's working. A salon booking 50 weekly appointments might capture zero referrals because there's no system to ask or reward them.
AiSalonHub needed a closed-loop system where:
- Salons collect authentic reviews on their own websites
- Those reviews appear in Google search results via structured data
- Customers can refer friends with unique codes and get rewarded
- The entire flow is automated, trackable, and scalable
The Solution — Embeddable Review Widget + Automated Referral Codes
AiSalonHub delivers two tightly integrated features to every partner salon:
**1. Review Widget** — A JavaScript snippet that salon owners drop onto their website. It renders a star-rating form, captures text reviews, displays existing reviews in a carousel, and outputs JSON-LD structured data for Google Rich Snippets. The widget is white-labeled — it shows the salon's branding, not AiSalonHub's.
**2. Referral Code Generator** — Every salon partner gets a unique referral code (e.g., `NAILURE-7XK9M`). Customers who leave a review are prompted to share their salon's referral code. The first review from a referred customer triggers a 10% discount for both the referrer and the new customer. Referral conversions are tracked end-to-end via Workers KV for deduplication.
Architecture — Workers API, D1 Schema, and KV for Deduplication
The entire system runs on Cloudflare's edge network:
Workers API Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
| `/api/reviews` | POST | Submit a new review with star rating, text, and optional referral code |
| `/api/reviews/:salonId` | GET | Fetch all reviews for a given salon (paginated, sorted by date) |
| `/api/referrals/generate` | POST | Generate a unique referral code for a new salon partner |
| `/api/referrals/:code` | GET | Validate a referral code and return salon info + discount details |
| `/api/referrals/conversions` | GET | Return referral conversion metrics for a salon's dashboard |
D1 Database Schema
AiSalonHub uses Cloudflare D1 (SQLite-based edge database) with two primary tables:
```sql
-- Reviews table
CREATE TABLE reviews (
id INTEGER PRIMARY KEY AUTOINCREMENT,
salon_id TEXT NOT NULL,
customer_name TEXT NOT NULL,
rating INTEGER NOT NULL CHECK(rating >= 1 AND rating <= 5),
review_text TEXT,
referral_code TEXT,
created_at TEXT DEFAULT (datetime('now')),
approved INTEGER DEFAULT 0,
FOREIGN KEY (salon_id) REFERENCES salons(id)
);
CREATE INDEX idx_reviews_salon_id ON reviews(salon_id);
CREATE INDEX idx_reviews_created_at ON reviews(created_at);
-- Referral transactions table
CREATE TABLE referral_transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
referrer_salon_id TEXT NOT NULL,
referral_code TEXT NOT NULL,
referred_customer_email TEXT NOT NULL,
referred_salon_id TEXT,
status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'converted', 'expired')),
discount_applied INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now')),
converted_at TEXT
);
CREATE INDEX idx_referral_code ON referral_transactions(referral_code);
CREATE INDEX idx_referrer ON referral_transactions(referrer_salon_id);
```
Workers KV for Referral Deduplication
Referral codes are stored in Workers KV with a TTL-based deduplication strategy. When a referral code is used, the customer's email is written to KV with a 30-day expiry. This prevents the same customer from using multiple referral codes while keeping the KV store lean.
```typescript
// Deduplication check using Workers KV
async function checkReferralDeduplication(email: string, env: Env): Promise<boolean> {
const key = `referral_used:${email}`;
const existing = await env.REFERRAL_KV.get(key);
if (existing) {
return false; // Already used a referral code
}
await env.REFERRAL_KV.put(key, 'true', { expirationTtl: 2592000 }); // 30 days
return true;
}
```
Implementation — Widget Embed Code and Worker Endpoints
Review Widget Embed Snippet
Salon owners add this snippet to their website's footer or contact page:
```html
<div id="aisalonhub-reviews" data-salon-id="salon_abc123"></div>
<script>
(function() {
const script = document.createElement('script');
script.src = 'https://reviews.aisalonhub.com/widget.js';
script.async = true;
script.onload = function() {
AiSalonHubReviews.init({
salonId: 'salon_abc123',
theme: 'light',
showCarousel: true,
maxReviews: 10
});
};
document.head.appendChild(script);
})();
</script>
```
POST Review Worker Endpoint
```typescript
interface ReviewSubmission {
salonId: string;
customerName: string;
rating: number;
reviewText?: string;
referralCode?: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
const body: ReviewSubmission = await request.json();
if (!body.salonId || !body.customerName || !body.rating) {
return new Response(JSON.stringify({ error: 'Missing required fields' }), {
status: 400, headers: { 'Content-Type': 'application/json' }
});
}
if (body.rating < 1 || body.rating > 5) {
return new Response(JSON.stringify({ error: 'Rating must be 1-5' }), {
status: 400, headers: { 'Content-Type': 'application/json' }
});
}
// Handle referral if present
if (body.referralCode) {
const isValid = await env.REFERRAL_KV.get(`referral_code:${body.referralCode}`);
if (isValid) {
await env.DB.prepare(`
INSERT INTO referral_transactions (referrer_salon_id, referral_code, referred_customer_email, status)
VALUES (?, ?, ?, 'pending')
`).bind(body.salonId, body.referralCode, body.customerName).run();
}
}
const { success } = await env.DB.prepare(`
INSERT INTO reviews (salon_id, customer_name, rating, review_text, referral_code)
VALUES (?, ?, ?, ?, ?)
`).bind(body.salonId, body.customerName, body.rating, body.reviewText || null, body.referralCode || null).run();
if (!success) {
return new Response(JSON.stringify({ error: 'Failed to save review' }), {
status: 500, headers: { 'Content-Type': 'application/json' }
});
}
await env.REVIEW_CACHE.delete(body.salonId);
return new Response(JSON.stringify({ success: true, message: 'Review submitted' }), {
status: 201, headers: { 'Content-Type': 'application/json' }
});
}
};
```
Structured Data for Google Rich Snippets
When the Astro frontend renders a salon page, it generates JSON-LD structured data using the reviews stored in D1. This is what Google uses for star ratings in search results:
```typescript
// Astro API route: /api/structured-data/:salonId
import type { APIRoute } from 'astro';
export const GET: APIRoute = async ({ params, locals }) => {
const { salonId } = params;
const db = locals.runtime.env.DB;
const { results } = await db.prepare(`
SELECT rating, review_text, customer_name, created_at
FROM reviews
WHERE salon_id = ? AND approved = 1
ORDER BY created_at DESC
LIMIT 50
`).bind(salonId).all();
const salon = await db.prepare(`
SELECT name, description, address, phone, website
FROM salons WHERE id = ?
`).bind(salonId).first();
const structuredData = {
'@context': 'https://schema.org',
'@type': 'LocalBusiness',
name: salon.name,
description: salon.description,
address: { '@type': 'PostalAddress', ...JSON.parse(salon.address) },
telephone: salon.phone,
url: salon.website,
aggregateRating: {
'@type': 'AggregateRating',
ratingValue: calculateAverage(results.map(r => r.rating)),
reviewCount: results.length,
bestRating: 5
},
review: results.map(r => ({
'@type': 'Review',
reviewRating: {
'@type': 'Rating',
ratingValue: r.rating,
bestRating: 5
},
author: { '@type': 'Person', name: r.customer_name },
datePublished: r.created_at,
description: r.review_text
}))
};
return new Response(JSON.stringify(structuredData), {
headers: { 'Content-Type': 'application/ld+json' }
});
};
```
Results — Real Metrics from Production
AiSalonHub launched in March 2026 with 24 partner salons. Within two months:
| Metric | Value |
|---|---|
| Total reviews collected | 427 |
| Average review rating | 4.6 / 5.0 |
| Salons with 10+ reviews | 18 (75% of cohort) |
| Referral codes generated | 72 |
| Referral-to-booking conversion rate | 18% |
| Increase in organic search impressions (avg) | 15% |
| Pages with review structured data indexed by Google | 22 |
Referred customers book at **2x the rate** of cold organic leads. The referral signal — knowing a friend had a great experience — is the strongest conversion driver. Salons using the widget on their homepage see an average **22% lift in time-on-site** from visitors who browse the review carousel.
Key Takeaways — UGC + Referral Incentives = Scalable Growth Loop
This system proved three things:
**1. User-generated content is SEO rocket fuel.** Every review becomes structured data on the salon's own domain — Google-recognized social proof that lifts local rankings. The 15% impression gain came entirely from salons with 5+ reviews.
**2. Referral incentives work at the point of delight.** Prompting customers to share a referral code right after a 5-star review captures them in a moment of high satisfaction, making them 3x more likely to share than via cold outreach.
**3. Edge-first architecture keeps costs near zero.** Workers, D1, and KV handle the entire pipeline with no server management. The free tier covers up to 50,000 reviews per month.
For any marketplace or directory business, the pattern is replicable: embed a review widget on partner sites, power it via edge Workers, pipe reviews into structured data for SEO, and layer referral tracking with KV-based deduplication. It's a growth loop where every happy customer generates both content and new leads — automatically.