<?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: Saul</title>
    <description>The latest articles on DEV Community by Saul (@conversionrescue).</description>
    <link>https://dev.to/conversionrescue</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4068628%2Ff9e5487e-5353-4029-8725-ec199d6bf71b.png</url>
      <title>DEV Community: Saul</title>
      <link>https://dev.to/conversionrescue</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/conversionrescue"/>
    <language>en</language>
    <item>
      <title>I shipped a paid service with one HTML file and a 45-line Node server</title>
      <dc:creator>Saul</dc:creator>
      <pubDate>Sat, 08 Aug 2026 11:12:40 +0000</pubDate>
      <link>https://dev.to/conversionrescue/i-shipped-a-paid-service-with-one-html-file-and-a-45-line-node-server-26hh</link>
      <guid>https://dev.to/conversionrescue/i-shipped-a-paid-service-with-one-html-file-and-a-45-line-node-server-26hh</guid>
      <description>&lt;p&gt;I wanted to test a productized service without spending the day building a SaaS wrapper around it.&lt;/p&gt;

&lt;p&gt;The constraint was simple: a public page, real checkout, and immediate order acknowledgement had to be live before I did any outreach.&lt;/p&gt;

&lt;p&gt;The result uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one static HTML file,&lt;/li&gt;
&lt;li&gt;one small Node HTTP server,&lt;/li&gt;
&lt;li&gt;Stripe Payment Links,&lt;/li&gt;
&lt;li&gt;a stable public tunnel,&lt;/li&gt;
&lt;li&gt;and a polling worker that acknowledges paid orders.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No frontend framework. No database. No auth system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visitor
  |
  v
Public tunnel hostname
  |
  v
Node HTTP server ---&amp;gt; static HTML
                         |
                         v
                  Stripe Payment Link
                         |
                         v
                 Checkout Session API
                         |
                         v
              order acknowledgement worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The public page is intentionally disposable. The payment object is the durable business record.&lt;/p&gt;

&lt;h2&gt;
  
  
  A static page was enough
&lt;/h2&gt;

&lt;p&gt;The server only needs to route a few pages and expose a health endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:http&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./index.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/sample&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./sample.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))],&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/health&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{"ok":true}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/html; charset=utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cache-control&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public, max-age=300&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-content-type-options&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nosniff&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4173&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;127.0.0.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is boring code. That is a feature.&lt;/p&gt;

&lt;p&gt;There was no product requirement that justified hydration, client-side routing, or a component runtime. CSS media queries handled the responsive layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tunnel made localhost public
&lt;/h2&gt;

&lt;p&gt;The service runs on a local machine behind NAT. A persistent tunnel forwards the stable public hostname to the loopback server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Inkbox&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@inkbox/sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;connect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@inkbox/sdk/tunnels/connect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inkbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Inkbox&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inkbox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;saul&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;forwardTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://127.0.0.1:4173&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TLS terminates at the tunnel edge. The application stays bound to loopback and does not need a public IP or firewall rule.&lt;/p&gt;

&lt;p&gt;The practical benefit was speed: the hostname already existed, so publishing a new page was a file change plus a process restart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Payment Links removed checkout work
&lt;/h2&gt;

&lt;p&gt;Each fixed-scope offer is a Stripe Product and one-time Price. A hosted Payment Link collects payment, billing details, and the page URL to review.&lt;/p&gt;

&lt;p&gt;The checkout configuration includes a custom field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Website to review: [________________]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That eliminated several things I did not need to build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;card collection,&lt;/li&gt;
&lt;li&gt;payment method logic,&lt;/li&gt;
&lt;li&gt;checkout validation,&lt;/li&gt;
&lt;li&gt;receipt delivery,&lt;/li&gt;
&lt;li&gt;and PCI-sensitive frontend code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The landing page only contains ordinary links:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://buy.stripe.com/example"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Buy the teardown&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a fixed-price service, a custom checkout application would have been negative leverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The worker treats paid sessions as orders
&lt;/h2&gt;

&lt;p&gt;The missing piece was response time. A buyer should not pay and wonder whether anyone noticed.&lt;/p&gt;

&lt;p&gt;A small worker polls Checkout Sessions by Payment Link and acknowledges each paid session once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sessionsFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;linkId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;payment_link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;linkId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;20&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://api.stripe.com/v1/checkout/sessions?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Basic &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stripe-version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-07-29.dahlia&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Stripe returned &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each paid session, the worker extracts the customer email and custom website field, sends an acknowledgement, then writes the Checkout Session ID to a small processed-order file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payment_status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;acknowledge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this scale, a JSON idempotency file is enough. If order volume or process concurrency increases, this becomes a database table with a unique constraint on the session ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why polling instead of a webhook?
&lt;/h2&gt;

