Connect Stripe

Connect your own Stripe with a restricted key, and what your AI sets in payments.json.

Connect Stripe

Connect Stripe once per environment with a restricted key. Gemmein then makes your products, prices and Payment Links in your Stripe account and registers the webhook that tells it who paid.

  1. Open Stripe's API keys page: Developers (bottom left) → API keys, or directly: test mode for Development, live mode for Live.
  2. Choose Create restricted key. When Stripe asks how you will use the key, choose Powering an integration you built, then Choose your own below the templates. Don't pick a template: each one turns on 15 to 42 permissions, and Gemmein refuses the key. The third-party option does the same, ticking many permissions for you.
  3. Turn on only these rows: the first five always, and the last two if your app sells plans. The filter box at the top of the key editor finds a row by name.
    SectionRowSet to
    CoreProductsWrite
    BillingPricesWrite
    Payment LinksPayment LinksWrite
    Webhook EndpointsWebhook EndpointsWrite
    ConnectAccountsRead
    BillingCustomer portalWrite, if you sell plans
    BillingSubscriptionsWrite, if you sell plans
    Accounts is under Connect, not Accounts v2. It is Read only, so Gemmein knows which Stripe account the key belongs to. Customer portal and Subscriptions let your members manage their plan in Stripe's portal, and let Gemmein end a deleted member's subscription. An app that sells only products or credit packs needs the five. Then choose Create.
  4. Optional, on Development's key only: two more rows let your AI test payments with the test kit. Charges and Refunds (under Core) set to Write refunds its own test payments, and Checkout Sessions (under Checkout) set to Read checks a test payment while Stripe's webhook is on its way. Gemmein accepts these rows on a sandbox (rk_test_) key only; a live key that holds them is refused.
  5. Already made a key with more? Edit it in Stripe and choose None on every section heading: each heading sets every row under it. Then turn on only the rows above.
  6. Paste the key on that environment's Payments page. It starts rk_test_ in Development and rk_live_ in Live. Don't share your keys anywhere else: paste the restricted key only there.
  7. Give each paid plan a price (an amount, a currency, and how often it bills: day, week, month or year), and each product or credit pack a one-time price: your AI writes them in gemmein/payments.json and npx gemmein sync sets them in Development, or you set them on the Payments page. Gemmein makes the Payment Link.

What it is. A Stripe restricted key, one per environment: test mode for Development, live mode for Live. It carries 5 permissions, plus 2 if you sell plans: Write on Products, Prices, Payment Links and Webhook Endpoints, and Read on Accounts; and, when your app sells plans through Stripe, Write on Customer portal and Subscriptions. None of them can pay out, refund, or read cards.

Does. Checks the permissions your app needs (five, plus two if it sells plans) with calls that change nothing, and names each one missing. Gemmein refuses a key that can do more than these seven: it tries harmless reads of your money and customer data (balance, charges and refunds, customers, payouts, payment intents, payment methods, invoices, checkout sessions, transfers, coupons, disputes and more), and a key that can make any of them is refused. The Payments page then shows what the key can read and the rows to set; the API answers stripe_key_too_broad with what it can read in readable. A Stripe account belongs to one Gemmein account: another Gemmein account connecting it is refused, while your own apps may share it. Registers one webhook endpoint in your Stripe with the eleven events Gemmein needs, plus the event of each custom rule you add. Checks the webhook every 6 hours and the key every day: a key that was revoked, rolled or given more permissions stops being used, and a webhook that was deleted, disabled, moved or stripped of events is flagged on Overview and by email. A key problem shows Connect a new key, and a webhook problem shows Repair webhook. Replace key, on the key row, swaps in a new key from the same Stripe account and keeps the webhook and your Payment Links. Your Payment Links keep selling throughout. Creates a Stripe product, price and Payment Link for each plan, product and credit pack you price. A new price makes a new Stripe price and Payment Link and turns the old ones off, because a Stripe price never changes. A rename renames the Stripe product, and removing one archives it in Stripe. In Live, the live-mode key creates Live's own products, prices and Payment Links from the prices going live copied. Sets up the customer portal from your plans, where members switch plan, cancel at the end of the period and update their card.

Does not. Pay out, refund, or read cards, customers, charges or payments: the key carries no permission for any of them, and none to move money out of the account. It can change subscriptions (Subscriptions: Write); Gemmein uses that only to end a deleted member's subscription, and members switch or cancel their own in the portal. Show the key again: once pasted, only its last four characters are shown. Accept a Stripe secret key (sk_…) or publishable key (pk_…), a live-mode key in Development or a test-mode key in Live. Take a Payment Link or a webhook signing secret made in Stripe: selling through Stripe needs the key, and Gemmein hands out only the Payment Links it made.

Needs something else when. You sell through a provider other than Stripe: use a relay.

