Selling through a provider

Sell products and plans through any provider's webhooks, and app-store purchases.

A product's "How it's sold" can name a relay instead of a Stripe Payment Link: GoCardless, Lemon Squeezy, Paddle, bank transfer, or any provider whose webhooks follow a supported signing standard and whose events you can map. fulfil_product sells it and refund_product takes it back. This example sells a product through any provider, with no server of your own:

{
  "name": "gocardless-paid",
  "trigger": {
    "kind": "receiver",
    "verify": { "scheme": "signature", "header": "Webhook-Signature" },
    "map": {
      "event_id": "events.0.id",
      "event_type": "events.0.action",
      "person_email": "events.0.details.customer_email",
      "payment_id": "events.0.links.payment"
    },
    "when": { "event_type": "confirmed" }
  },
  "actions": [
    { "type": "fulfil_product", "product": "Starter pack", "ref": "{{mapped.payment_id}}" }
  ]
}

Mapped fields are available in templates as {{mapped.<name>}}, and {{event.<path>}} reads the raw payload.

For refunds, add a second relay with when set to { "event_type": "refunded" } and the action refund_product. g.payments.buy on a product sold this way answers 409 product_not_sellable, because there is no Payment Link to open.

Subscription plans

Plans are sold the same three ways. A plan's "How it's sold" can name a relay, and two actions run its lifecycle: grant_plan starts or renews it and revoke_plan ends it. A relay-sold plan is active until revoked, with no lapse timer, so the revoke goes on the provider's end-of-access event, on its own relay: the subscription expired, its period ended, or the payment was refunded. A cancellation that only stops the next renewal is not that event; the person keeps the period they paid for. g.subscriptions.checkout on a plan sold this way answers 409 plan_not_sellable.

Set the plan's "How it's sold" on the Payments page to the relay that grants it. The Webhooks page offers the pair as starter text under A payment provider (shared token), for a sender that posts a token in a header rather than a signature (a bank-transfer bridge of your own, for one). Both templates set trustUnsignedEvents, with the warning in their note: saving one is your choice to accept it (see Unsigned events):

  • Payment → grant the plan: a shared_token receiver mapping the event's id, email and payment reference, and grant_plan with ref set to that reference, so a resent event grants once.
  • Access ended → revoke the plan: its own webhook and secret, a when that you fill in with the provider's event for the end of access (expiry, end of period or a refund), and revoke_plan.

Change the plan name in the text to the plan's exact name. A shared token is the whole of the check on these requests. Where the provider signs its webhooks, use its signature recipe instead, and the opt-in isn't needed.

App stores

An App Store or Play purchase reaches Gemmein as a RevenueCat webhook into a receiver whose map names person_token. The app gives RevenueCat the person's storeAccountToken as its app user id, so neither the person id nor the address enters a third party's ledger. The Webhooks page offers three templates: revenuecat-purchase → fulfil_product on the three purchase events (its when holds them as a set, { "in": [...] }); revenuecat-expiration → revoke_access on EXPIRATION, when access ends; and revenuecat-refund → refund_product on a refund, a CANCELLATION with cancel_reason CUSTOMER_SUPPORT. The purchase and the refund both use RevenueCat's transaction_id as the ref, so a refund undoes that one purchase: its access and its receipt, and it takes back its credits, first from that purchase's own grant, then from other credits the person holds, never below zero. Access held through another purchase stays. A cancellation keeps the access until the period ends, because RevenueCat sends EXPIRATION then. RevenueCat sends a shared token, not a signature, so each template sets trustUnsignedEvents (see Unsigned events). Mobile has the full setup.

Was this page helpful?

Last updated