{
  "$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"
}