Every dev shop has done this dance.
Project's done. It's deployed, it's working, the client's stoked. Final invoice goes out. And then you're sitting there staring at the GitHub repo settings page, cursor hovering over "add collaborator", thinking: do I add them now, or do I wait until they pay?
Add them now and the code's theirs. Every commit, cloneable, gone. If they decide to ghost you on that last invoice, congrats β you did the work for free and they've got the source. Wait until they pay and now you're the guy holding code hostage, sending "happy to transfer access once the invoice is settled π" emails and feeling gross about it.
There's no good move. That's the code-handover standoff and it's worse than the design version, because code is the one deliverable that's fully useful the second it leaves your hands.
Zips are not the answer
The usual workaround is: don't give repo access, just send a zip on payment.
Cool, except now you've got the other problem. Which commit is the zip? You cut it Tuesday, then fixed a bug Wednesday, then the client paid Friday. Is the thing you're about to send the thing they actually approved? You're not sure, so you re-clone, re-export, double-check the build, and it eats an hour you didn't have on a project that's already closed out in your head.
And if you get it wrong β ship a stale export, miss the env example, forget the migration you wrote last minute β that's a support thread starting on a project you thought was finished. On money you've already spent in your head.

The move: snapshot at invoice send
Here's how we ended up solving it in Handl, and honestly it's the obvious thing once you see it.
When you send the invoice, Handl takes a snapshot of your GitHub repo right then β the exact commit as it stands at send time. That snapshot gets held as a tarball, attached to the invoice, locked. The client can see it's there. They can't pull it yet.
Invoice gets paid in full? The tarball unlocks. They download the exact commit you snapshotted β the one that matches the state of the project when you invoiced, which is the state they approved. No re-cloning, no "wait which version", no scrambling. The thing they download is the thing you froze at handover.
And the timing does the annoying part for you. You're not adding collaborators, not transferring anything, not deciding when to hand over. You send the invoice like you always do. Payment lands, code lands. Same instant. You're asleep and it still works.
Couple of things worth being straight about:
It's GitHub only. Snapshots are GitHub, full stop. Not GitLab, not Bitbucket, not a self-hosted Gitea box you're proud of. If your code lives somewhere other than GitHub, this particular trick isn't for you yet.
It's a snapshot, not a transfer. You're not handing over the repo, ownership, history, or collaborator access. You're delivering a tarball of one commit β the built, approved state β for them to download. If your deal includes actual repo ownership transfer, that's a separate manual thing you do yourself. This is "here's the code you paid for", not "here's the keys to my GitHub org".

Why this beats every version of "just trust them"
The thing I like about it isn't the security theatre. It's that it kills the awkward conversation.
You know the invoice-first-please email is bad for the relationship. You also know handing over code before payment on a new client is how you end up in a "we're not happy with the final result" negotiation that's really just a discount grab. (Clients get coached to do this, by the way β there's a whole genre of advice telling them to withhold and renegotiate at handover. We got into it over in the guide on handing over final files without getting ghosted.)
The snapshot-on-send flow means you never pick a side. You don't withhold β the client can see the deliverable sitting right there, locked, named, sized, real. You don't hand over blind either. The gate holds until the money's in, then it opens itself. Nobody had to be the bad guy, and nobody had to send a passive-aggressive email with a smiley face doing a lot of work.
And here's the money bit β literally. Handl never touches the cash. The payment goes straight into your own Stripe account the second the client pays. We're gating the download, not the dollars. Your money is never sitting in some holding account waiting on us. It's yours the moment it clears; the tarball just happens to unlock at the same time.
When you'd actually turn it on
Not every project. Retainer client you've worked with for three years and trust with your life? Don't bother, hand over however you want. It's off by default and you flip it on per project.
Where it earns its place:
- New client, first project, big final invoice. Exactly the situation where you don't yet know if they're the type to renegotiate at the finish line.
- Fixed-scope builds where the final commit is the whole deliverable. The code is the product. Gate it.
- Anything where "which version" has ever bitten you. The snapshot being the exact approved commit is worth it on its own, honestly, even before the payment part.
One caveat so nobody's surprised: it's a full-payment gate. Paid in full, it opens β there's no half-payment-half-unlock. Size your milestones so that final invoice makes sense as one unlock. And once they've downloaded it, that's it β a later dispute doesn't claw a tarball back any more than it claws back a zip you emailed. The gate does its work up to the moment of payment, which is the moment that actually matters.
That's the whole thing. Snapshot on send, unlock on payment, GitHub only, your money never leaves your account. Set it up on your next final invoice over on the deliverables page β and go stop hovering over that "add collaborator" button.

