Payments
Overview
We use Polar as our payments. Polar is modern payment provider that supports bunch of features out of the box like subscriptions, webhooks, entitlements and more.
Setting Up Polar
- Create polar account
- Create sandbox API keys for testing
- Add API keys to your NuxtStart project configuration
Syncing Polar Data
We sync Polar products, orders and subscriptions to our database to make it available for our application.
Webhooks
We rely on Polar's webhooks to keep our database in sync with any changes happening in Polar. Whenever a relevant event happens in Polar (like new purchase, subscription update etc), Polar will send a webhook to our backend with the details of the event. We have webhook handlers set up to process these webhooks and update our database accordingly.
In critical events like new purchase or subscription plan change, we don't rely solely on webhooks as they may get delayed. Instead, we manually fetch the latest data from Polar API right after the action to ensure our database is updated in near real-time. For non-critical updates, we can rely on webhooks alone.
Products
As mentioned about Products are synced from Polar to our database via webhooks. However, if you already have products created on Polar before setting up NuxtStart & Webhooks, you can use syncPolarProducts nitro task to sync all existing products from Polar to your database. Note that, This is development only, for production we recommend you create products after deploying your project or make minor changes to existing products to trigger webhooks to sync data.
Using syncPolarProducts Nitro Task
While in development, easiest way to trigger nitro task is to use Nuxt Devtools. Nuxt Devtools already installed and navigate to Server Tasks tab either by clicking on three dots in the left sidebar and selecting Server Tasks or by using Command Palette (Cmd + K or Ctrl + K) and searching for Server Tasks. Once you're in Nitro Tasks tab, you should see syncPolarProducts task listed there. Once task is selected simply click on the Run button on right to execute the task. This will fetch all existing products from Polar and sync them to your database. You can also view list of sync tasks in output.
Orders
TBD
Subscriptions
TBD
Listening to Polar Webhooks
To listen to Polar webhooks, we already have webhook handlers set up in our backend. You can find them in server/api/webhooks/polar directory. However to receive webhooks in development, you need to expose your local server to the internet using tools like ngrok or localtunnel.
If you want free solution, you can use LocalTunnel. Check out our blog on Top 3 Free ngrok Alternatives for more details.
Fetching Data from Polar
We rely on Polar's webhooks to populate and update payment related data including products, orders and subscriptions. Whenever you create or update product in Polar, the changes will be reflected in NuxtStart database automatically via webhooks. Similarly, when a user makes a purchase or updates their subscription, the relevant data will be updated in NuxtStart.
Polar webhooks sends data within few seconds of the event happening, so you can expect near real-time updates in your NuxtStart application. For events like new purchase & plan changes we manually hit Polar API to fetch the latest data to ensure everything is in sync instead of waiting for webhook to arrive. For example, If a user upgrades their subscription plan, webhook may deliver within few seconds but until then user may try to access new plan features. To avoid such issues we fetch latest subscription data right after plan change. Same goes for new purchases.
We use Pinia store to manage payment related state in our NuxtStart application. It has plenty of helpful methods to sync data between Polar and NuxtStart database and also to fetch data from Database to Pinia store. This allows you to create seamless payment experiences in your NuxtStart application.
How Guest Checkout Works
flowchart TD
%% ===== Pre-auth Purchase Flow =====
subgraph G1["Guest Purchase (Pre-Auth)"]
direction TB
A1["User completes checkout"]
A2["Payment provider (Polar) creates order & subscription"]
A3["Webhook received by backend"]
A4["Order stored with userId as null
and polarCustomerId from order.customer.id"]
A1 --> A2 --> A3 --> A4
end
%% ===== Signup Flow =====
subgraph G2["User Signup via BetterAuth"]
direction TB
B1["User signs up with email"]
B2["BetterAuth creates user record"]
B3["BetterAuth BeforeCreate / BeforeSignUp Hook"]
B4["Lookup Polar customer by email"]
B5{"Polar customer exists?"}
B6["Attach polarCustomerId to user"]
B7["Continue signup"]
B1 --> B2 --> B3 --> B4 --> B5
B5 -- No --> B7
B5 -- Yes --> B6 --> B7
end
%% ===== Post-Signup Linking =====
subgraph G3["Post-Signup Data Linking"]
direction TB
C1["BetterAuth AfterCreate / AfterSignUp Hook"]
C2{"user.polarCustomerId present?"}
C3["Find guest orders by polarCustomerId"]
C4["Update orders: set userId"]
C5["Find guest subscriptions by polarCustomerId"]
C6["Update subscriptions: set userId"]
C7["User now owns historical purchases"]
C1 --> C2
C2 -- No --> C7
C2 -- Yes --> C3 --> C4 --> C5 --> C6 --> C7
end
%% ===== Relationships (Linking Nodes forces Vertical Layout) =====
%% Linking the last node of G1 to the first of G2
A4 --> B1
%% Linking the last node of G2 to the first of G3
B7 --> C1
Tips
- Do not rely on webhooks alone as it may get delayed. Only use them for non-critical updates. For critical updates like new purchase or plan change, manually fetch latest data from Polar API right after the action.
- Webhook order of delivery is not guaranteed. For example,
order.paidmay arrive beforeorder.created. - Ensure idempotency in webhook handlers to avoid duplicate processing.
Key Decisions
- We store
polarCustomerIdin our user records whenever new user is registered by fetching Polar customer by email. This allows us to link any historical guest purchases (specifically for guest checkouts) made before sign up to the newly created user account. Hence, we don't have to listen forcustomer.createdwebhook which may never arrive for guest checkouts.

