Launched this week
HonorBox
Sell digital products with just Stripe and GitHub
84 followers
Sell digital products with just Stripe and GitHub
84 followers
Your storefront is a static site on GitHub Pages. Checkout is a Stripe Payment Link on your own account. A scheduled GitHub Action delivers each sale by inviting the buyer to your private repo.




HonorBox
The Stripe + GitHub combo is clever since it skips a database entirely — repo as the product store, Stripe as the ledger. What happens if a buyer needs a refund or you want to revoke access after the fact, does that require manually editing repo permissions? Also curious how you're handling license key delivery if a product needs one, since that's usually the part that turns "just Stripe" into a real backend.
HonorBox
@leo404 No manual permission editing. The free engine keeps your books in a ledger and delivery is one scheduled Action, so revoking is removing the collaborator, one call or one click, and the reconcile pass will tell you if paid access and actual access ever disagree. Pro automates the refund case end to end: a refund guard watches Stripe and revokes the buyer's repo access on its own when a refund lands. On license keys, you're right that this is where 'just Stripe' usually grows a backend, which is why I kept keys out of the delivery path entirely. Access IS the repo invite. If your product itself needs keys, Pro ships an ed25519 license module: keys signed in CI, verified offline in your app with drop-in snippets for JS and Python, still no server
The reconcile pass architecture makes sense — the silent failure modes you described (HTTP 200 on a dead link, sessions completing 23 hours late, an empty ledger that looks like a working store) are exactly the class of bugs that don't surface until money is involved. The ed25519 module in Pro is a good call too: signing in CI means the key is already in the artifact without adding a runtime revocation server. Curious about the verification side — are the drop-in JS/Python snippets doing an offline signature check against the embedded public key, or does the license itself carry a validity window that expires without needing a revocation lookup?
HonorBox
@leo404 both, and they're independent. the snippet does an offline ed25519 verify against the public key you ship inside your app, no network call anywhere. then it checks an optional expires field that sits inside the signed payload: null for a perpetual licence, a unix timestamp when you want a window. so the window is enforced by the same signature check rather than by a second lookup. which means there's no revocation path at all, and that's the honest tradeoff: nothing phones home, and a perpetual token you signed cannot be pulled back. if you need revocation you either issue short windows and re-sign on renewal, or add your own check, which puts the network back in. worth saying the seam is deliberate. repo access is the revocable half, keys are the offline half. mixing them is where this usually grows a server.
The reconciliation job should be able to cover downtime gaps, not just the latest polling window. That feels like the difference between a simple “payment happened, invite user” script and something you could actually trust.
How do you decide the lookback boundary in practice? Is it based on the last successfully reconciled Stripe cursor in the ledger, or do you intentionally rescan a fixed historical window to catch delayed sessions and Action failures?
HonorBox
@akarsh_hegde The first one: the boundary is the committed cursor, not a fixed rescan. The job commits a creation-time watermark with its state, and every run scans created > cursor minus a fixed 25 hour overlap. The overlap is sized against the session, not the poll interval: a Checkout Session can complete almost 24 hours after it was created, so the window has to outlive the session or a late payer falls outside it forever.
Two more rules make it trustworthy: the cursor only advances when a run actually commits, so downtime cannot move it and a resumed job scans everything since before the outage. And it refuses to advance past any session still waiting on a retry, so a deferred invite cannot age out of the window either.
Rescans cost one extra API page because processing is idempotent; a missed sale costs a customer. Every sizing decision leans toward re-reading.
HonorBox is listed across Payments, Developer Tools, Productivity, and Social & Community, which makes me wonder about the first target user. Is this mainly for makers/dev teams handling rewards or recognition, or is the payment angle more central? A concrete example workflow would help place it quickly.
HonorBox
@mia_qiao Payments is the whole point, the category spread is just tagging. The first target user is a developer selling a code-shaped product to other developers: a CLI, a template pack, a course that lives in a repo, a paid tool. Concrete workflow: fork the template, edit one config file, run the init script with a restricted Stripe key and it creates the product, price and payment link for you, turn on GitHub Pages and the store is live. Your buyer clicks buy, pays on Stripe, types their GitHub username at checkout, and a scheduled Action invites that username to your private product repo. The storefront I sell from is this exact engine unmodified, so the demo is the source.
This is such a clever use of tools developers already trust. Stripe handles the money, GitHub handles access, and HonorBox just connects the two.
How long does it usually take from payment to getting access to the private repo? And what happens if the buyer uses a different email for Stripe and GitHub?
Really smart idea. Congrats on the launch!
HonorBox
@john_heaton06 Thanks! Usually within minutes, always within a few hours in the default polling mode, and there's an opt-in webhook mode if you want it near-instant. The email question is the nice part: emails never have to match, because delivery doesn't key on email at all. Checkout asks for the buyer's GitHub username as a custom field and the invite goes to that username, so their Stripe receipt can go anywhere. And since GitHub expires an unaccepted invite after seven days, the engine re-issues it before that happens, a buyer who missed the notification doesn't lose what they paid for.
the reconciliation pass is the part that actually matters here, most people building the happy-path version wouldn't even think about a payment link silently returning 200 while deactivated. how far back does the reconciliation job look though, if someone's GitHub Action was down for a few days does it catch every sale in that gap or just the most recent window
HonorBox
@omri_ben_shoham1 Every sale in the gap. The lookback is not anchored to now, it is anchored to a cursor the job commits alongside its own state, so a dead Action just means the cursor stops moving. When it comes back, it scans everything created after cursor minus a 25 hour overlap, which includes the entire outage however long it was. The overlap exists because a checkout session can complete almost a day after creation, so the window has to outlive the session.
Re-reading the same sessions is safe by design: a processed-id set plus the invite call being idempotent, GitHub answers "already invited" rather than double-granting.
And for the belt-and-braces case, the paid audit walks it from the money's side instead: list every paid session over the last N days and prove each one actually has access, naming the ones that don't.
the one-product-per-repo model makes total sense for a single digital product, but what happens once someone's catalog grows to 10-20 different products? is each one really its own separate repo with its own scheduled Action to maintain, or is there a hub-and-spoke setup where one repo manages delivery for the whole catalog?
HonorBox
@galdayan Hub and spoke, exactly. Product repos are just content, they hold nothing operational: no Action, no state, no secrets. One private ops repo runs the single scheduled Action for the whole catalog, and a grant table in its config maps each payment link or price id to the repo that purchase unlocks.
The poll reads recent paid sessions once per run and routes each buyer to whichever repo their grant names, so product number 12 costs you one config line and one payment link, not new infrastructure. The cursor, ledger and state live in that one hub too, which is what the reconcile pass reads when it proves every paid order across every product actually reached its buyer.
The paid tier ships a complete multi-product config as a worked example.
@honorbox_dev that clears it up a lot, thanks. one follow-up - since the cursor, ledger and state all live in that single hub repo now, what happens on a run where the hub's Action itself fails partway through (rate limit, github outage, bad config push)? does the next scheduled run just pick back up from the last good cursor position across all products, or is there a risk that one bad hub run needs manual reconciliation across the whole catalog instead of just one product?
HonorBox
@galdayan Next run picks up from the last committed position, and the failure unit is a session, not the catalog. State only moves by a commit at the end of a successful pass, so a run that dies partway (rate limit, outage, bad push) leaves the previous cursor and processed-set intact. Whatever it delivered before dying gets re-seen on the next run and lands on the idempotent invite, GitHub answers "already invited", and the ledger dedupes on a ref derived from the session id, so nothing double-fulfills and nothing is lost.
Failures inside a run are per-session too: a session that fails transiently is deferred and the cursor refuses to advance past it, while every other session in the catalog processes normally. A config push that breaks the run fails it loudly in Actions with state untouched, and a concurrency group stops two runs from racing the same state.
So manual reconciliation is never required for correctness. The reconcile tool exists as the independent auditor that proves delivery per product from the money side after any incident, rather than as a repair step you owe.
@honorbox_dev that's a solid design, appreciate the detail. last one on this thread - the reconcile tool proving delivery after an incident sounds great for correctness, but who's actually watching for the incident in the first place? does a deferred session raise any kind of alert, or would a solo dev only notice a stuck session by happening to check Actions logs?