Skip to main content
Getly

Subscriptions for your own product: what we built and why

Getly Billing lets a seller charge their own customers on a recurring plan through the API — hosted checkout, card or crypto, five webhooks, a status endpoint. The design decisions behind it, including the one that surprised us in testing.

6 min read
1,055 words
Subscriptions for your own product: what we built and why

A seller wrote to us with a specific request. He runs a small SaaS, he already uses the Getly API to sell one-off files, and he wanted the same API to handle monthly plans: create a plan, send a customer to a checkout page, get told when the payment goes through, get told when it fails, ask at any moment whether someone is still paid up. He compared what he wanted to Stripe Billing, and he was right to. We did not have it. What we had was a webhook list that promised sixteen events, seven of which nothing in the codebase had ever sent.

This is the story of the two weeks that followed, told as decisions, because that is what a product is.

What it is, in one paragraph

Getly Billing charges a seller's own customers on a recurring plan through Getly, for something the seller runs themselves. The plan lives in the API (POST /api/v1/billing/plans: an amount in cents from fifty, an interval of days, weeks, months or years, up to fifty-two of them). The subscribe page lives on getly.store, minted per customer with the seller's own customer id attached. The customer pays by card, which becomes a real Stripe subscription that renews by itself, or in USDT/USDC, which buys exactly one period. From then on the seller's backend receives five webhooks — created, renewed, payment failed, canceled, expired — and every one of them carries that customer id, so the handler needs no lookup. When a webhook is not enough, one GET answers whether a person is paid up. The plan never appears in the marketplace catalogue; the subscriber lands on the seller's site, not ours.

Decision one: it costs what your own traffic costs

A subscriber to someone's SaaS did not find that SaaS on Getly. They came from the seller's own site, signed up on the seller's own page, and were sent to us only to pay. That is exactly the situation our pricing model already describes as own traffic, and own traffic pays ten percent. So Getly Billing takes ten percent of each payment and nothing else — no monthly fee, no per-plan fee, no setup. We looked at charging a platform fee on top and decided against it for a simple reason: the number is already in the pricing table, and a second number for the same situation would need a second explanation.

Decision two: where the money goes depends on what the seller has

Our first draft sent every payment straight to the seller's Stripe Connect account, because that is the cleanest accounting. It would also have excluded most sellers: only a small minority of stores that have ever sold anything have connected Stripe, and it would have made crypto impossible, because there is no Connect transfer for a stablecoin. The shipped version is a hybrid. If the store has a Connect account, the transfer happens inside Stripe on every invoice and our fee is taken as Stripe's application fee. If not, the money lands on Getly's account and is paid out on the 1st and 15th exactly like a marketplace sale, through the same journal the payout run already reads. Crypto always settles through Getly. The seller sees one dashboard either way.

Decision three: crypto pays per period, and nothing pretends otherwise

Nothing can pull a crypto payment. So a crypto plan is not a subscription in the card sense; it is a period bought with one invoice. The subscriber is reminded seven days before the period ends and pays again from their Getly account. Pay early and the new period stacks onto the end of the current one; pay late and it starts today, because nobody should pay for days already lost. If nobody pays, an hourly job closes the period and the seller's endpoint receives the expired event. We say this plainly on the subscribe page, before the click, because a customer who expected auto-renewal and got silence would blame the seller, and the seller would blame us.

Decision four: access is by application

This one came from the founder, and it is the one most likely to annoy a developer in a hurry, so it deserves its reasoning. When Getly charges a card, Getly is the merchant of record: the charge carries our name, the dispute lands on our account, the regulator's question comes to us. Selling a file we host is one thing. Charging monthly for a service we never see is another. So before the first charge, the seller tells us the public website the product runs on and a few sentences about what it is, and a person reads that. Until approval, every write call answers a specific error code that says exactly this, with a link to the form; read calls work, so an integration can be built and tested before the review is done. Usually the review takes less than a day. We would rather lose a seller who will not tell us what they sell than find out from a chargeback.

What testing found that reading did not

Before shipping we ran the whole thing against a copy of the production database with Stripe in test mode, a headless browser paying with a test card, and Stripe's test clocks to make a month pass in a minute. Everything on the list worked: the application, the gate, plans, checkout, the first payment, a genuine renewal, cancellation from the subscriber's side, the crypto path with its reminders and expiry. One thing did not. When a card was declined, the seller's endpoint never received the payment-failed event. Stripe sends the subscription's status change a moment before it sends the failed invoice, our status sync had already written "past due", and the failure handler concluded the seller had been told. The fix is to deduplicate by invoice, not by status — a seller is told once per invoice, and Stripe's retries of the same invoice stay quiet. Reading the code twice would not have found this; the order of two events on the wire did.

If you run something people should pay for every month and you already talk to our API, the landing page has the five-step version and the reference has the rest. Apply from your dashboard; the API opens the moment a person says yes.

Ready to start selling?

Independent marketplace for digital creators. Keep 80–90% of every sale. Accept cards and stablecoins.