個人開発SaaSのリファラルプログラム実装2026 — Stripe クーポン × Supabase × Resend で紹介報酬を自動化する
結論:4ファイルでリファラルプログラムの骨格が完成する
紹介コード発行 + サインアップ追跡 + Stripe クーポン報酬付与 + Resend 通知メールの4点セットで、広告費をかけずに既存ユーザーに新規獲得を手伝ってもらう仕組みが作れます。
// リファラルプログラムの最小フロー
ユーザーが紹介リンクをシェア (referral_code 入り URL)
→ 新規ユーザーがサインアップ (referrals テーブルに記録, Supabase)
→ 新規ユーザーが有料プランに転換 (Stripe Webhook: customer.subscription.created)
→ 紹介者に Stripe クーポン(1ヶ月割引)を自動付与 + 通知メール送信 (Resend)
この記事では、個人開発SaaSで手軽に始められるリファラルプログラムを、実コード付きで解説します。
masatoman.net で Claude Crew Lab(Free MVP)を運用中です。Standard(¥1,980)・Premium(¥4,980)はローンチ準備段階のため、実際の紹介経由転換データはまだありません。この記事で紹介する実装パターンは、Stripe 公式ドキュメント・ReferralHero ケーススタディ・Viral Loops 業界レポートをベースにしており、筆者がテスト環境で動作確認したコードを含みます。
この記事でわかること:
- なぜリファラルプログラムが個人開発SaaSで有効か
- Supabase の
referralsテーブル設計 - 紹介コードの発行と管理方法
- サインアップ時の紹介コード追跡実装
- Stripe Webhook で転換を検知してクーポンを自動付与する実装
- Resend で紹介報酬の通知メールを送る実装
- 「で、どう稼ぐ?」— 紹介プログラムの収益貢献の考え方
リファラルプログラムが個人開発に刺さる理由
広告費は出せない。SNSフォロワーも多くない。でも既存ユーザーは少しでもいる。そういう「個人開発初期」において、リファラルプログラムは最もコスパの高い獲得チャネルの一つです。
仕組みはシンプルです。既存ユーザーが「紹介リンク」をシェアし、そこ経由で新規登録・有料転換が起きたら、紹介者に報酬(割引・無料期間など)を渡す。ユーザーが営業してくれる構造です。
個人開発の文脈でリファラルが機能しやすい理由:
| 理由 | 詳細 |
|---|---|
| 同質コミュニティ | 個人開発者は同じコミュニティにいる → 紹介先の質が高い |
| 熱量の高い初期ユーザー | 初期ユーザーはプロダクトへの共感が強く、紹介モチベーションが高い |
| 実装コストが小さい | Stripe クーポン + Supabase + Resend で数時間で動く |
| CAC(顧客獲得単価)が低い | 報酬コスト(割引1ヶ月)のみで獲得できる |
Step 0:Supabase のテーブル設計
まず referrals テーブルと profiles テーブルへの referral_code カラム追加から始めます。
-- profiles テーブルに紹介コードを追加
ALTER TABLE profiles ADD COLUMN referral_code TEXT UNIQUE;
ALTER TABLE profiles ADD COLUMN referred_by TEXT; -- 誰に紹介されたか
-- referrals テーブル(紹介の追跡)
CREATE TABLE referrals (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referrer_id UUID REFERENCES auth.users(id) NOT NULL,
referee_id UUID REFERENCES auth.users(id),
referral_code TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'signed_up', 'converted', 'rewarded')),
stripe_coupon_id TEXT,
created_at TIMESTAMPTZ DEFAULT now(),
converted_at TIMESTAMPTZ,
rewarded_at TIMESTAMPTZ
);
-- RLS 設定
ALTER TABLE referrals ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users can see own referrals"
ON referrals FOR SELECT
USING (referrer_id = auth.uid() OR referee_id = auth.uid());
status の遷移:
pending→ 紹介リンク作成済み、未使用signed_up→ 紹介リンク経由でサインアップ完了converted→ 有料プランに転換(報酬付与対象)rewarded→ Stripe クーポン発行済み
Step 1:紹介コードの発行
ユーザーが初回ログインしたタイミングで、ランダムな紹介コードを自動発行します。
// lib/referral.ts
import { createClient } from '@/lib/supabase/server'
import { customAlphabet } from 'nanoid'
const nanoid = customAlphabet('ABCDEFGHJKLMNPQRSTUVWXYZ23456789', 8)
export async function ensureReferralCode(userId: string): Promise<string> {
const supabase = createClient()
const { data: profile } = await supabase
.from('profiles')
.select('referral_code')
.eq('id', userId)
.single()
if (profile?.referral_code) return profile.referral_code
const code = nanoid()
await supabase
.from('profiles')
.update({ referral_code: code })
.eq('id', userId)
return code
}
export function buildReferralUrl(code: string, baseUrl: string): string {
return `${baseUrl}/signup?ref=${code}`
}
nanoid で8文字のコードを生成します。I, O, 0, 1 を除いた文字セットを使うことで、読み間違いを防ぎます。
Step 2:サインアップ時の紹介コード追跡
サインアップページで ?ref=XXXX パラメータを受け取り、referrals テーブルに記録します。
// app/signup/page.tsx の一部
import { ensureReferralCode } from '@/lib/referral'
export default async function SignupPage({
searchParams,
}: {
searchParams: { ref?: string }
}) {
// ref パラメータを cookie に保存(Next.js middleware 経由でも可)
return <SignupForm referralCode={searchParams.ref} />
}
// app/api/auth/callback/route.ts(Supabase Auth callback 処理内)
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const code = searchParams.get('code')
const referralCode = searchParams.get('ref') // cookie から取り出すことも多い
const supabase = createClient()
if (code) {
const { data: { user } } = await supabase.auth.exchangeCodeForSession(code)
if (user && referralCode) {
await trackReferralSignup(user.id, referralCode)
}
}
return NextResponse.redirect(new URL('/dashboard', request.url))
}
// lib/referral.ts に追加
export async function trackReferralSignup(
refereeId: string,
referralCode: string
) {
const supabase = createClient()
// 紹介コードから紹介者を特定
const { data: referrer } = await supabase
.from('profiles')
.select('id')
.eq('referral_code', referralCode)
.single()
if (!referrer) return // 無効なコードは無視
// 自己紹介防止
if (referrer.id === refereeId) return
// referrals テーブルに記録
await supabase.from('referrals').insert({
referrer_id: referrer.id,
referee_id: refereeId,
referral_code: referralCode,
status: 'signed_up',
})
// referee のプロフィールに referral_code を記録
await supabase
.from('profiles')
.update({ referred_by: referralCode })
.eq('id', refereeId)
}
Step 3:Stripe Webhook で転換を検知して報酬を付与
customer.subscription.created イベントを受け取ったとき、紹介経由かどうか確認してクーポンを発行します。
// lib/referral-reward.ts
import Stripe from 'stripe'
import { createClient } from '@/lib/supabase/server'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
export async function handleReferralConversion(
stripeCustomerId: string
) {
const supabase = createClient()
// Stripe customer から Supabase user を特定
const { data: profile } = await supabase
.from('profiles')
.select('id, referred_by')
.eq('stripe_customer_id', stripeCustomerId)
.single()
if (!profile?.referred_by) return // 紹介経由でない
// 紹介レコードを特定
const { data: referral } = await supabase
.from('referrals')
.select('id, referrer_id')
.eq('referral_code', profile.referred_by)
.eq('referee_id', profile.id)
.eq('status', 'signed_up')
.single()
if (!referral) return // 既に処理済み or 対象外
// 紹介者の Stripe customer ID を取得
const { data: referrerProfile } = await supabase
.from('profiles')
.select('stripe_customer_id')
.eq('id', referral.referrer_id)
.single()
if (!referrerProfile?.stripe_customer_id) return
// Stripe クーポンを作成して紹介者に付与
const coupon = await stripe.coupons.create({
duration: 'once',
percent_off: 100, // 1ヶ月無料(¥1,980 分)
name: '紹介プログラム報酬 — 1ヶ月無料',
metadata: {
referral_id: referral.id,
referee_id: profile.id,
},
})
await stripe.customers.update(referrerProfile.stripe_customer_id, {
coupon: coupon.id,
})
// referrals テーブルのステータスを更新
await supabase
.from('referrals')
.update({
status: 'rewarded',
stripe_coupon_id: coupon.id,
converted_at: new Date().toISOString(),
rewarded_at: new Date().toISOString(),
})
.eq('id', referral.id)
return { referrerId: referral.referrer_id, couponId: coupon.id }
}
Webhook ハンドラへの組み込み:
// app/api/stripe/webhook/route.ts(既存 Webhook ハンドラに追記)
case 'customer.subscription.created': {
const subscription = event.data.object as Stripe.Subscription
const rewardResult = await handleReferralConversion(
subscription.customer as string
)
if (rewardResult) {
await sendReferralRewardEmail(rewardResult.referrerId)
}
break
}
Step 4:Resend で報酬通知メールを送る
// lib/referral-emails.ts
import { Resend } from 'resend'
import { createClient } from '@/lib/supabase/server'
const resend = new Resend(process.env.RESEND_API_KEY!)
export async function sendReferralRewardEmail(referrerId: string) {
const supabase = createClient()
const { data: profile } = await supabase
.from('profiles')
.select('email, display_name')
.eq('id', referrerId)
.single()
if (!profile?.email) return
await resend.emails.send({
from: 'masato@masatoman.net',
to: profile.email,
subject: '🎉 紹介報酬が届きました — 来月1ヶ月分が無料になります',
html: `
<p>${profile.display_name ?? 'こんにちは'} さん、</p>
<p>あなたが紹介した方が Claude Crew Lab の有料プランに登録しました!</p>
<p>お礼として、<strong>来月のサブスクリプション料金を1ヶ月無料</strong>にするクーポンをアカウントに適用しました。</p>
<p>次回の請求時に自動的に適用されます。Stripe の請求履歴でご確認ください。</p>
<p>引き続き、気に入ってもらえそうな方がいればぜひ紹介リンクをシェアしてください。</p>
<p>— masato</p>
`,
})
}
Step 5:ダッシュボードで紹介状況を表示する
ユーザーが自分の紹介リンクとその状況を確認できる UI を作ります。
// app/dashboard/referral/page.tsx
import { createClient } from '@/lib/supabase/server'
import { ensureReferralCode, buildReferralUrl } from '@/lib/referral'
export default async function ReferralPage() {
const supabase = createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) return null
const referralCode = await ensureReferralCode(user.id)
const referralUrl = buildReferralUrl(referralCode, process.env.NEXT_PUBLIC_SITE_URL!)
const { data: referrals } = await supabase
.from('referrals')
.select('status, created_at, converted_at')
.eq('referrer_id', user.id)
.order('created_at', { ascending: false })
const convertedCount = referrals?.filter(r => r.status === 'rewarded').length ?? 0
return (
<div>
<h1>紹介プログラム</h1>
<p>友達を紹介すると、転換1件につき1ヶ月無料になります。</p>
<div>
<label>あなたの紹介リンク</label>
<input readOnly value={referralUrl} />
</div>
<p>報酬獲得済み: {convertedCount} 件</p>
<table>
<thead>
<tr>
<th>ステータス</th>
<th>登録日</th>
<th>転換日</th>
</tr>
</thead>
<tbody>
{referrals?.map((r, i) => (
<tr key={i}>
<td>{r.status}</td>
<td>{new Date(r.created_at).toLocaleDateString('ja-JP')}</td>
<td>{r.converted_at ? new Date(r.converted_at).toLocaleDateString('ja-JP') : '—'}</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
で、どう稼ぐ?
リファラルプログラムを「稼ぐ」視点で整理します。
リファラル経由獲得の収益貢献の考え方
以下は 業界ベンチマークを使った試算例(実測値ではなく計算モデル)です。
ReferralHero・Viral Loops のレポートによると、リファラルプログラムを設置した SaaS では、新規サインアップのうち 10〜30% が紹介経由になるとされています(対象は初期〜成長期のツール系SaaS)。
| シナリオ | 前提 | 試算 |
|---|---|---|
| 紹介経由サインアップ率(業界参考値) | 新規の10〜30% | 例: 月10人中1〜3人 |
| 紹介経由の有料転換率(参考値) | 一般より高い傾向(知人紹介のため) | 業界一般の Free→有料転換率 3〜8% に対して 1.5〜2倍とも |
| 報酬コスト | 1ヶ月無料(¥1,980 分) | 転換1件につき最大¥1,980 |
| 紹介1件の生涯価値(LTV) | 12ヶ月継続想定の試算 | ¥1,980 × 12 = ¥23,760(試算例) |
数字はあくまで試算です。実際のリファラル率・転換率は、プロダクトの熱量・ユーザーのコミュニティとの距離感によって大きく変わります。
個人開発での優先タイミング
リファラルプログラムは「有料ユーザーが5〜10人いる段階」から始めるのが現実的です。紹介してくれる人がいなければ紹介は起きません。Free MVP で熱量の高いユーザーを先に作り、その人たちに最初の紹介を依頼するのが筆者の想定する順序です。
実装コスト目安:
- DB 設計 + 紹介コード発行: 1〜2時間
- サインアップ追跡: 1〜2時間
- Stripe クーポン報酬付与: 2〜3時間
- Resend 通知メール: 30分〜1時間
- ダッシュボード UI: 1〜2時間
合計6〜10時間で、広告費ゼロの自己拡散獲得チャネルが完成します。
Claude Crew Lab との連携
Standard(¥1,980/月)のローンチ後、最初の10〜20人の有料ユーザーを獲得したタイミングでリファラルプログラムを設置する予定です。Free MVP ユーザーの中から熱量が高い方にまず紹介を依頼し、その結果を Build in Public で公開していく計画です。
関連記事
個人開発SaaSのLTV設計2026
アップセル×リテンションで1ユーザーから最大収益を引き出す実装ガイド
個人開発SaaSの無料→有料転換設計2026
Free→有料転換を仕組み化する実践ガイド
個人開発のメール自動化2026
Resend+Supabaseで無料→有料転換を仕組み化する実践ガイド
Claude Crew Lab Free — 毎月の実験記録をメールで
Claude Code × 個人開発のリアルな事故・発見・SaaS アイデアを毎月第1月曜にお届け。登録で「収益化チェックリスト 15 項目」を無料プレゼント。
個人開発の実験ログを月1回、無料で
失敗 / 実数字 / 仮説 / 次に試すこと。売れた話だけでなく売れなかった理由も共有します。
この記事が役に立ったらシェア