Inspiration

Every one of us has needed help that wasn't available in a shop. The sink is broken at 11pm, your laptop won't boot the morning of a deadline, your kid needs tutoring in a language nobody at school speaks. Facebook groups solve this, but they solve it badly: everything is public, posts scroll away in hours, there's no way to tell whether a stranger offering help is reliable, and the moment you need a phone number you're pushed into handing over your contact details to anyone. We kept noticing the same pattern. The help was always there, sitting a few streets away. What was missing was the layer of trust that would let two strangers actually go through with it. That's what Handover is: the neighborhood skill exchange that behaves like a marketplace instead of a public feed.

What it does

Handover lets people list the skills they have - plumbing, bike repair, Spanish tutoring, help with a computer - and find that skill offered nearby by someone else. The core loop is deliberately small: add skills to your profile, search by keyword and radius, send a request, chat privately, then close the loop with a rating and an optional tip. Tipping routes 80% to the neighbor through Google Play Billing, and the price is always shown before purchase. The part we're proudest of is what the app refuses to do. It never shows anyone's exact location. Search results return a rough area and a distance, and your phone number is encrypted at rest and only revealed to a neighbor who accepted your request and explicitly agreed to share theirs, for that one request. Chat rooms use 15-minute access tokens rather than open-ended sessions. Every post and photo passes automated safety screening, every report reaches a real moderator queue, and users can block each other permanently. It's a neighborly app that takes privacy and safety as the default rather than as a settings screen.

How we built it

A Flutter client (Android, iOS, web, desktop) talking to a FastAPI + SQLAlchemy backend, served through nginx with TLS and WebSocket support, packaged with Docker Compose.

A few decisions worth calling out:

  • Location privacy is architectural, not a setting. We implemented geohashing from scratch. Search results carry a grid cell that gets decoded into a bounding box for the map, so the app can draw an area without ever receiving a coordinate. The implementation lives in frontend/lib/services/geohash.dart and is covered by tests.
  • No third-party auth dependency. Tokens are HS256 JWTs we sign ourselves, with jti claims checkable against a BlockedToken table so sessions can be revoked. Passwords use Argon2, and legacy PBKDF2 hashes are accepted and transparently migrated. Two-factor auth is built in with TOTP and recovery codes.
  • Contacts encrypted with Fernet under a key derived from a separate config value, never stored in plaintext.
  • Safety is layered. Detoxify handles text toxicity at a 0.75 threshold; images go through magic-byte validation, Pillow integrity checks, dimension limits to stop decompression abuse, and an optional NSFW classifier (Hive or Google Vision) at 0.85. Critically, when a model fails to load we route to human review rather than silently marking content as safe. A failure in the safety system must not fail open.
  • Payments go through Google Play via RevenueCat, so we never touch card data.

Challenges we ran into

The hardest problem was making privacy feel invisible. We kept building screens where the "safe" version was strictly worse for the user, a map with a vague blob, a chat where you can't get a phone number - and the honest fix was to accept that friction as the product. Once we stopped treating it as a defect, the design got much easier. Signing the release build was its own adventure. We lost the original upload keystore, so we generated a new one, submitted a reset request to Google Play with its certificate, and waited for approval before we could ship. A build that worked perfectly on our machines was rejected by Play purely because of a signing key mismatch. The new key is backed up in two places now. We also spent real time on child-safety compliance before the app was ever public: a designated child safety contact, a public account-deletion page, a legal page served by the API itself, and reporting flows that route to humans. None of it is visible in a screenshot, and all of it was mandatory. And we had to be honest with ourselves about scope. Tipping, two-factor, image cropping, an admin moderation console - each of these was a day we'd budgeted as an afternoon.

Accomplishments that we're proud of

The moderation pipeline. Reports from the app land in a real console where a human can see the post, its owner, and the reporter's reason side by side, then either remove the post or dismiss the report. That loop closes in seconds rather than days, and it's the difference between "we have moderation" and moderation that actually functions. We're also proud that the safety checks degrade into human review instead of into nothing. That's a small piece of engineering that took real thought, and it's the kind of thing that only matters until the day it matters.

Finally: the app is signed, built, and going to Play. Flutter, FastAPI, our own auth, our own geohash, a working moderation console, and a real payment split - two friends had put that together end to end.

What we learned

The most surprising lesson was that privacy features are trust features. Every rough area instead of a pin, every consent step, every short-lived token is a moment where the user decides whether we can be trusted. The work that felt like friction while we built it turned out to be the thing that makes the product credible. We also learned that safety systems have to be designed for failure, not just for success. The first version of our moderation code would have marked content as safe whenever a model failed to load. Fail-closed, even if it means a queue full of manual reviews, is the only defensible default. On the technical side: we underestimated how much of "just ship it" is packaging. Version codes, signing keys, Play target-API policy, and the reset procedure consumed more time than any single feature. None of it is interesting and all of it is the product reaching people.

What's next for Handover

The neighborhood layer. Right now an area is just a radius around you. The real goal is a local network: verified address or building-level presence, so a search means "in my building" or "on my street," not "within 5 km of a coordinate." That unlocks the genuinely valuable version of the product, the person who watches the building, the student who tutors in the same block, the elder who needs a hand with groceries three doors down. Verified reputation. Ratings exist, but they aren't carried yet. We want review history, repeat handshakes, and a visible track record that means something, so a neighbor can tell a first-time helper from someone the block trusts. Recurring help. Most real favors aren't one-time. A weekly walk for an older neighbor, a standing tutoring slot, a monthly filter change. Onboarding that starts useful. Rather than an empty search page, a new user should be able to get their first request or offer going in under a minute, using a suggested skill and a suggested radius. And a real moderation team. The tools work, but at our scale the moderators are volunteers. The goal is a small local team with clear escalation, which is what the community is actually ready to support.

Built With

Share this project:

Updates

Submission history