2
6 Comments

Building v2 of my payments backend in Go after a year on Node. Here is what v1 actually taught me.

I run 2Settle, a product with one stubborn goal: make crypto easy to spend on everyday things. Not trading, not wires. The coffee, the subscription, the bill you owe right now, paid with money you already hold.

v1 shipped on a Node stack and taught me what the product actually is. v2 is a ground-up build in Go. I want to share the reasoning, because "we rewrote it in a faster language" is the lazy version of the story and it is not what happened.

Why the language changed

The honest driver was not raw speed. It was the shape of the problem. A payment is not a request you can retry casually. It is real, final, and there is a person standing at a checkout waiting on it. The moment you take everyday spending seriously, your backend is doing many small, independent, time-sensitive things at once, and each one has money attached.

Go's model for concurrency, thousands of things happening at the same time without the code turning into a callback maze, maps almost exactly onto that. In v1 I was constantly working around the runtime to get predictable behavior under load. In v2 the runtime works with me. That is the whole trade.

Three things v1 taught me that shaped v2

  1. Version one is a research project you get to charge for. You do not know what the product is until real people use it in ways you did not plan. Everything I thought was a core feature on day one looked different once money moved through it.
  2. Boring and predictable beats clever. In payments, the exciting path is the one you never want to hit. v2 is deliberately less clever than v1 in a lot of places, and it is far easier to reason about at 2am.
  3. Correctness is a product feature, not a tax. Users do not see your concurrency model, but they feel it the first time a payment does exactly what they expected, instantly. That trust is the product.

What I am not doing this time

Not chasing every chain and token on day one. Not adding features v1 never validated. Not calling it done. It is still building, still being tested, shipped, and tested again.

Happy to answer anything about the Node to Go decision or building payment infrastructure the second time. What I will not get into is anything live-system specific, for obvious reasons.

on July 24, 2026
  1. 1

    yeah the schema tax is real. what's kept it manageable for me: treat event shapes like migrations, never delete or rename a field, tag every event with a version, upcast old ones on read so replay always sees the current shape. and past a certain volume full replay gets slow too, periodic balance snapshots plus replay-from-snapshot is what keeps the 5 minute read honest

  2. 1

    the idempotency point below is the right worry for correctness. the one that got me on similar flows was afterward: a support ticket lands and someone needs to know why the balance is what it is. a current-balance table gives you the what, not the why. an append only trail of every state change makes that a 5 minute read instead of a log-diving afternoon

    1. 1

      Agreed, and here's the sharp version: the append-only trail has to be the source of the balance, not a log sitting next to it. The moment it's a parallel audit table, it drifts from the balance exactly when you need the two to agree, because whatever bug skipped writing the balance correctly usually skipped or mangled the audit row too. Fold the events to derive the balance and "why is it this" and "what is it" can't disagree by construction.

      Event sourcing is heavier up front, and for money that trade usually pays: the 2am job turns from "reconcile the audit log against reality" into "replay the events." The tax you take on is versioning your event schema forever, which never fully goes away.

  3. 1

    "Correctness is a product feature" is the line I'd frame on the wall. One push though: the correctness that bites at 2am usually isn't the concurrent request path Go just cleaned up for you. It's the async reconciliation after the payment. A crypto payment is final on-chain, but your backend hears about it through confirmations and webhooks that land late, out of order, or twice, and every so often a reorg unwinds one you'd already treated as done. Go makes the in-flight handling clean; it can't tell you whether your ledger's view of a payment is eventually consistent with the chain's.

    When we built commission tracking on Stripe, every money-correctness bug lived in that same async layer: refunds clawing back a payout, a webhook firing twice, events arriving out of order. Never the request handler. So the v2 question I'm most curious about: what's your idempotency and reconciliation model for an event that arrives twice, or never? That's where "boring and predictable" earns its keep.

  4. 1

    I'm curious what v2 has forced you to unlearn from v1. Was there a decision you were convinced was fundamental in the first version that you now see as solving the wrong problem?

  5. 1

    The v1 to v2 leap you describe is exactly where most solo founders stall — they get stuck refactoring instead of shipping. I ran into this too. What helped was a hard rule: no rewrite until the existing code actively costs more in maintenance than the rewrite would take. Saved months.

Trending on Indie Hackers
I built an AI that turns an idea into a live business in under 10 minutes. Here’s what 1,000 launches taught me User Avatar 87 comments Building Noodle, a keyboard-first REST client for the terminal User Avatar 33 comments Building a startup costs $0. Your tooling budget costs $500K. Here's why. User Avatar 32 comments "Looks Good to Me" Is Quietly Killing Your Feedback Loop User Avatar 31 comments I didn't want to build another AI chatbot User Avatar 16 comments I built a competitor monitor for indie founders User Avatar 15 comments