DEV Community

Cover image for My payment webhook silently failed on the first real transaction — and I built a tool to catch silent failures
Lizard Gecko
Lizard Gecko

Posted on

My payment webhook silently failed on the first real transaction — and I built a tool to catch silent failures

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.

Why test with real money

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.

The money went through. The payment provider confirmed it. My app still said Free plan.

The bug

The webhook endpoint was returning 401: Invalid webhook signature or payload.

I went through the usual suspects:

  • 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 express.raw() and the handler rejected anything that wasn't a Buffer.
  • Was the signature header name right? Yes.
  • Was the payload being re-serialized with JSON.stringify? No.

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 200 {"received": true}. Plan activated.

Two lessons:

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

The irony

The whole reason I built ApiPulse is silent failures.

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.

ApiPulse lets you assert on what comes back:

  • status code
  • response body contains / doesn't contain a string
  • response time under a threshold
  • a header exists

Plus SSL expiry alerts, public status pages, and alerts that fire only when status changes, not on every check.

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

There's a free plan with 3 monitors: https://apipulse.live

A question for you

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.

Top comments (0)