💡 Inspiration
Every trip ends the same way. Someone paid the hotel, someone the car, someone covered dinner in a different currency, and one person who "isn't on the app" still owes everybody. Existing splitters answer this with a subscription and a spreadsheet dressed as a screen. We wanted the thing itself: one honest balance with every friend, who pays whom, and paying back in one tap.
Halv means "half" in Norwegian and Danish. That is the whole promise.
📱 What it does
- One number per person. Every trip, flat and dinner rolls into a single balance per friend. Green is owed to you, coral is what you owe. Nothing else in the app is coloured by money.
- The round. Halv's own debt engine settles a five-person trip in two or three transfers instead of ten, and shows why a transfer was rerouted. Any member can switch simplification off and it falls back to plain pairwise nets. Facts stay pairwise; the plan is derived, never stored.
- Scan the receipt, tap what you had. A photo becomes line items in a few seconds. Each person taps their dishes, shared items split by unit, and a six-person bill is done in under a minute.
- Real multi-currency. A group is a set of ledgers, one per currency. A dollar expense on a euro trip stays in dollars. There is no exchange rate anywhere in the app because an invented one is a lie.
- Pay back in one tap. Friends save how they like to be paid. Revolut opens with amount and currency prefilled; a bank account copies straight into your banking app. Halv never holds or moves money.
- Works offline, works without the app. Local-first: the app opens instantly from a mirror on the device and syncs later. You can split with someone who has no account yet; when they join, they claim their history.
💸 How we make money (and why it isn't a subscription)
The core app is free, forever, for everyone. We never charge per group, never per member, and never with an auto-renewing subscription. That is the wedge against the incumbents, and we mean it.
The one thing that costs us real money is receipt scanning, so that is the one thing we sell, as passes, not plans, all through RevenueCat:
| Pass | Price | Shape |
|---|---|---|
| Free | 0 | 3 scans a month |
| Starter Pass | $1.99 / 14 days | half price, once, for your first pass |
| Trip Pass | $3.99 / 14 days | 20 scans a day |
| Group Pass | $9.99 / 30 days | everyone in the group scans; the clock starts on the first scan, not at checkout |
The Group Pass is the part we are proud of. When you buy it from a group screen, Halv asks whether to record the $9.99 as an ordinary equal-split expense in that group. Say yes and every member owes you their share, about $2 on a five-person trip. The app splits its own price. Halv collects nothing extra; the split is an IOU between friends, exactly like any other expense in the ledger. The purchase becomes a live demo of the product.
Under the hood: RevenueCat is the source of truth for purchases on both
platforms (one shared purchase code path via Skip's skip-revenue), the
RevenueCat webhook lands entitlements in our Postgres, and every scan is
reserved server-side against the right pool. Passes are bound to the
account, not the device, so entitlements are cross-platform and there is no
restore flow to explain. Non-renewing passes never expire inside RevenueCat,
so expiry is owned by our backend.
🔨 How we built it
Two people in Poland, one Swift codebase, native on both platforms.
- SwiftUI for the whole UI; the same sources transpile to Kotlin / Jetpack Compose for Android with Skip. A design system (type ramp, one earned colour, floating frosted chrome) keeps both platforms on the same language.
- Supabase (Postgres, RLS, 19 Deno edge functions) is the backend. Every money write is an idempotent edge function with a client-minted id, so an offline retry can never double-charge a ledger.
- PowerSync mirrors each user's membership-scoped slice of Postgres into on-device SQLite. Reads are local; expense writes queue offline and replay as one idempotent call.
- Receipt OCR is a small FastAPI service on Cloud Run calling Gemini through Vertex AI, returning structured line items with quantities.
- Our own debt engine, a separate Swift package with a golden-corpus contract test between the client and a TypeScript port on the server, so the round on your phone and the settlement recorded by the server never disagree.
- RevenueCat + PostHog EU for purchases and product analytics. No amounts or receipt content ever leave the app.
We built it with Claude Code as a third pair of hands: 1,500+ commits since April 2026, 1,900 tests, and a folder of architecture decision records that both humans and agents read before touching money code.
😰 Challenges we ran into
- Multi-currency without lying. The first design summed everything in the group's currency. We ripped it out. Now every balance primitive takes one currency and is called once per ledger; "zero in euros" is not "square".
- Simplified debts that disagreed with the friend page. The group screen showed the simplified plan while the cross-group friend balance showed raw nets. Users read that as a bug, because it is. The fix was making the friend ledger follow each group's own toggle.
- One codebase, two runtimes. Skip's subset bites: a hit-shape modifier that compiled fine on iOS silently aborted the Android transpile; a nested bottom sheet can never give a text field focus on Android. Each one cost a build and became a rule in our project guide.
- Selling scans without a subscription. Apple intro offers exist only for auto-renewing subscriptions, so "half price on your first pass" had to be its own SKU. RevenueCat never expires a non-renewing purchase, so a 14-day pass needed its own expiry on our side.
- Splitting with people who aren't users. Ghost members that can pay and owe, then claim their whole history with one consent-gated link, took three waves of database migrations to get right.
🙌 Accomplishments that we're proud of
- The round: fewer transfers, with an explanation for every rerouted one.
- A group pass that splits itself. Nobody else's paywall is a product demo.
- Offline-first on both platforms, from a single Swift codebase.
- Shipped to the App Store and Google Play on the same day.
🧠 What we learned
- Money direction is the only colour a finance app has earned. Everything else is ink.
- Users forgive a missing feature; they never forgive two screens that disagree about who owes whom.
- A paywall that behaves like the product it sells converts better than one that interrupts it.
🚀 What's next for Halv
Pay by bank at settle time through an open-banking provider so the transfer itself is one tap, still without Halv touching the money. Product Hunt and an App Store featuring nomination in November with the accessibility pass and Android parity work bundled in. And more passes that split themselves.
Built With
- android
- cloudflare
- deno
- fastapi
- firebase-cloud-messaging
- gemini
- google-cloud-run
- google-sign-in
- ios
- jetpack-compose
- kotlin
- postgresql
- posthog
- powersync
- python
- revenuecat
- sign-in-with-apple
- skip
- sqlite
- supabase
- swift
- swiftui
- typescript
- vertex-ai
Log in or sign up for Devpost to join the conversation.