
Referral Program
- 61 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Grow customers with a refer-a-friend program using unique shareable links, tiered rewards, and built-in fraud prevention.
About
Sets up a refer-a-friend program with shareable tracking links, tiered rewards, and fraud controls. A developer uses it to acquire customers through existing-customer referrals.
- Unique shareable referral links with tiered rewards
- Built-in referral fraud prevention
Referral Program by the numbers
- 61 all-time installs (skills.sh)
- Ranked #534 of 853 Sales & Marketing skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill referral-programAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 61 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Grow customers with a refer-a-friend program using unique shareable links, tiered rewards, and built-in fraud prevention.
Files
Referral Program
Overview
A referral program turns your existing customers into an acquisition channel by rewarding them for introducing new customers. Referral apps like ReferralCandy and Referral Hero handle unique link generation, double-sided rewards (referrer and referee), fraud detection, and post-purchase email triggers without custom code. Only build a custom referral system if your tiering logic, CRM integration, or fraud rules exceed what these tools support.
When to Use This Skill
- When building a "give $10, get $10" refer-a-friend program
- When adding tiered rewards that escalate with the number of successful referrals
- When fraud from self-referrals or multiple accounts is draining referral reward budget
- When measuring referral program CAC versus other acquisition channels
- When integrating referral tracking with post-purchase email flows in Klaviyo
Core Instructions
Step 1: Determine platform and choose the right referral tool
| Platform | Recommended Tool | Why |
|---|---|---|
| Shopify | ReferralCandy | Purpose-built for e-commerce; auto-generates referral links per customer, handles double-sided rewards, post-purchase email trigger, and fraud detection |
| Shopify | Referral Hero | More flexible reward types (cash, gift cards, store credit, custom); deep Klaviyo integration for referral email flows |
| Shopify | Smile.io | Combines loyalty points with referral mechanics in one app — best when you want both |
| WooCommerce | ReferralCandy or Referral Hero | Both offer WooCommerce plugins; connect via REST API to track orders and issue rewards |
| BigCommerce | ReferralCandy | Available on the BigCommerce App Marketplace |
| Custom / Headless | Build referral tracking + reward logic | Required when platform integrations or fraud rules don't match your needs |
---
Step 2: Platform-specific setup
---
Shopify
Option A: ReferralCandy (recommended — full referral suite)
1. Install ReferralCandy from the Shopify App Store 2. Connect your Shopify store — ReferralCandy automatically imports existing customers 3. Configure your reward structure:
- Go to ReferralCandy → Rewards → Set Rewards
- Choose reward type for referrer: cash via PayPal, store discount, or custom gift
- Choose reward type for referee: discount code applied at checkout
- Example: Referrer gets $10 store credit; referee gets $10 off their first order
4. Configure referral email timing:
- Go to Post-Purchase Email → Settings
- Set send delay to 1–3 days after purchase (customer is in the honeymoon phase)
- Customize the email template with your brand and the referral link
5. ReferralCandy automatically:
- Generates a unique referral link per customer
- Tracks clicks and attributions with a 30-day cookie window
- Detects self-referrals and blocks reward issuance
- Sends the referee a welcome email with their discount code when they sign up via the link
6. View performance in ReferralCandy → Analytics:
- Referrals sent, clicks, signups, and purchases
- Revenue attributed to referrals
- Top referrers (for VIP outreach)
Option B: Referral Hero (Klaviyo-first)
1. Install Referral Hero from the App Store 2. Configure in Referral Hero → Campaigns → Create Campaign 3. Referral Hero integrates with Klaviyo — when a referral converts, it triggers a Klaviyo flow for reward notification emails 4. Reward types include store credit (issued via Shopify discount codes), cash (PayPal), or gift cards
Adding a referral entry point in post-purchase emails (Klaviyo): 1. In Klaviyo, go to the Post-Purchase flow 2. Add an email at the 3-day step with the referral link 3. ReferralCandy and Referral Hero both provide a Klaviyo integration that injects {{ customer.referral_link }} as a merge tag
---
WooCommerce
ReferralCandy for WooCommerce:
1. Install the ReferralCandy plugin from WordPress.org or the ReferralCandy integrations page 2. Connect your WooCommerce store with your ReferralCandy API credentials 3. ReferralCandy tracks WooCommerce orders and attributes purchases to referrers automatically 4. Configure rewards and email timing in the ReferralCandy dashboard (same as Shopify above)
Referral Hero for WooCommerce: 1. Install the Referral Hero WooCommerce plugin 2. Configure a campaign and set reward type to WooCommerce coupon code 3. Referral Hero issues a unique coupon code to each referee upon signup and notifies the referrer when they convert
Manual referral program with AutomateWoo:
- If you prefer full control, use AutomateWoo to trigger referral emails and issue discount codes
- AutomateWoo has a built-in Referral workflow template under AutomateWoo → Workflows → Referrals
---
BigCommerce
ReferralCandy for BigCommerce:
1. Go to Apps → Search "ReferralCandy" and install 2. Configuration is the same as the Shopify version 3. ReferralCandy tracks BigCommerce orders and issues BigCommerce discount codes as rewards
---
Custom / Headless
For headless storefronts, build referral tracking with tiered rewards and fraud detection:
// lib/referrals.ts
import { randomBytes } from 'crypto';
// Generate a unique, human-friendly referral code per customer
export async function getOrCreateReferralCode(customerId: string): Promise<string> {
const existing = await db.referralCodes.findFirst({ where: { customerId } });
if (existing) return existing.code;
const customer = await db.customers.findUnique({ where: { id: customerId } });
const prefix = (customer!.firstName ?? 'USER').slice(0, 4).toUpperCase();
const suffix = randomBytes(3).toString('hex').toUpperCase();
const code = `${prefix}${suffix}`; // e.g., JANE3A9F
await db.referralCodes.create({ data: { customerId, code } });
return code;
}
// Middleware: capture referral code from ?ref= URL param into a 30-day cookie
export function referralTrackingMiddleware(req: Request, res: Response, next: NextFunction) {
const code = req.query.ref as string;
if (code && !req.cookies.ref_code) {
res.cookie('ref_code', code, { maxAge: 30 * 86400000, httpOnly: true, secure: true, sameSite: 'lax' });
}
next();
}
// Tiered rewards: reward amount increases as referrer accumulates successful referrals
const REFERRAL_TIERS = [
{ minReferrals: 0, referrerRewardCents: 1000, refereeRewardCents: 1000 }, // $10/$10
{ minReferrals: 5, referrerRewardCents: 1500, refereeRewardCents: 1000 }, // $15/$10 after 5
{ minReferrals: 10, referrerRewardCents: 2500, refereeRewardCents: 1500 }, // $25/$15 after 10
];
function getReferralReward(successfulReferrals: number) {
return [...REFERRAL_TIERS].reverse().find(t => successfulReferrals >= t.minReferrals)!;
}
// On new account creation: attribute referral, run fraud checks
export async function onCustomerCreated(customerId: string, refCode: string | undefined) {
if (!refCode) return;
const referralCode = await db.referralCodes.findFirst({ where: { code: refCode } });
if (!referralCode || referralCode.customerId === customerId) return; // block self-referral
// Fraud check: same shipping address as referrer
const [referee, referrer] = await Promise.all([
db.customers.findUnique({ where: { id: customerId }, include: { addresses: true } }),
db.customers.findUnique({ where: { id: referralCode.customerId }, include: { addresses: true } }),
]);
const refereeAddr = referee?.addresses[0]?.addressHash;
const referrerAddr = referrer?.addresses[0]?.addressHash;
if (refereeAddr && refereeAddr === referrerAddr) return; // same address = block
await db.referrals.create({ data: { referrerId: referralCode.customerId, refereeId: customerId, code: refCode, status: 'pending' } });
}
// On referee's first purchase: issue rewards to both parties
export async function onRefereeFirstPurchase(refereeId: string, orderId: string) {
const referral = await db.referrals.findFirst({ where: { refereeId, status: 'pending' } });
if (!referral) return;
const order = await db.orders.findUnique({ where: { id: orderId } });
if (!order || order.subtotalCents < 2500) return; // require minimum $25 order
const referrerStats = await db.referralCodes.findFirst({ where: { customerId: referral.referrerId } });
const tier = getReferralReward(referrerStats?.totalReferrals ?? 0);
await Promise.all([
issueStoreCredit(referral.referrerId, tier.referrerRewardCents, `Referral reward`),
issueStoreCredit(refereeId, tier.refereeRewardCents, 'Welcome reward — referred by a friend'),
]);
await db.referrals.update({ where: { id: referral.id }, data: { status: 'rewarded', orderId } });
await db.referralCodes.update({ where: { customerId: referral.referrerId }, data: { totalReferrals: { increment: 1 } } });
}Referral program analytics SQL:
-- Monthly referral program performance
SELECT
DATE_TRUNC('month', r.created_at) AS month,
COUNT(*) AS total_referrals,
COUNT(CASE WHEN r.status = 'rewarded' THEN 1 END) AS successful_referrals,
ROUND(100.0 * COUNT(CASE WHEN r.status = 'rewarded' THEN 1 END) / NULLIF(COUNT(*), 0), 1) AS conversion_rate_pct,
SUM(CASE WHEN r.status = 'rewarded' THEN rc.total_rewards_cents END) / 100.0 AS rewards_paid_usd
FROM referrals r
JOIN referral_codes rc ON r.referrer_id = rc.customer_id
GROUP BY 1
ORDER BY 1 DESC;---
Step 3: Promote the referral program
The referral link needs to be visible — customers won't find it if it's buried:
1. Post-purchase confirmation page — add a referral CTA to the order confirmation page: "Love your order? Share with a friend and you both get $10" 2. Post-purchase email — include the referral link in the 3-day post-purchase email (ReferralCandy and Klaviyo both support this automatically) 3. Customer account page — add a "Refer a Friend" section showing their link and referral history 4. Transactional emails — include a subtle referral CTA in shipping confirmation emails
---
Step 4: Measure referral program ROI
| Metric | How to Measure |
|---|---|
| Referral conversion rate | Successful referrals / total referral clicks |
| Referral CAC | Total rewards paid / successful referrals |
| Revenue attributed to referrals | Tag referred orders and compare total revenue |
| Referred customer CLV vs. non-referred | Cohort comparison at 90 days and 12 months |
Compare referral CAC against your paid acquisition channels. Referral programs typically produce 20–30% lower CAC than paid channels when optimized.
Best Practices
- Use ReferralCandy before building from scratch — it handles link generation, attribution, fraud detection, and reward issuance in minutes
- Require a minimum first-order value to qualify for the reward — prevents reward farming with $1 test orders
- Use store credit, not one-time discount codes — store credit requires the customer to return and creates a second purchase occasion
- Apply a 30-day attribution window — shorter windows miss customers who take time to convert
- Send a reward notification email immediately when the referee purchases — the referrer often doesn't know the reward was granted; this also prompts further referrals
- Block referrals to the same shipping address — this is the clearest fraud signal for household self-referrals
Common Pitfalls
| Problem | Solution |
|---|---|
| Self-referral fraud via multiple email accounts | Block same shipping address referrals (strongest signal); ReferralCandy detects this automatically |
| Referral link shared on coupon forums | Monitor referred cohort CLV; if it's significantly lower than organic, apply a higher minimum order value |
| Store credit issued before 30-day return window | Delay reward issuance by 30 days after the referee's order to account for returns |
| Referral cookie overwritten by later ad click | Store the first referral click separately; ReferralCandy uses first-click attribution for referrals |
| Low referral email open rates | Move the referral ask from the receipt email to a dedicated 3-day post-purchase email when customer satisfaction is highest |
Related Skills
- @affiliate-program
- @customer-lifetime-value
- @customer-segmentation
- @email-marketing-automation
{
"context": "Tests whether the agent implements the referral tracking middleware with correct cookie settings, first-click attribution (not last-click), click logging, and IP-based rate limiting. Both the middleware and signup attribution logic should be present.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Cookie name is ref_code",
"max_score": 10,
"description": "The referral cookie is stored under the exact name 'ref_code' (not 'referral', 'ref', or any other name)"
},
{
"name": "30-day cookie maxAge",
"max_score": 10,
"description": "Cookie maxAge is set to exactly 30 * 86400 * 1000 milliseconds (or equivalent 2592000000ms), representing a 30-day attribution window"
},
{
"name": "httpOnly cookie flag",
"max_score": 8,
"description": "Cookie has httpOnly: true set"
},
{
"name": "Secure cookie flag",
"max_score": 7,
"description": "Cookie has secure: true set"
},
{
"name": "sameSite lax",
"max_score": 7,
"description": "Cookie has sameSite set to 'lax' (not 'strict' or 'none')"
},
{
"name": "First-click wins",
"max_score": 15,
"description": "Cookie is only set when no existing ref_code cookie is present — existing ref_code cookies are NOT overwritten"
},
{
"name": "Click logging",
"max_score": 10,
"description": "Each click event is logged to the database with at minimum: the referral code, the visitor's IP address, and a timestamp"
},
{
"name": "User agent logged",
"max_score": 8,
"description": "The click log entry includes the user-agent from request headers"
},
{
"name": "IP rate limiting threshold",
"max_score": 15,
"description": "Clicks from the same IP are rate-limited, blocking or skipping cookie-set/logging after more than 10 clicks from the same IP within 24 hours"
},
{
"name": "Self-referral prevention",
"max_score": 10,
"description": "The signup attribution code checks that the referral code's owner is not the same as the newly created customer before creating the referral record"
}
]
}
Referral Tracking Middleware
Problem Description
A mid-sized e-commerce company is launching a refer-a-friend program. The marketing team has noticed that when customers share referral links, they sometimes click the link multiple times across different sessions, or the referral link attribution gets overwritten when a customer clicks an ad the same day. They need a robust Express.js middleware that reliably attributes the original referral visit to a new customer signup, regardless of how many times the link is clicked or which channel the customer ends up converting through.
The engineering team also has concerns about referral links getting posted on deal-sharing forums (like RetailMeNot or Reddit), which could result in the same IP address making hundreds of clicks — none of which are genuine referrals. The middleware should detect and block this kind of abuse at the click-logging layer.
Output Specification
Implement the referral tracking middleware in a file called referral-middleware.ts (TypeScript). The middleware should:
- Capture referral codes from incoming requests and persist them for later attribution
- Track click events with enough metadata for fraud analysis
- Protect against high-volume clicks from the same IP address (rate limiting)
Also produce a referral-attribution.ts file that shows how a new customer signup reads the stored referral code and creates the pending referral record.
Both files should be standalone TypeScript modules with type annotations. You do not need to connect to a real database — use stub/interface definitions for db so the logic is clear.
{
"context": "Tests whether the agent implements the referral fraud detection function with the correct set of signals, correct blocking decisions for each signal, proper corporate email domain logic, IP cluster threshold, and timing-based signals.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Same shipping address blocks",
"max_score": 15,
"description": "A 'same_shipping_address' (or equivalent) signal is returned with block: true when both referrer and referee share the same shipping address"
},
{
"name": "Same address uses hash comparison",
"max_score": 8,
"description": "The shipping address comparison uses a hash field (not string comparison of raw address fields) to detect duplicates"
},
{
"name": "Corporate email domain signal",
"max_score": 10,
"description": "A 'same_corporate_email_domain' (or equivalent) signal is returned when referrer and referee share the same non-consumer email domain"
},
{
"name": "Corporate email is non-blocking",
"max_score": 8,
"description": "The same-email-domain signal has block: false (does NOT block automatically)"
},
{
"name": "Consumer domains excluded",
"max_score": 8,
"description": "The email domain check excludes at least gmail.com, yahoo.com, hotmail.com, and outlook.com from triggering the corporate domain signal"
},
{
"name": "IP cluster signal",
"max_score": 10,
"description": "A signal is returned when the referrer has more than 3 referrals from the same IP range within the past 30 days"
},
{
"name": "IP cluster is non-blocking",
"max_score": 7,
"description": "The IP cluster signal has block: false"
},
{
"name": "Immediate signup signal",
"max_score": 10,
"description": "A signal is returned when the referee account was created within 5 minutes of the most recent referral click"
},
{
"name": "Immediate signup is non-blocking",
"max_score": 7,
"description": "The immediate-signup signal has block: false"
},
{
"name": "FraudSignal interface",
"max_score": 7,
"description": "The return type or interface for each signal includes both a 'signal' string field and a 'block' boolean field"
},
{
"name": "30-day IP lookback window",
"max_score": 10,
"description": "The IP cluster check uses a 30-day lookback window (not a different duration)"
}
]
}
Referral Fraud Detection
Problem Description
A fintech startup running a referral program has discovered that a small number of bad actors are draining the referral reward budget. Some users are creating multiple accounts at the same address to refer themselves. Others appear to be operating referral "rings" where many accounts are created from the same network and immediately refer each other. The fraud team needs a structured, auditable fraud detection function that evaluates each referral relationship and returns a list of signals — some of which should block the referral outright, others which should simply be logged for manual review.
The team wants the function to be modular and signal-based so they can tune blocking thresholds independently for each signal type without changing business logic. Each signal should carry a name and a flag indicating whether it warrants an automatic block.
Output Specification
Implement the fraud detection logic in a TypeScript file called referral-fraud.ts. The function should accept the referrer ID, referee ID, and referee email, query relevant data, and return an array of fraud signals. Each signal object must have at minimum a signal name string and a block boolean.
The function should evaluate at least the following patterns:
- Shared contact or address details between referrer and referee
- Email domain similarity (with exclusions for common consumer email providers)
- Behavioral patterns around account creation timing relative to referral clicks
- Geographic clustering signals based on network proximity
Use TypeScript interfaces and stub the db access layer. Include a comment explaining the rationale for each signal's blocking decision.
{
"context": "Tests whether the agent implements the three-tier referral reward system with correct dollar amounts, minimum order gating, store credit (not discount codes), dual-party reward issuance, 1-year store credit expiry, referrer email notification, and proper record updates.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Base tier values",
"max_score": 8,
"description": "Tier at 0+ referrals sets referrer reward to 1000 cents ($10) and referee reward to 1000 cents ($10)"
},
{
"name": "Mid tier values",
"max_score": 8,
"description": "Tier at 5+ referrals sets referrer reward to 1500 cents ($15) and referee reward to 1000 cents ($10)"
},
{
"name": "Top tier values",
"max_score": 8,
"description": "Tier at 10+ referrals sets referrer reward to 2500 cents ($25) and referee reward to 1500 cents ($15)"
},
{
"name": "Minimum order threshold",
"max_score": 12,
"description": "Reward is NOT granted when the order subtotal is below 2500 cents ($25); orders under $25 are skipped/returned early"
},
{
"name": "Store credit (not discount codes)",
"max_score": 10,
"description": "Rewards are issued as store credit — code does NOT use coupon codes, discount codes, or voucher codes as the reward mechanism"
},
{
"name": "Both parties rewarded",
"max_score": 10,
"description": "Both the referrer and the referee receive store credit on the same qualifying purchase event"
},
{
"name": "Referrer email notification",
"max_score": 8,
"description": "A notification email is sent to the referrer after rewards are issued (not the referee)"
},
{
"name": "Referral status updated",
"max_score": 8,
"description": "The referral record status is updated to 'rewarded' (or equivalent) after successful reward issuance"
},
{
"name": "totalReferrals incremented",
"max_score": 8,
"description": "The referrer's total successful referral count is incremented by 1 after a successful reward event"
},
{
"name": "1-year store credit expiry",
"max_score": 10,
"description": "Store credit is issued with an expiry of 365 days (1 year) from the issuance date"
},
{
"name": "Tier lookup uses highest eligible tier",
"max_score": 10,
"description": "getReferralReward (or equivalent) returns the highest tier for which minReferrals <= successfulReferrals, not just the first match"
}
]
}
Referral Reward Engine
Problem Description
A growing online retailer wants to encourage their best customers to refer more friends by offering escalating rewards. The existing flat-rate payout is easy to game: fraudsters are signing up dummy accounts and placing tiny orders just to claim the reward. Meanwhile, power-users who have referred many friends are complaining they still get the same reward as someone on their first referral. The business wants to fix both problems simultaneously.
The team needs a reward engine that grants rewards only on qualifying purchases, scales up the referrer's reward based on their referral track record, and notifies the referrer so they keep engaging with the program. The rewards should be stored against each customer's account and expire after a reasonable period to encourage return purchases.
Output Specification
Implement the reward engine in a TypeScript file called referral-rewards.ts. It should include:
- The tier definitions as a constant
- A function to look up the correct tier for a given number of successful referrals
- The first-purchase event handler that validates eligibility, issues rewards, updates records, and triggers notifications
Also include a reward-issuance.ts file that shows the reward issuance function with the correct expiry policy.
Use stub/interface definitions for db and email notification helpers so the logic is clear without needing a real database. Annotate the code with TypeScript types.
{
"name": "finsi/referral-program",
"version": "0.1.0",
"summary": "Refer-a-friend flows with unique links, reward tiers, and fraud prevention",
"skills": {
"referral-program": {
"path": "SKILL.md"
}
}
}