Creem Setup
Configure Creem products, Edge secrets, and the webhook.
Creem runs outside the desktop app. The Electron template only ships public product IDs in the renderer and server-side Edge Functions that hold the Creem API key, verify webhooks, and write Supabase entitlement rows.
Create products
Create the products you plan to sell in Creem:
| Plan in template | Creem product type | App env |
|---|---|---|
| Pro monthly | Subscription | VITE_CREEM_PRODUCT_PRO_MONTHLY=prod_... |
| Pro yearly | Subscription | VITE_CREEM_PRODUCT_PRO_YEARLY=prod_... |
| Lifetime | One-time | VITE_CREEM_PRODUCT_LIFETIME=prod_... |
Put those IDs in soar-electron/.env. They are renderer-public and safe to
ship. Do not put CREEM_API_KEY in the app .env.
Creem API selection is controlled by the Edge secret CREEM_SERVER_IDX:
| Value | API base |
|---|---|
0 | Production API (https://api.creem.io) |
1 | Test API (https://test-api.creem.io) |
Use test mode while validating checkout and webhook handling.
Set Edge secrets
Set secrets on the Supabase project:
supabase secrets set \
CREEM_API_KEY=creem_test_xxx \
CREEM_WEBHOOK_SECRET=whsec_xxx \
CREEM_SERVER_IDX=1 \
--project-ref <ref>Or copy supabase/functions/.env.example to the gitignored
supabase/functions/.env, fill it in, and upload it:
supabase secrets set --env-file supabase/functions/.env --project-ref <ref>| Secret | Used by | Notes |
|---|---|---|
CREEM_API_KEY | create-checkout, customer-portal, activate-license | Server-side Creem API key. |
CREEM_WEBHOOK_SECRET | creem-webhook | Must match the signing secret configured in Creem. |
CREEM_SERVER_IDX | All Creem functions | 0 production, 1 test. |
BILLING_SUCCESS_URL | create-checkout | Optional hosted page that bounces the browser back to the desktop app. |
Supabase injects SUPABASE_URL, SUPABASE_ANON_KEY, and
SUPABASE_SERVICE_ROLE_KEY into Edge Functions. Do not set those in the app or
in the Edge secret file.
Deploy functions
Apply the billing migration before deploying functions:
supabase db push --project-ref <ref>Then deploy:
pnpm supabase:deploy
pnpm supabase:deploy <project-ref>The helper script deploys every folder under supabase/functions/ except
_shared. It also passes --no-verify-jwt for creem-webhook; all other
functions keep JWT verification on through supabase/config.toml.
Expected functions:
| Function | JWT | Role |
|---|---|---|
create-checkout | On | Creates a Creem checkout for the signed-in user and records a pending payment row. |
customer-portal | On | Creates a Creem portal URL for the signed-in user's stored customer id. |
activate-license | On | Optional license-key activation and deactivation. |
creem-webhook | Off | Public Creem webhook; verifies creem-signature before writing entitlements. |
Verify deploys with:
supabase functions list --project-ref <ref>Configure the webhook
In Creem, add this webhook URL:
https://<ref>.supabase.co/functions/v1/creem-webhookSet the webhook signing secret to the same value you stored as
CREEM_WEBHOOK_SECRET.
The current handler processes this implemented event set:
| Event | Behavior |
|---|---|
checkout.completed | Updates the payments row matching object.request_id / order_no to status = 'success', stores Creem order/customer ids, and returns. |
subscription.paid | Upserts the signed-in user's subscriptions row from object.metadata.userId, product, customer, and subscription period data. |
Other event types are ignored today. If you need cancellation, refund, pause, or
past-due handling, extend supabase/functions/_shared/creem.ts and keep the
client read model unchanged.
Webhook trust and idempotency:
creem-webhookreads the raw request body before JSON parsing.verifyCreemSignature()recomputes the HMAC-SHA256 usingCREEM_WEBHOOK_SECRETand compares it to thecreem-signatureheader.payments.order_nois unique, so repeatedcheckout.completeddeliveries converge on the same row.subscriptions.user_idis unique, so repeatedsubscription.paiddeliveries upsert the same entitlement row.- Handler errors return
500so Creem can retry.
Checkout return page
A desktop app cannot rely on the browser redirect directly landing in the app,
so BILLING_SUCCESS_URL can point at a tiny hosted bounce page. The template
includes supabase/pay-success.sample.html.
Before hosting it, update the sample's stale soar:// link to the current
scheme:
soar-electron://billing?status=success&orderId=...Use your rebranded scheme after running pnpm rebrand --scheme.
Then set the hosted page as an Edge secret:
supabase secrets set BILLING_SUCCESS_URL=https://your-domain.com/pay-success --project-ref <ref>create-checkout appends status=success&orderId=<orderNo> before sending the
URL to Creem. If BILLING_SUCCESS_URL is unset, Creem falls back to the
product's configured redirect.
Local testing
Serve functions locally with a local Edge env file:
supabase functions serve --env-file supabase/functions/.envLocal functions run under:
http://localhost:54321/functions/v1/<name>Use supabase start too if you want the local Postgres/Auth stack. For a full
hosted sandbox checkout, remote deploys are usually simpler because Creem needs
to reach the webhook URL.
After a sandbox checkout, inspect rows:
select order_no, user_id, product_id, status, customer_id, updated_at
from public.payments
order by created_at desc
limit 10;
select user_id, product_id, product_type, status, period_end, customer_id, subscription_id
from public.subscriptions
order by updated_at desc
limit 10;Production checklist
-
VITE_BILLING_ENABLED=truein the desktop build. -
VITE_CREEM_PRODUCT_*values match the Creem environment you are using. - Billing migration is applied.
-
CREEM_API_KEY,CREEM_WEBHOOK_SECRET, andCREEM_SERVER_IDXare set as Supabase Edge secrets. -
pnpm supabase:deployhas deployed all four functions. - Creem webhook points at
https://<ref>.supabase.co/functions/v1/creem-webhook. -
BILLING_SUCCESS_URLusessoar-electron://billingor your rebranded scheme if you host the bounce page. - A sandbox checkout writes both
paymentsandsubscriptions. - You have extended webhook handling if your product needs cancellations, refunds, or past-due status changes reflected in-app.
