Seller help
How to connect a payment provider, attach a product to a repository, configure benefits, and understand what a buyer sees after paying.
Before you start
A seller account needs a verified email address and at least one active payment provider before a public seller page is served. A Lemon Squeezy account connected for license validation counts as a provider too.
Create the repository in the app first. One repository is one product you sell access to: it holds the benefits, the payment mappings, and the public page. For GitHub benefits, connect your own GitHub account and make sure it is an administrator of every repository you grant. Perkz uses that connection to send and revoke collaborator invites.
Checkout stays in your payment provider. Perkz receives that provider's webhooks, records the order, and fulfils the benefits.
Connect a payment provider
Open the repository's Benefits page and find the Payment providers card. Polar, Stripe, and Lemon Squeezy are listed separately. Each provider is configured once per seller account, so the secret is reused by every repository; product mappings are per repository.
- Copy the webhook URL shown for that provider. It looks like
https://perkz.to/api/webhooks/provider?seller=…and contains your seller id. - In the provider dashboard, add that URL as a webhook endpoint and subscribe to the events listed below. Copy the endpoint's signing secret.
- Paste the signing secret into the provider card and choose Save secret. Every delivery is verified against it.
- Add the product ID from that provider in the same card and press the add button. The product is now mapped to this repository.
| Provider | Events Perkz processes | Product ID |
|---|---|---|
| Polar | order.paid, order.refunded, subscription.updated, subscription.canceled, subscription.revoked | The Polar product ID. |
| Stripe | checkout.session.completed, invoice.payment_succeeded, customer.subscription.updated, customer.subscription.deleted, charge.refunded, charge.dispute.created | The product on the price; if the checkout session does not expand line items, set session metadata product_id (or perkz_product_id) instead. |
| Lemon Squeezy | order_created, order_refunded, subscription_created, subscription_updated, subscription_cancelled, subscription_expired, subscription_payment_success | The product ID or the variant ID; either is matched. |
Lemon Squeezy also has a separate account entry on the dashboard: add it with an API key when you use the Lemon Squeezy license check on a repository. That account validates buyer license keys against the Lemon Squeezy API and is independent of the webhook secret above.
Map a product to a repository
A mapping says "this provider product unlocks this repository". The ID must match the product ID Perkz receives in the webhook: Polar sends a product id, Stripe sends the price's product, and Lemon Squeezy sends either the product or the variant.
One product maps to one repository, and the same product cannot be added twice to the same repository. A repository can have several products, for example one monthly and one annual price, or products from more than one provider. The mapped IDs are listed under the provider with a delete action; deleting a mapping stops new orders for that product from fulfilling.
Payments for a product with no mapping are recorded by the provider but cannot be attached to a repository.
Benefit types
Benefits are ordered on the repository's Benefits page and appear to the buyer in that order. Each one has a type, a name, an optional description, and a JSON config validated against that type.
- GitHub repository access
Config lists one or more
owner/repoentries and a permission:pull,push,admin,maintain, ortriage. The buyer signs in with the GitHub account they want to use, and Perkz sends the collaborator invitation.This is the only benefit type that goes through the claim link. One purchase can be claimed by one GitHub account.
- Discord access
Config names the server, an optional invite channel, optional roles, an optional welcome message, and whether a revoked buyer is kicked or only has roles removed. The buyer joins from the purchases page, where Perkz shows the generated invite.
- License key
Keys come from your uploaded pool, from generated keys (pattern, character set, optional prefix), or from your own webhook. The config also sets the concurrent activation limit (0 means unlimited), an optional expiry in days, and an optional activation webhook.
Buyers see the key and can activate or deactivate devices on the purchase's license page. Your software can validate keys against
GETorPOST/api/license/validatewithkeyand an optionalinstance_id. The endpoint returnsvalid,activations_used,activations_limit, andactivatedwhen an instance id is supplied; CORS is open so desktop and CLI software can call it. A revoked key returnsvalid: false.- Downloads
Config lists files with their storage key, size, mime type, and optional version, plus an optional download limit and expiry in days. Buyers get signed download buttons; each request checks that the grant is still active before the URL is issued.
- Links
Config lists links with a label, URL, optional description, and optional password. Buyers see the list with copy actions for the URL and the password.
- Text
Config holds a title and content, with an optional collapsed state. Buyers read it on the purchases page after signing in.
What buyers see
When the provider confirms a payment, Perkz emails the buyer at the checkout address, records the order, and creates the grants. Grants that need nothing from the buyer, such as a license key or download, are fulfilled right away; GitHub access waits for the claim. The email links to one of two places:
- Purchase with a GitHub benefit: the email contains an access link to
/api/purchases/<orderId>/access?token=…. It sets a seven-day cookie and redirects to/claim/<orderId>. The claim page asks the buyer to sign in with GitHub and confirm the claim (plus any agreement text you added), which sends the invitation. It shows whether the grant is pending or granted and links togithub.com/<owner>/<repo>/invitationsso the buyer can accept. - Purchase without a GitHub benefit: the email links to
/purchases/<orderId>. The buyer signs in with the email used at checkout to see the Discord invite, license key, downloads, links, or text.
The claim page also has three guarded states. Opening /claim/<orderId> without the emailed cookie shows a neutral "open the access link from your email" page. With the cookie, an already-claimed order asks the visitor to sign in with the same GitHub account. A refunded or cancelled order shows that the purchase is no longer active.
Buyers can open /purchases at any time to see every order tied to their account email, its status, and its benefits. Orders are matched on that email, so a different checkout email means a different account.
Refunds and cancellations
A refund in the payment provider revokes the order's grants. GitHub collaborators are removed, Discord roles are removed (and the buyer is kicked when that option is set), license keys are marked revoked and their activations cleared, and download links stop being issued. Text and link benefits disappear from the purchases page because the grant is no longer active. The order is marked refunded.
A cancelled or expired subscription revokes grants the same way. A replayed purchase webhook afterwards does not restore access or resend the purchase email. Disputes are logged but not revoked automatically, so handle those in the provider and contact support if a grant needs attention.
Refunds for your own Pro subscription are separate from your buyers' refunds: email [email protected] and support will look at it.
Free vs Pro
The plan wording on the pricing page is the source of truth; prices live there.
| Free | Pro |
|---|---|
|
|
In the current app, a Free account can also have one Discord server and one Lemon Squeezy account; adding another prompts the upgrade. Pro removes those limits. Product mappings and providers are not limited per repository.
Troubleshooting
- A paid order never appeared.
- Check that the webhook endpoint URL copied into the provider still contains your seller id, that its signing secret was saved for that provider, and that the event is one Perkz processes. Then confirm the product ID in the mapping matches the id in the paid event exactly. Provider dashboards usually list failed deliveries and let you resend them.
- The GitHub invitation failed.
- The connected GitHub account must be an administrator of every repository in the benefit, with access to grant collaborators. Fix the connection or permission and the grant can be retried; the buyer sees the error on the purchase until it succeeds.
- The buyer lost the email.
- The access link can be used again by the same claimant; ask the buyer to look for the purchase confirmation and check spam. For a purchase without a GitHub benefit, they can sign in at /purchases with the checkout email. If the email is gone, contact [email protected].
- The claim page says the purchase is already claimed.
- Each purchase can be claimed by one GitHub account. The buyer must sign in with that account. A different account cannot take over the grant.
- A license key fails validation.
- The key must be assigned to a completed order, not still available in the pool, and must not be expired or revoked. The activation limit is per key; deactivating a device frees a slot. The public endpoint returns a reason such as Key not found or Key has been revoked.
- The public seller page is not available.
- Public seller pages require a verified email and a connected payment provider. Verify the account email and save a provider webhook secret (or add a Lemon Squeezy account) first.
Still stuck? Email [email protected] with the order id or the product ID.