Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ Server-side secrets (not in `.env`):
| Secret | Purpose |
|--------|---------|
| `REVENUECAT_WEBHOOK_SECRET` | Authenticates RevenueCat webhook requests |
| `REVENUECAT_SECRET_API_KEY` | Admin API key (used by `delete-account` and `rc-entitlement-drift-check`) |
| `REVENUECAT_SECRET_API_KEY` | RC v1 secret key (used by `delete-account` only) |
| `REVENUECAT_SECRET_API_KEY_V2` | RC v2-scoped secret key (used by `rc-entitlement-drift-check`). Issue separately in RC dashboard with `customer_information:customers:read` + `customer_information:entitlements:read` scopes. |
| `REVENUECAT_PROJECT_ID` | RC project id (used by `rc-entitlement-drift-check`) |
| `RC_DRIFT_CHECK_INVOKE_SECRET` | Bearer for pg_cron → `rc-entitlement-drift-check`; must mirror `vault.secrets.rc_drift_check_invoke_secret`. See `docs/payments.md` → "Drift Health Check" for the first-time setup and rotation runbook. |

Expand Down
2 changes: 1 addition & 1 deletion docs/edge-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ A table-row mutex on `public.drift_check_runs` (default-deny RLS) prevents overl
### Secrets Required

- `RC_DRIFT_CHECK_INVOKE_SECRET` — bearer that pg_cron uses to invoke this function; must mirror `vault.secrets.rc_drift_check_invoke_secret`
- `REVENUECAT_SECRET_API_KEY` — RC v2 admin key (shared with `delete-account`)
- `REVENUECAT_SECRET_API_KEY_V2` — RC v2-scoped secret key with `customer_information:customers:read` + `customer_information:entitlements:read`. Distinct from `delete-account`'s v1 `REVENUECAT_SECRET_API_KEY`; v1 keys are rejected by RC v2 endpoints.
- `REVENUECAT_PROJECT_ID` — RC project id (`proj18594bd9`)

### Tables Written
Expand Down
17 changes: 13 additions & 4 deletions docs/payments.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,18 @@ After both have changed and the vault row is verified, manually fire one run to
**First-time setup (only once per environment):**

```bash
# 1. Generate and set the function env var
# 1. Generate and set the function env vars
INVOKE_SECRET=$(openssl rand -hex 32)
supabase secrets set RC_DRIFT_CHECK_INVOKE_SECRET="$INVOKE_SECRET"
supabase secrets set REVENUECAT_PROJECT_ID="proj18594bd9"
# REVENUECAT_SECRET_API_KEY is already set (used by delete-account)

# REVENUECAT_SECRET_API_KEY_V2 is a SEPARATE key from REVENUECAT_SECRET_API_KEY.
# delete-account uses the v1 key on /v1/subscribers; this function uses v2
# endpoints which only accept a v2-scoped key. Issue a new v2 secret key in
# the RC dashboard (Project Settings → API Keys) with permissions:
# customer_information:customers:read
# customer_information:entitlements:read
supabase secrets set REVENUECAT_SECRET_API_KEY_V2="<v2-secret-key>"

# 2. Deploy the function
supabase functions deploy rc-entitlement-drift-check --no-verify-jwt
Expand Down Expand Up @@ -205,7 +212,8 @@ The cron job will start firing at the next `:17 mod 6h` UTC mark.

- **Edge Function secrets** (set via dashboard or CLI):
- `REVENUECAT_WEBHOOK_SECRET` — must match the Bearer token configured in RevenueCat webhook settings
- `REVENUECAT_SECRET_API_KEY` — RC v2 REST API key, used by `delete-account` and `rc-entitlement-drift-check`
- `REVENUECAT_SECRET_API_KEY` — RC **v1** secret key, used by `delete-account` against `/v1/subscribers`
- `REVENUECAT_SECRET_API_KEY_V2` — RC **v2**-scoped secret key (separate from the v1 key), used by `rc-entitlement-drift-check` against `/v2/projects/...` endpoints. Required scopes: `customer_information:customers:read`, `customer_information:entitlements:read`.
- `REVENUECAT_PROJECT_ID` — RC project id, used by `rc-entitlement-drift-check`
- `RC_DRIFT_CHECK_INVOKE_SECRET` — Bearer for pg_cron → `rc-entitlement-drift-check`; must mirror `vault.secrets.rc_drift_check_invoke_secret`
- `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY` — auto-injected, no manual setup needed
Expand All @@ -225,7 +233,8 @@ The cron job will start firing at the next `:17 mod 6h` UTC mark.
| `EXPO_PUBLIC_REVENUECAT_API_KEY` | `.env` + EAS secrets | RevenueCat Apple API key, read at build time |
| `EXPO_PUBLIC_REVENUECAT_GOOGLE_API_KEY` | `.env` + EAS secrets | RevenueCat Google API key, read at build time |
| `REVENUECAT_WEBHOOK_SECRET` | Supabase Edge Function secrets | Webhook auth, server-side only |
| `REVENUECAT_SECRET_API_KEY` | Supabase Edge Function secrets | RC v2 REST API admin key (`delete-account`, `rc-entitlement-drift-check`) |
| `REVENUECAT_SECRET_API_KEY` | Supabase Edge Function secrets | RC v1 secret key (`delete-account` only — used against `/v1/subscribers`) |
| `REVENUECAT_SECRET_API_KEY_V2` | Supabase Edge Function secrets | RC v2-scoped secret key (`rc-entitlement-drift-check` — used against `/v2/projects/...` endpoints). Required scopes: `customer_information:customers:read`, `customer_information:entitlements:read`. |
| `REVENUECAT_PROJECT_ID` | Supabase Edge Function secrets | RC project id, used by `rc-entitlement-drift-check` |
| `RC_DRIFT_CHECK_INVOKE_SECRET` | Supabase Edge Function secrets **and** Supabase Vault | Bearer that pg_cron uses to invoke `rc-entitlement-drift-check`; rotate in both places together |

Expand Down
5 changes: 4 additions & 1 deletion supabase/functions/rc-entitlement-drift-check/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ serve(async (req) => {

try {
const projectId = Deno.env.get("REVENUECAT_PROJECT_ID");
const rcKey = Deno.env.get("REVENUECAT_SECRET_API_KEY");
// RC v2 endpoints (list-customers, list-entitlements) require a
// v2-scoped secret key. delete-account uses the v1 key on /v1/subscribers;
// they're separate env vars so neither can break the other.
const rcKey = Deno.env.get("REVENUECAT_SECRET_API_KEY_V2");
if (!projectId || !rcKey) {
throw new Error("missing_revenuecat_env");
}
Expand Down