Example. A restricted key made in Stripe test mode with the five permissions and the two plan rows (the app sells a plan), pasted on Development's Payments page; "pro" priced at 15 GBP a month in gemmein/payments.json and set by npx gemmein sync. g.subscriptions.checkout("pro") opens the Payment Link Gemmein made in your Stripe.

Changing a price, or renaming something Gemmein made in Stripe, needs the key connected. Disconnecting forgets the key: the webhook, its signing secret and every Payment Link keep working, and only making or changing things in Stripe stops. If two people save at once, the later save is refused with a reload message rather than overwriting the first.

Turning payments off stops new sales: nothing new can be bought and Gemmein turns off the Payment Links it made. Existing customers carry on as before. A renewal keeps its access, and a cancellation, a full refund of a purchase, or a renewal Stripe stops retrying takes it away. The webhook stays; a new sale that still arrives is kept on the Errors page for you to refund or grant, and is never granted on its own. Removing the webhook is a separate action, Disconnect and delete, which waits until no Gemmein link is on, no subscription can still charge, and 72 hours have passed since the last link went off. A key from a different Stripe account is refused. In Development, Start over lets you connect another Stripe account once nothing can still pay.

A payment names what was bought by the Payment Link it was made on. A payment on a link Gemmein did not make with this environment's key is kept on the Errors page for you to refund or grant, and is never granted on its own.

Protect your accounts

  • Use a different email address for Stripe and for Gemmein. If one inbox is compromised, the other account stays safe.
  • Make them addresses only you use, not your public name or contact address: for example, a new address kept only for this. Attackers target the addresses they can find on your site, on LinkedIn or in git commits.
  • Turn on two-step sign-in for both inboxes and for Stripe. You sign in to Gemmein with a code sent to your email, so your inbox's security is your Gemmein account's security.
  • Never share your keys or sign-in codes, including in screenshots. Gemmein asks for a key only in the Connect Stripe field on the Payments page.

Going live

Going live copies your plans and products into Live with their names, prices, credit allowances and packs, and what they unlock. Development's Stripe products, Payment Links and signing secret stay in Development. Connect Stripe in Live with a live-mode key and Gemmein creates them in Stripe live mode. Until then customers can't pay in Live, and Live's checkout answers payments_not_configured; you can still go live first.

Once live, Promote on the Go live page copies what Development has into Live:

  • Plans, products and credit packs Live doesn't have are copied whole.
  • Plan and pack definitions (credits, rollover, the welcome allowance, a pack's credits) are brought to Development's values.
  • A different price is shown and never applied. Change Live's price on Live's Payments page.
  • An AI tool or collection unlocked by a plan Live doesn't have is re-linked to Live's plan of the same name. If no name matches, the promote stops before anything moves and says which one.
  • An AI provider key is copied only when you tick Use the same key as Development.

Each priced plan and product has a Tax category, such as Online service, Software subscription, Ebook or Audio. Gemmein sets the matching tax code on the product it makes in Stripe, which Stripe Tax and Managed Payments read. A product starts on Online service and a plan on Software subscription.

Set by your AI

In Development your AI sets how each plan and product is sold, in one file the project keeps: gemmein/payments.json. Per product: its price, the file inside the project (or the link) a buyer gets, the credits it adds and its tax category. Per plan: its price and how often it bills, the credits each period grants and what rolls over, and its tax category.

{ "plans": [
    { "name": "Free", "default": true },
    { "name": "Pro", "paid": true,
      "price": { "amountMinor": 900, "currency": "gbp", "interval": "month" },
      "credits": { "perPeriod": 500, "rollover": "none" } } ],
  "products": [
    { "name": "Field Guide", "price": { "amountMinor": 1500, "currency": "gbp" },
      "deliveryFile": "assets/field-guide.pdf", "taxCategory": "ebook" } ] }
  • npx gemmein sync shows what it will change, uploads the file, and sets it in Development. With Stripe connected there, each priced item sells at once through Stripe test mode; without, it waits as not sold yet and is made the moment the key connects.
  • Those rows read set by your AI on the Payments page. You can edit any of them; the next sync shows your edit and leaves it as it is, unless you run npx gemmein sync --overwrite.
  • An item missing from the file stays. "remove": true on it takes it down: its Payment Link goes off, and a file stays for whoever bought it.
  • A product's file is a pdf, zip, epub, audio file or image up to 100 MB, or a video (mp4, webm, mov) up to 500 MB (50 MB in Development). The buyer's g.files.link(file) plays an audio or video product in the page; other files download.
  • npx gemmein payments --cloud reads back what Development sells: each price, how it is sold, its Payment Link, its file and tax category, and whether Stripe is connected.
  • Live is never set by sync. Going live and promote copy Development's plans and products into Live, where Live's own key makes them.
Was this page helpful?

Last updated