<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ape Collective</title>
    <description>The latest articles on DEV Community by Ape Collective (@ape_collective_).</description>
    <link>https://dev.to/ape_collective_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3984498%2F96691135-95cf-4a07-9a19-220add4dee1b.png</url>
      <title>DEV Community: Ape Collective</title>
      <link>https://dev.to/ape_collective_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ape_collective_"/>
    <language>en</language>
    <item>
      <title>How to verify Gumroad license keys in an Electron app (and the 3 gotchas nobody warns you about)</title>
      <dc:creator>Ape Collective</dc:creator>
      <pubDate>Mon, 15 Jun 2026 00:18:51 +0000</pubDate>
      <link>https://dev.to/ape_collective_/how-to-verify-gumroad-license-keys-in-an-electron-app-and-the-3-gotchas-nobody-warns-you-about-38gn</link>
      <guid>https://dev.to/ape_collective_/how-to-verify-gumroad-license-keys-in-an-electron-app-and-the-3-gotchas-nobody-warns-you-about-38gn</guid>
      <description>&lt;p&gt;If you sell a desktop app on Gumroad, it hands every buyer a license key. But Gumroad stops there — checking that key inside your app is entirely up to you. Here's how to do it properly in Node/Electron, plus the three traps that catch almost everyone.&lt;/p&gt;

&lt;p&gt;We'll use gumroad-license-lite, a tiny, zero-dependency, MIT-licensed helper (you can npm install it or just copy its ~120 lines).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Turn on license keys in Gumroad&lt;br&gt;
On your product, enable "Generate a unique license key per sale," then grab your product_id (in the product settings / API). Every buyer now gets a key on their receipt.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify a key&lt;br&gt;
const { verifyGumroadLicense } = require('gumroad-license-lite');&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;const result = await verifyGumroadLicense({&lt;br&gt;
  productId: 'YOUR_PRODUCT_ID',&lt;br&gt;
  licenseKey,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;if (result.valid) {&lt;br&gt;
  unlockApp(result.email);&lt;br&gt;
}&lt;br&gt;
result.valid is true only if the key is real and the sale wasn't refunded, disputed, or a cancelled subscription — not just "does this key exist," which is gotcha #1 below.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Gate your app on launch
You don't want to call Gumroad on every launch, and you want the app to survive a flaky connection. LicenseGate caches the result and re-checks periodically:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;const path = require('node:path');&lt;br&gt;
const { LicenseGate } = require('gumroad-license-lite');&lt;/p&gt;

&lt;p&gt;const gate = new LicenseGate({&lt;br&gt;
  productId: 'YOUR_PRODUCT_ID',&lt;br&gt;
  storageFile: path.join(app.getPath('userData'), 'license.json'),&lt;br&gt;
  recheckEveryDays: 3,&lt;br&gt;
  offlineGraceDays: 14,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;// on your activation screen:&lt;br&gt;
await gate.activate(userEnteredKey);&lt;/p&gt;

&lt;p&gt;// on every launch:&lt;br&gt;
const status = await gate.check();&lt;br&gt;
if (!status.licensed) showActivationScreen();&lt;br&gt;
The 3 gotchas&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;"Valid" isn't the same as "exists." A refunded or charged-back sale still has a real, working key. If you only check that the key exists, people can buy, copy the key, refund, and keep your app forever. Always check the refund / dispute / subscription flags (the helper above does this for you).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The uses counter is global, not per-device. Gumroad tracks a uses count, but it can't tell you which machines — so you can't actually enforce "3 devices per license." One key can quietly unlock a hundred installs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Offline means locked out. A pure online check fails the moment your user has no internet — on a plane, on hotel wifi — and your paying customer can't open the app. A local cache (like above) softens this, but a plain JSON cache is editable, so it's friction-reduction, not real protection.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you outgrow the basic check&lt;br&gt;
An online check is genuinely fine for a lot of apps. But when those three gotchas start costing real money, you need cryptographically signed, device-bound tokens that verify offline and automatic lockout on refunds and chargebacks. That's a meaningfully bigger build — a signing server, client SDKs, a refund webhook — so I packaged it as KeyGate for people who'd rather not assemble it from scratch. Either way, the free tool above is the right place to start.&lt;/p&gt;

&lt;p&gt;TL;DR&lt;br&gt;
Enable license keys and grab your product_id&lt;br&gt;
Check validity, not just existence&lt;br&gt;
Cache for offline use — but know its limits&lt;br&gt;
Free tool: &lt;a href="https://github.com/apecollective/gumroad-license-lite" rel="noopener noreferrer"&gt;https://github.com/apecollective/gumroad-license-lite&lt;/a&gt;&lt;br&gt;
How do you handle licensing for the apps you sell on Gumroad? Genuinely curious what others are doing.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>electron</category>
      <category>tutorial</category>
      <category>gumroad</category>
    </item>
  </channel>
</rss>
