{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreihytjqkwzmwvofuaurj3xt6gbzzxew4ou25bbcvwrsruuvw75ew3u",
"uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mouslvlioq32"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreidaytinmiq46umhwisfxrkvzovdal4mlv37uwu4eljudnnfejqoaq"
},
"mimeType": "image/webp",
"size": 80588
},
"path": "/cekuu35/how-i-add-polar-merchant-of-record-stripe-to-any-nextjs-app-in-10-minutes-without-an-sdk-4o8l",
"publishedAt": "2026-06-22T11:36:03.000Z",
"site": "https://dev.to",
"tags": [
"nextjs",
"stripe",
"payments",
"webdev",
"Polar + Stripe Kit for Next.js"
],
"textContent": "If you're a solo dev outside the US, \"just add Stripe\" is rarely just. Stripe doesn't onboard sellers directly in a lot of countries, so you reach for a Merchant of Record (MoR) like Polar or Lemon Squeezy — and then you hit the _second_ wall: the official SDK throws `fetch failed` the moment you deploy to a serverless platform like Vercel.\n\nI shipped paid checkout on two live products this way and got tired of re-solving the same problems. Here's the pattern that actually works in production.\n\n### 1. Skip the SDK. Use native `fetch`.\n\nMost payment SDKs assume a long-lived Node server. On serverless, the bundled HTTP client and keep-alive sockets misbehave and you get opaque `fetch failed` errors. The fix is boring and bulletproof: call the REST API directly.\n\n\n\n // lib/polar.ts\n const POLAR_API = \"https://api.polar.sh/v1\";\n\n export async function createCheckout(productId: string, successUrl: string) {\n const res = await fetch(`${POLAR_API}/checkouts/`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${process.env.POLAR_ACCESS_TOKEN!}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ product_id: productId, success_url: successUrl }),\n });\n if (!res.ok) throw new Error(`Polar checkout failed: ${res.status}`);\n return res.json();\n }\n\n\nNo SDK, no version drift, no serverless surprises. Works on Vercel, Cloudflare, anywhere `fetch` exists.\n\n### 2. Verify webhooks yourself (it's ~15 lines)\n\nDon't trust a payment callback you didn't sign-check. Polar uses the Standard Webhooks spec (base64 HMAC); Stripe uses its own `t=,v1=` scheme. Both are a `crypto.timingSafeEqual` away:\n\n\n\n // lib/verify.ts\n import crypto from \"node:crypto\";\n\n export function verifyPolar(payload: string, signature: string, secret: string) {\n const expected = crypto\n .createHmac(\"sha256\", Buffer.from(secret, \"base64\"))\n .update(payload)\n .digest(\"base64\");\n return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));\n }\n\n\n`timingSafeEqual` (not `===`) is the part people skip — and it's exactly the part that matters for a signature check.\n\n### 3. Verify payment status server-side on the success page\n\nThe redirect to your `success_url` is **not** proof of payment — a user can just visit that URL. Re-fetch the checkout server-side before you grant access or trigger a download:\n\n\n\n const checkout = await getCheckout(checkoutId);\n const PAID = [\"succeeded\", \"confirmed\"];\n if (!PAID.includes(checkout.status)) redirect(\"/pricing\");\n\n\n### 4. One source of truth for tiers\n\nKeep your subscription tiers in one typed object and generate the pricing UI from it. Adding a plan becomes a one-line change, not a five-file hunt.\n\n\n\n export const TIERS = {\n pro: { name: \"Pro\", price: 19, productId: process.env.POLAR_PRO_ID! },\n business: { name: \"Business\", price: 49, productId: process.env.POLAR_BIZ_ID! },\n } as const;\n\n\n### Why this matters\n\nThis is the unglamorous 20% of payments that eats 80% of the time: serverless `fetch` failures, signature verification, the success-page trust gap. None of it is hard once you've seen it — but the first time costs you a weekend.\n\nI packaged the full working version — checkout, signed webhooks for both Polar and Stripe, tiers, and a server-verified success page — as a drop-in Next.js kit so you don't have to re-derive it. It's the exact code running on my own live products.\n\n**→ Polar + Stripe Kit for Next.js**\n\nEither way, the four patterns above are yours to copy. Ship the payment, not the yak-shave.",
"title": "How I add Polar (Merchant of Record) + Stripe to any Next.js app in 10 minutes — without an SDK"
}