<?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: Lizard Gecko</title>
    <description>The latest articles on DEV Community by Lizard Gecko (@lizard_gecko_0126a0bcb606).</description>
    <link>https://dev.to/lizard_gecko_0126a0bcb606</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%2F4135298%2Fb914f235-4908-4bb9-bc9c-8a86c6270a87.png</url>
      <title>DEV Community: Lizard Gecko</title>
      <link>https://dev.to/lizard_gecko_0126a0bcb606</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lizard_gecko_0126a0bcb606"/>
    <language>en</language>
    <item>
      <title>My payment webhook silently failed on the first real transaction — and I built a tool to catch silent failures</title>
      <dc:creator>Lizard Gecko</dc:creator>
      <pubDate>Mon, 21 Sep 2026 08:24:24 +0000</pubDate>
      <link>https://dev.to/lizard_gecko_0126a0bcb606/my-payment-webhook-silently-failed-on-the-first-real-transaction-and-i-built-a-tool-to-catch-4i9d</link>
      <guid>https://dev.to/lizard_gecko_0126a0bcb606/my-payment-webhook-silently-failed-on-the-first-real-transaction-and-i-built-a-tool-to-catch-4i9d</guid>
      <description>&lt;p&gt;I spent four months building ApiPulse, an uptime monitor for APIs, solo, in the evenings. This week I launched it. The first thing I did was pay for it myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why test with real money
&lt;/h2&gt;

&lt;p&gt;Configuration looks correct right up until it isn't. So I made a real €5 purchase with my own card to walk the full chain: checkout, payment provider, webhook, plan activation in the app.&lt;/p&gt;

&lt;p&gt;The money went through. The payment provider confirmed it. My app still said &lt;strong&gt;Free plan&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug
&lt;/h2&gt;

&lt;p&gt;The webhook endpoint was returning &lt;code&gt;401: Invalid webhook signature or payload&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I went through the usual suspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Was the body parsed as JSON before signature verification? (That breaks HMAC, since the signature is computed over the raw bytes.) No — the route used &lt;code&gt;express.raw()&lt;/code&gt; and the handler rejected anything that wasn't a Buffer.&lt;/li&gt;
&lt;li&gt;Was the signature header name right? Yes.&lt;/li&gt;
&lt;li&gt;Was the payload being re-serialized with &lt;code&gt;JSON.stringify&lt;/code&gt;? No.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The actual cause was boring: the webhook secret in my hosting environment variables had been truncated on copy-paste. It was missing its prefix. I replaced it, the provider's automatic retry hit the endpoint, and it returned &lt;code&gt;200 {"received": true}&lt;/code&gt;. Plan activated.&lt;/p&gt;

&lt;p&gt;Two lessons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Test the payment chain with real money before launch.&lt;/strong&gt; Otherwise your first customer pays and stays on the free plan, and you find out from an angry email.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When signature verification fails, check the config before rewriting the code.&lt;/strong&gt; Most of the time it's the boring cause.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The irony
&lt;/h2&gt;

&lt;p&gt;The whole reason I built ApiPulse is silent failures.&lt;/p&gt;

&lt;p&gt;Most uptime tools check whether an endpoint returns 200. But a 200 OK can still carry an empty body, a malformed payload, or an error wrapped inside a "successful" response. The dashboard stays green while the thing is broken.&lt;/p&gt;

&lt;p&gt;ApiPulse lets you assert on what comes back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;status code&lt;/li&gt;
&lt;li&gt;response body contains / doesn't contain a string&lt;/li&gt;
&lt;li&gt;response time under a threshold&lt;/li&gt;
&lt;li&gt;a header exists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus SSL expiry alerts, public status pages, and alerts that fire only when status changes, not on every check.&lt;/p&gt;

&lt;p&gt;Stack: Angular (standalone components + signals), Node/Express + TypeScript, PostgreSQL, Resend for email, Paddle for payments.&lt;/p&gt;

&lt;p&gt;There's a free plan with 3 monitors: &lt;a href="https://apipulse.live" rel="noopener noreferrer"&gt;https://apipulse.live&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A question for you
&lt;/h2&gt;

&lt;p&gt;If you run APIs in production: what failure has your monitoring missed? I built this from my own frustration, which is a sample size of one, so I'd like to know whether it matches yours.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>node</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
