Appearance
LemonSqueezy Setup Guide
How to configure LemonSqueezy as the payment provider for SchemaStack.
1. Create a LemonSqueezy Account
Sign up at lemonsqueezy.com and create a store.
2. Store ID
- Go to Settings → Stores in the LemonSqueezy dashboard
- Copy the Store ID (numeric, e.g.
12345) - Set it in your environment:
PAYMENT_LEMONSQUEEZY_STORE_ID=12345
3. API Key
- Go to Settings → API in the LemonSqueezy dashboard
- Click Create API Key
- Give it a name (e.g.
schemastack-production) - Copy the key — it's only shown once
- Set it in your environment:
PAYMENT_LEMONSQUEEZY_API_KEY=eyJ0eXAi...
Important: LemonSqueezy has separate test mode and live mode. Test mode uses a different API key, separate products/variants, and no real charges. Toggle test mode in the dashboard header.
4. Create Products and Variants
Each subscription tier needs a Product with monthly and/or yearly Variants in LemonSqueezy.
Recommended Setup
| Product | Variant (Monthly) | Variant (Yearly) |
|---|---|---|
| Pro Plan | Pro Monthly | Pro Yearly |
| Enterprise | Enterprise Monthly | Enterprise Yearly |
Steps:
- Go to Store → Products
- Click New Product
- Set the product name (e.g. "Pro Plan")
- Under Pricing, select Subscription
- Add variants for each billing interval:
- Pro Monthly: set monthly price
- Pro Yearly: set yearly price
- Save and note the Variant IDs (visible in the URL or via API)
Getting Variant IDs
Variant IDs are visible in the LemonSqueezy dashboard URL when viewing a variant, or via the API:
bash
curl -s https://api.lemonsqueezy.com/v1/variants \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/vnd.api+json" | jq '.data[].id'Map Variants to Plan Provider Mappings
Each variant ID must be stored in the plan_provider_mapping table:
sql
INSERT INTO plan_provider_mapping (tier, billing_interval, provider, provider_plan_id, provider_variant_id, price_cents, currency)
VALUES
('PRO', 'MONTHLY', 'LEMONSQUEEZY', 'product_id', 'variant_id_monthly', 1900, 'USD'),
('PRO', 'YEARLY', 'LEMONSQUEEZY', 'product_id', 'variant_id_yearly', 19000, 'USD'),
('ENTERPRISE', 'MONTHLY', 'LEMONSQUEEZY', 'product_id', 'variant_id_monthly', 4900, 'USD'),
('ENTERPRISE', 'YEARLY', 'LEMONSQUEEZY', 'product_id', 'variant_id_yearly', 49000, 'USD');Replace product_id and variant_id_* with actual LemonSqueezy IDs.
5. Webhook Configuration
Go to Settings → Webhooks in the LemonSqueezy dashboard
Click Create Webhook (the
+button)Configure:
- URL:
https://your-domain.com/api/webhooks/payment/LEMONSQUEEZY - Signing Secret: Choose a strong secret (6-40 characters). This is used for HMAC-SHA256 signature verification.
- Events: Subscribe to these events:
subscription_createdsubscription_updatedsubscription_cancelledsubscription_expiredsubscription_pausedsubscription_resumedsubscription_unpausedorder_createdsubscription_payment_successsubscription_payment_failed
- URL:
Set the signing secret in your environment:
PAYMENT_LEMONSQUEEZY_WEBHOOK_SECRET=your-strong-secret-here
How Webhook Verification Works
LemonSqueezy sends an X-Signature header with each webhook request. The signature is an HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret. Our WebhookProcessingService verifies this before processing any event.
Custom Data in Checkouts
When SchemaStack creates a checkout URL, it includes custom_data with:
organisation_id— links the subscription back to the orgtier_name— the subscription tier being purchasedbilling_interval— MONTHLY or YEARLY
This data is passed through by LemonSqueezy in webhook events, allowing us to associate subscriptions with organisations.
6. Environment Variable Summary
| Variable | Description | Example |
|---|---|---|
PAYMENT_PROVIDER | Active payment provider | LEMONSQUEEZY (default) |
PAYMENT_LEMONSQUEEZY_API_KEY | API key from dashboard | eyJ0eXAi... |
PAYMENT_LEMONSQUEEZY_STORE_ID | Store ID from Settings → Stores | 12345 |
PAYMENT_LEMONSQUEEZY_WEBHOOK_SECRET | Signing secret from webhook config | my-secret |
These map to application.properties:
properties
payment.provider=${PAYMENT_PROVIDER:LEMONSQUEEZY}
payment.lemonsqueezy.api-key=${PAYMENT_LEMONSQUEEZY_API_KEY:}
payment.lemonsqueezy.store-id=${PAYMENT_LEMONSQUEEZY_STORE_ID:}
payment.lemonsqueezy.webhook-secret=${PAYMENT_LEMONSQUEEZY_WEBHOOK_SECRET:}7. Test Mode vs Live Mode
| Aspect | Test Mode | Live Mode |
|---|---|---|
| API Key | Separate test key | Separate live key |
| Products/Variants | Test products only | Live products only |
| Webhooks | Test webhooks | Live webhooks |
| Charges | No real charges | Real money |
| Dashboard toggle | Top-right switch | Top-right switch |
Recommendation: Set up products, variants, and webhooks in test mode first. Once verified, recreate them in live mode with the same structure and update your environment variables.
8. Testing Webhooks Locally
LemonSqueezy can only deliver webhooks to a public URL. To receive them on your local machine, use a tunnel:
bash
# Install ngrok if you don't have it
brew install ngrok # Homebrew
# Or download directly: https://ngrok.com/download
# Expose local Quarkus (port 8080) to the internet
ngrok http 8080This gives you a public URL like https://abc123.ngrok-free.app. Set your LemonSqueezy webhook URL to:
https://abc123.ngrok-free.app/api/webhooks/payment/LEMONSQUEEZYNow any webhook events from LemonSqueezy (test mode purchases, subscription changes) will be forwarded to your local Quarkus instance. The ngrok terminal shows all incoming requests in real time.
Tip: The ngrok URL changes every time you restart it (unless you have a paid plan with a fixed domain). Update the webhook URL in the LemonSqueezy dashboard each time.
9. Verifying the Setup
- Start ngrok and update the webhook URL in LemonSqueezy dashboard
- Test webhook delivery: LemonSqueezy lets you send test webhook events from the webhook settings page
- Create a test checkout: Call
POST /api/billing/checkoutwith a valid tier and interval — you should get a LemonSqueezy checkout URL - Complete a test purchase: Use the checkout URL in test mode (no real charges)
- Verify subscription sync: After checkout, the webhook should fire and your org's subscription tier should update automatically