Prerequisites
- A Paddle account (paddle.com). Approval can take a few business days — they’ll ask for company info and a website URL.
- A sandbox account (sandbox-vendors.paddle.com) — gets you going while production review is pending.
- Postgres up, schema pushed.
1. Set the active provider
Insrc/config/site.ts:
2. Create products + prices in Paddle
In the Paddle dashboard (or sandbox dashboard): Catalog → Products → New product. Each product can have multiple prices (one-time vs recurring, different currencies, etc.). Copy thepri_… ID for each price you’ll expose.
For the vibestrap scaffold itself you need a promo and standard price
(both one-time).
3. Get your API key, client token, and webhook secret
- API key: Developer Tools → Authentication → API keys. Use a server-side key (secret).
- Client token: Developer Tools → Authentication → Client-side tokens. Public, used by Paddle.js in the browser.
- Webhook secret: Developer Tools → Notifications → New destination → the Notification key for that destination. This is not your API key — see pitfalls below.
4. Set env vars
In.env.local (variable names match src/env.ts):
NEXT_PUBLIC_PADDLE_ENV controls both server-side SDK env (Environment.sandbox
vs Environment.production) and the client-side checkout endpoint. Keeping
them in lockstep is mandatory.
5. Configure the webhook destination
Paddle calls webhook destinations “Notifications”. Add one:- URL:
https://your-domain.com/api/webhooks/paddle - Events (minimum):
transaction.completed,subscription.activated,subscription.updated,subscription.canceled. - Copy the destination’s Notification key — this is your
PADDLE_WEBHOOK_SECRET.
localhost:3000 via ngrok or cloudflared and use
that URL.
Verify it works
- Set
NEXT_PUBLIC_PADDLE_ENV=sandboxand use sandbox keys. pnpm dev, open/pricing, click checkout — Paddle’s hosted checkout should open.- Pay with a Paddle test card (e.g.
4242 4242 4242 4242with the sandbox environment, any future expiry, CVC100). - Watch your terminal — the webhook route should log a
transaction.completedevent being processed. - Check the database:
Common pitfalls
- Notification key vs API key confusion — these are completely separate credentials. The API key authenticates your server calls; the Notification key signs incoming webhooks. Mixing them up means signatures never verify.
- Sandbox / production env mismatch — if
NEXT_PUBLIC_PADDLE_ENV=productionbut you used a sandbox API key (or vice versa), the SDK silently talks to the wrong API and checkout creation fails with a vague auth error. - Async webhook unmarshal — Paddle’s
webhooks.unmarshalisasync(it does an HMAC compare under the hood). Don’tawaitit inside a synchronous callback or you’ll lose the event. - Price IDs vary per environment — sandbox
pri_…IDs don’t exist in production. You’ll need a separate set of env vars per environment, or a mapping table. - No hosted return URL on portal —
paddle.customerPortalSessions.createdoesn’t accept areturnUrl; the customer has to navigate back via your app’s chrome.
Official docs
- Paddle Developer Portal: developer.paddle.com
- Webhooks reference: developer.paddle.com/webhooks/overview
- Paddle.js (client): developer.paddle.com/paddlejs/overview
- Sandbox dashboard: sandbox-vendors.paddle.com