When to use it
The product works locally and needs its first production environment. The steps assume Vercel for hosting; any Node.js host works if it can call the cron endpoint.
Rules to keep
SUPABASE_SERVICE_ROLE_KEY,STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRETandCRON_SECRETare server-only: never prefix them withNEXT_PUBLIC_and never commit their values.supabase/seed.sqlcontains demo data: never run it in production.- A payment is confirmed only by the signed Stripe webhook.
Steps
- Database. Create the production Supabase project, then apply the
migrations in order:
supabase link --project-ref <ref>andsupabase db push. - Auth. In Supabase, under Authentication → URL Configuration, set the Site URL and the redirect URLs to the production domain.
- Hosting. Import the repository into Vercel and copy every variable
from
.env.example:NEXT_PUBLIC_APP_URLis the public domain, without a trailing slash;CRON_SECRETis a long random value, for example fromopenssl rand -hex 32.
- Stripe. Create a webhook endpoint to
https://<domain>/api/stripe/webhookwith the eventscheckout.session.completed,checkout.session.async_payment_succeeded,checkout.session.expiredandcharge.refunded. Copy its signing secret intoSTRIPE_WEBHOOK_SECRETand redeploy. Use test keys first and switch to live keys only after the checks below. - Cron.
GET /api/cronmust run every 5–15 minutes withAuthorization: Bearer $CRON_SECRET. On Vercel add avercel.json:
Vercel sends the header automatically when{ "crons": [{ "path": "/api/cron", "schedule": "*/10 * * * *" }] }CRON_SECRETis set. Intervals shorter than a day need the Pro plan; Supabasepg_cronwithpg_netis an alternative. - Email. Verify the sending domain on Resend and set
RESEND_API_KEYandEMAIL_FROM. Without them, notifications are only logged. - First admin. Register from
/register, then promote the user in the Supabase SQL editor:update public.profiles set role = 'admin' where id = '<user uuid>';
Checks after deploy
curl -i https://<domain>/api/cronwithout the header answers401.- A paid test booking stays
pending_paymentuntil the webhook confirms it, and Stripe shows the webhook delivery as successful. - Cancelling that booking refunds it according to the service policy.
Done when
- the verification commands pass locally;
- the three checks above pass on the production domain;
- no secret appears in the repository or in client bundles.
