I build a dating app solo. Last week I emailed 204 people who signed up and never finished onboarding, apologized for an earlier bug, gave them a working link.

Zero came back. Not a low number. Zero.

I almost filed it under dead list and moved on. Then I checked SES and 185 had actually delivered. And two of those people had opened the app again within 48 hours. One of them hit my ID verification screen twelve times in seventy-three minutes.

Nobody does that unless they're trying.

So I curled the verification provider:

x-frame-options: DENY

I was rendering their flow in an iframe. They forbid being framed, like every serious KYC provider does, because framing is how credential harvesting works. That iframe had never rendered a single pixel for anyone. The container behind it was background #fff, so what those twelve attempts were staring at was a full screen white void with no error and no way forward.

That was bug one. Behind it were two more.

Bug two: Android kills backgrounded apps. When the user came back, my app cold started, decided they were unverified, and created a brand new verification session, overwriting the session id of the one they had just finished. My status endpoint only ever looks up the stored id. So every verification completed during an app kill became permanently unreachable.

Bug three is the one that actually hurt. My webhook handler computed its HMAC over a re-serialized, sorted-key JSON object instead of the raw request bytes. Signature never matched. Returned 401. The provider only retries on 5xx and 404, so a 401 is a permanent silent drop. Every verification result they ever sent me went in the bin. Two users had passed a government ID check three separate times in August and been locked out of my own app ever since.

Here's the part I keep thinking about. All three had the same shape:

try { await saveTheThing() } catch (e) {}

proceedAnyway()

I wrote all of it. Every one of those empty catch blocks was me deciding a failure here shouldn't block the user. What it actually did was make the failure invisible while still blocking the user, which is the worst available combination.

I found a fourth the next morning in my onboarding save. Same shape. It had been silently leaving accounts on default values, and those accounts were then near-invisible in my own discovery query. Been doing it since July.

I had monitoring for whether my server was up. I had nothing for whether it was doing anything. Those are not the same question and I did not know that until this week.

What I added: a nightly job that counts verification attempts against completions and emails me if there were attempts and zero completions. It would have caught this on day one instead of day four. The same check would have caught the onboarding leak I found in July.

For scale, and because I'd rather be honest than sound bigger than I am: I went from 21 verified users to 31. This is a small app. The bug was not costing me thousands of users. It was costing me all of them.

If you have a third party integration where the happy path is the only path you've ever walked, go read what your error handler does when it isn't happy. Mine did nothing, four times, for four days.

submitted by /u/Unlikely-Ad348
[link] [留言]