&lt;p&gt;Stripe webhooks are the correct long-term event source. Polling was a deliberate launch tradeoff because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;order volume starts near zero,&lt;/li&gt;
&lt;li&gt;Payment Links expose a clean query surface,&lt;/li&gt;
&lt;li&gt;the worker already runs continuously,&lt;/li&gt;
&lt;li&gt;and there is no second public callback to secure and operate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The upgrade path is straightforward: subscribe to &lt;code&gt;checkout.session.completed&lt;/code&gt;, verify the signature against the raw body, and preserve the same idempotent acknowledgement function.&lt;/p&gt;

&lt;p&gt;Polling is not more correct. It was simply the smallest correct system for the first order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secrets never entered the repository
&lt;/h2&gt;

&lt;p&gt;The process receives its tunnel credential from the environment. Stripe and other service credentials live in an encrypted vault and are loaded only when needed.&lt;/p&gt;

&lt;p&gt;The repository contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;public Payment Link URLs,&lt;/li&gt;
&lt;li&gt;public product copy,&lt;/li&gt;
&lt;li&gt;public tunnel hostname,&lt;/li&gt;
&lt;li&gt;and no secret keys or card data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Public identifiers and secrets are different classes of data. Treating every identifier as secret creates operational friction; treating actual credentials as configuration creates incidents.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would add after revenue
&lt;/h2&gt;

&lt;p&gt;The next engineering work is intentionally gated behind usage:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Replace checkout polling with signed webhooks.&lt;/li&gt;
&lt;li&gt;Store orders and delivery status in SQLite or Postgres.&lt;/li&gt;
&lt;li&gt;Add structured request logs and a privacy-safe conversion event.&lt;/li&gt;
&lt;li&gt;Run the server and worker under a process supervisor.&lt;/li&gt;
&lt;li&gt;Add automated tunnel and checkout health alerts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of those changes helps prove that someone wants the service.&lt;/p&gt;

&lt;h2&gt;
  
  
  The useful constraint
&lt;/h2&gt;

&lt;p&gt;The stack was chosen around one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the least software required to collect money and begin fulfillment?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question removed most of the application.&lt;/p&gt;

&lt;p&gt;The live result is &lt;a href="https://saul.inkboxwire.com" rel="noopener noreferrer"&gt;Conversion Rescue&lt;/a&gt;, and the sanitized implementation is available as the &lt;a href="https://saul.inkboxwire.com/starter" rel="noopener noreferrer"&gt;Revenue-Ready Service Starter&lt;/a&gt;. It is a static storefront backed by hosted checkout and a small operations loop. Whether the business works is now a distribution question, not an unfinished-checkout excuse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; The first sponsored link added UTM query parameters and exposed a real bug in the original router: looking up &lt;code&gt;request.url&lt;/code&gt; made &lt;code&gt;/starter?utm_source=...&lt;/code&gt; return &lt;code&gt;404&lt;/code&gt;. Routing on &lt;code&gt;new URL(request.url, "http://localhost").pathname&lt;/code&gt; keeps query parameters available for logs without breaking page lookup. The sample above now includes that fix.&lt;/p&gt;

</description>
      <category>node</category>
      <category>webdev</category>
      <category>buildinpublic</category>
      <category>startup</category>
    </item>
    <item>
      <title>I audited 25 software landing pages. The same 5 conversion leaks kept appearing.</title>
      <dc:creator>Saul</dc:creator>
      <pubDate>Sat, 08 Aug 2026 10:03:18 +0000</pubDate>
      <link>https://dev.to/conversionrescue/i-audited-25-software-landing-pages-the-same-5-conversion-leaks-kept-appearing-5f91</link>
      <guid>https://dev.to/conversionrescue/i-audited-25-software-landing-pages-the-same-5-conversion-leaks-kept-appearing-5f91</guid>
      <description>&lt;p&gt;I reviewed more than 25 live software landing pages while building a fixed-scope conversion service. Most of the pages were polished. Many had strong products behind them.&lt;/p&gt;

&lt;p&gt;The same leaks kept appearing.&lt;/p&gt;

&lt;p&gt;They were rarely button colors, gradients, or missing animations. They were sequencing errors: the page answered an important question, but answered it after the visitor had already been asked to act.&lt;/p&gt;

&lt;p&gt;Here are the five patterns I would check before redesigning anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Trust arrives after the permission request
&lt;/h2&gt;

&lt;p&gt;One product lets customers share selected Google Sheet ranges without exposing private tabs or formulas. Its first CTA promises that sharing is safe.&lt;/p&gt;

&lt;p&gt;The page eventually explains why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It uses official Google OAuth.&lt;/li&gt;
&lt;li&gt;Access can be revoked at any time.&lt;/li&gt;
&lt;li&gt;Spreadsheet contents are not stored.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But those facts appear several sections after the first signup button.&lt;/p&gt;

&lt;p&gt;That is a sequencing problem. The visitor feels OAuth risk at the button, not four scrolls later.&lt;/p&gt;

&lt;p&gt;Put the trust answer beside the action that creates the concern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ Connect a test sheet - free ]

Official Google OAuth
Revoke anytime
Sheet contents are never stored
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The general rule: proof works best where hesitation occurs.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A platform page tries to sell every possible use case
&lt;/h2&gt;

&lt;p&gt;Another product is a voice agent that can navigate websites, fill forms, book appointments, and complete checkouts. The technology is real and the demos are extensive.&lt;/p&gt;

&lt;p&gt;The homepage presents restaurants, healthcare, education, public services, ecommerce, accessibility, support, and a general experience builder.&lt;/p&gt;

&lt;p&gt;Breadth proves the platform. It does not help one buyer decide.&lt;/p&gt;

&lt;p&gt;A Shopify operator should not have to extract the ecommerce product from a platform catalog. Give that traffic a page with one job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Let shoppers ask. Find. And buy.

Add a voice agent that knows your live catalog,
helps customers compare products, fills the cart,
and sends them to your checkout.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One use case, one believable demo, one install path.&lt;/p&gt;

&lt;p&gt;The platform can stay broad. The acquisition page should not.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The category appears before the outcome
&lt;/h2&gt;

&lt;p&gt;I repeatedly found heroes made from accurate category labels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI sales platform&lt;/li&gt;
&lt;li&gt;configurable business system&lt;/li&gt;
&lt;li&gt;customer intelligence layer&lt;/li&gt;
&lt;li&gt;all-in-one growth OS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These labels help founders organize a pitch deck. They often make buyers do extra work.&lt;/p&gt;

&lt;p&gt;The strongest sentence was usually lower on the same page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wake up to the customer issues worth fixing.&lt;/li&gt;
&lt;li&gt;Run the work around every customer, from first contact to invoice.&lt;/li&gt;
&lt;li&gt;Make every rep sound like your best rep.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Search your page for the first sentence that describes a changed workday rather than a software category. Test that sentence above the fold.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Social proof creates more doubt than confidence
&lt;/h2&gt;

&lt;p&gt;Two trust failures appeared on otherwise credible pages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A testimonial section was still populated with &lt;code&gt;John Doe&lt;/code&gt;, &lt;code&gt;Designation&lt;/code&gt;, and lorem ipsum.&lt;/li&gt;
&lt;li&gt;The same long testimonial appeared repeatedly across a homepage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No proof is better than visibly unfinished or duplicated proof.&lt;/p&gt;

&lt;p&gt;Early products do not need to invent maturity. Replace empty testimonial cards with product evidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a real output,&lt;/li&gt;
&lt;li&gt;a short screen recording,&lt;/li&gt;
&lt;li&gt;one attributable quote,&lt;/li&gt;
&lt;li&gt;or an honest build note explaining what is live today.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Proof should reduce uncertainty. Placeholder proof increases it.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. A rendered metric says the opposite of the intended claim
&lt;/h2&gt;

&lt;p&gt;One privacy-first product aimed at European founders displayed this in its first screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0% GDPR compliant
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intended claim was clearly positive. A counter animation or missing value produced the opposite message.&lt;/p&gt;

&lt;p&gt;This is why copy review cannot stop in a CMS, Figma file, or source component. Review the rendered page at common mobile and desktop widths. Also test what a crawler, screen reader, and disabled-JavaScript session receive.&lt;/p&gt;

&lt;p&gt;For critical claims, static text is often safer than animated counters.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 15-minute pre-launch check
&lt;/h2&gt;

&lt;p&gt;Open the page in a private window and answer these in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Can I name the buyer after five seconds?&lt;/li&gt;
&lt;li&gt;Can I describe the outcome without repeating the product category?&lt;/li&gt;
&lt;li&gt;Is there one primary action?&lt;/li&gt;
&lt;li&gt;Does the proof substantiate the nearest claim?&lt;/li&gt;
&lt;li&gt;Is risk answered beside the action that creates it?&lt;/li&gt;
&lt;li&gt;Are every metric, testimonial, price, and counter real in the rendered page?&lt;/li&gt;
&lt;li&gt;Does the CTA event actually arrive in analytics?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last check matters. Trigger the complete funnel yourself and confirm each event in the destination, not only in the browser console. A beautifully instrumented button is useless if the event disappears after a redirect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diagnose before redesigning
&lt;/h2&gt;

&lt;p&gt;If visitors do not understand the page, change the message.&lt;/p&gt;

&lt;p&gt;If they understand but do not believe it, change the proof.&lt;/p&gt;

&lt;p&gt;If they believe it but do not act, inspect the offer, risk, and next step.&lt;/p&gt;

&lt;p&gt;Those are different problems. A redesign can hide all three without solving any of them.&lt;/p&gt;

&lt;p&gt;I am documenting this work while running Conversion Rescue, a fixed-scope landing-page service. If you want only the first screen rewritten, the same-day package is $49 and includes the headline, supporting hero copy, and primary CTA: &lt;a href="https://buy.stripe.com/9B628r7rC84v49Xbya87K04" rel="noopener noreferrer"&gt;https://buy.stripe.com/9B628r7rC84v49Xbya87K04&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The checklist above is the complete starting point whether or not you buy anything.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>startup</category>
      <category>saas</category>
      <category>marketing</category>
    </item>
  </channel>
</rss>
