How To: Sell Through Third-Party Marketplaces¶
This guide walks you through distributing your PyLocket-protected application on third-party marketplaces such as AppSumo, Gumroad, Paddle, and LemonSqueezy — where the marketplace (not your own Stripe) processes customer payments.
Overview¶
When you sell through a third-party marketplace, the payment flow is handled by the marketplace. PyLocket provides two integration methods for provisioning licenses to marketplace customers:
Option A (Redemption Codes):
You → Generate codes in portal → Upload codes to marketplace → Customer redeems → License + Download
Option B (API Provisioning):
Marketplace → Webhook to your backend → Your backend calls PyLocket API → License + Download
Choose Option A when the marketplace supports distributing redemption codes (AppSumo, manual email campaigns, partner deals). Choose Option B when you have a backend that can receive webhooks and call APIs in real time (Gumroad, Paddle, LemonSqueezy, or any custom system).
Supported Marketplaces¶
| Marketplace | Typical Integration | Notes |
|---|---|---|
| AppSumo | Redemption codes (Option A) | AppSumo distributes codes to customers directly |
| Gumroad | API provisioning (Option B) | Gumroad fires webhooks on purchase |
| Paddle | API provisioning (Option B) | Paddle fires webhooks on order completion |
| LemonSqueezy | API provisioning (Option B) | LemonSqueezy fires webhooks on order creation |
| Manual / Partner | Redemption codes (Option A) | For email campaigns, partner bundles, review copies |
Option A: Redemption Codes¶
Best for AppSumo deals and manual distribution where you generate codes upfront and hand them off to the marketplace or directly to customers.
Step 1: Create a Batch of Codes¶
In the Developer Portal, go to Redemption Codes and click Create Batch.
Configure:
- Application: Select the app customers will receive
- Channel: Select the marketplace (e.g.,
appsumo) - Quantity: How many codes to generate (e.g., 500)
- Label: A descriptive name (e.g., "AppSumo Launch — Tier 1")
- Device Limit: How many devices each license allows
- License Expiry: Set to 0 for perpetual, or a number of days
- License Type: Perpetual, subscription, or trial
Click Generate Codes. The portal shows all generated codes once — save them immediately.
Step 2: Download the CSV¶
Click Download CSV to get all codes in a spreadsheet-compatible format. Each code follows the format PLR-XXXXXXXX-XXXX.
Step 3: Upload Codes to the Marketplace¶
For AppSumo:
- Go to your AppSumo partner dashboard
- Navigate to your product's code management section
- Upload the CSV file containing the redemption codes
- AppSumo distributes these codes to customers upon purchase
For manual distribution:
- Send codes via email, share in a spreadsheet, or include in partner bundles
- Each code can only be redeemed once
Step 4: Customer Redeems a Code¶
When a customer receives their code, they visit the PyLocket redemption page:
Or you can provide a pre-filled link:
The customer:
- Enters their redemption code
- Optionally provides their email (for license key backup)
- Clicks Redeem Code
- Receives their license key and download link
Step 5: Monitor Redemptions¶
In the Developer Portal, the Redemption Codes page shows:
- Batch progress: Redeemed count vs total (with progress bar)
- Individual code status: Available, Redeemed, Revoked, or Expired
- Redeemed by: Customer email (if provided)
- Timestamp: When each code was redeemed
Step 6: Revoke Unused Codes¶
If a deal ends or you need to recall unused codes:
- Open the batch in the portal
- Click Revoke All to revoke all unredeemed codes
- Or revoke individual codes from the codes table
Revoked codes return a "code has been revoked" error if someone tries to redeem them.
Option B: API Provisioning¶
Best for Gumroad, Paddle, LemonSqueezy, and custom backends that can receive webhooks in real time.
Step 1: Get Your API Key¶
- Go to Developer Portal → Settings → API Keys
- Generate or rotate your API key
- Save the key securely — it is shown only once
Step 2: Set Up a Webhook Receiver¶
Create an endpoint on your backend that receives purchase webhooks from the marketplace.
Python example (Flask):
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
PYLOCKET_API_KEY = "your-api-key-here"
PYLOCKET_API_URL = "https://api.pylocket.com/v1/marketplace/licenses"
APP_ID = "your-app-uuid"
@app.route("/webhook/purchase", methods=["POST"])
def handle_purchase():
payload = request.json
customer_email = payload["email"]
order_id = payload["order_id"]
# Call PyLocket to create a license
response = requests.post(
PYLOCKET_API_URL,
headers={
"X-API-Key": PYLOCKET_API_KEY,
"Content-Type": "application/json",
},
json={
"app_id": APP_ID,
"channel": "gumroad", # or paddle, lemonsqueezy, etc.
"customer_email": customer_email,
"marketplace_order_id": order_id,
"license_config": {
"device_limit": 1,
"expiry_days": None, # perpetual
"license_type": "perpetual",
},
},
)
result = response.json()
# Send the license key + download URL to the customer
send_email_to_customer(
email=customer_email,
license_key=result["license_key"],
download_url=result["download_url"],
)
return jsonify({"status": "ok"})
cURL example:
curl -X POST https://api.pylocket.com/v1/marketplace/licenses \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"app_id": "your-app-uuid",
"channel": "gumroad",
"customer_email": "customer@example.com",
"marketplace_order_id": "order-12345",
"license_config": {
"device_limit": 1,
"expiry_days": null,
"license_type": "perpetual"
}
}'
Response:
{
"license_key": "XXXX-XXXX-XXXX-XXXX",
"license_id": "uuid-here",
"app_name": "Your App",
"download_url": "https://cdn.pylocket.com/v1/downloads/signed-token"
}
Step 3: Per-Marketplace Webhook Setup¶
Gumroad:
- Go to Gumroad → Settings → Webhooks (under the "Advanced" tab)
- Add your endpoint URL:
https://yourbackend.com/webhook/purchase - Gumroad sends a POST with
email,product_id,sale_id, etc.
Paddle:
- Go to Paddle → Developer Tools → Events
- Subscribe to the
transaction.completedevent - Paddle sends a POST with
customer.email,transaction_id, etc.
LemonSqueezy:
- Go to LemonSqueezy → Settings → Webhooks
- Add your endpoint URL and subscribe to
order_created - LemonSqueezy sends a POST with
data.attributes.user_email,data.id, etc.
Step 4: Error Handling¶
Your webhook handler should handle these cases:
| HTTP Status | Meaning | Action |
|---|---|---|
201 |
License created successfully | Send license to customer |
400 |
Invalid request (missing fields) | Log and investigate |
401 |
Invalid API key | Check your API key |
404 |
App not found | Check your app_id |
429 |
Rate limit exceeded | Retry after the Retry-After header value |
Billing¶
Marketplace-sourced licenses follow the same billing model as direct Stripe licenses:
- $4 per license charged at the time of redemption (Option A) or API provisioning (Option B)
- Billed via your existing Stripe metered billing
- No upfront cost for generating redemption codes — you are only charged when a code is actually redeemed
- Usage events are idempotent — redeeming or provisioning the same license twice does not double-charge
This means you can generate 1,000 codes for an AppSumo deal but only pay for the 347 that actually get redeemed.
Multi-Tier Deals (AppSumo Stacking)¶
AppSumo deals often use a tiered pricing model. To support this, create a separate batch for each tier with different license configurations:
| Tier | Batch Label | Device Limit | Features |
|---|---|---|---|
| Tier 1 | "AppSumo Launch — Tier 1" | 1 | {"tier": "basic"} |
| Tier 2 | "AppSumo Launch — Tier 2" | 3 | {"tier": "pro"} |
| Tier 3 | "AppSumo Launch — Tier 3" | 5 | {"tier": "business", "priority_support": true} |
In your application's Runtime, read the features field from the license to determine which tier the customer is on:
import pylocket
license_info = pylocket.get_license_info()
tier = license_info.get("features", {}).get("tier", "basic")
if tier == "business":
enable_priority_support()
enable_advanced_analytics()
elif tier == "pro":
enable_advanced_analytics()
Tracking & Analytics¶
Source Column¶
The Licenses page in the Developer Portal now shows a Source column with color-coded badges:
- Purple: AppSumo
- Indigo: Stripe (direct)
- Pink: Gumroad
- Blue: Paddle
- Yellow: LemonSqueezy
- Gray: Manual
- Green: API
Use this to track which marketplace channels are driving the most license activations.
Batch Monitoring¶
The Redemption Codes page shows real-time batch progress:
- Total codes generated vs redeemed (with progress bar)
- Per-code status and redemption details
- Export data to CSV for marketplace reporting
Troubleshooting¶
| Issue | Cause | Resolution |
|---|---|---|
| "Redemption code not found" | Typo in code or wrong code format | Verify the code matches the PLR-XXXXXXXX-XXXX format |
| "This code has already been redeemed" | Code used by another customer | Each code is single-use; generate more codes if needed |
| "This code has expired" | Code past its expiry date | Generate a fresh batch with no expiry or a longer expiry |
| "This code has been revoked" | Code was manually revoked | Issue a replacement code from a new batch |
| "Rate limit exceeded" (redemption page) | More than 5 redemption attempts per minute from same IP | Wait 60 seconds and retry |
API returns 401 Unauthorized |
Invalid or expired API key | Rotate your API key in Settings → API Keys |
API returns 429 Too Many Requests |
Exceeded 60 requests per minute | Implement exponential backoff in your webhook handler |
See Also¶
- Distribute Your App — Direct Stripe distribution workflow
- Configure Licensing — Device limits, expiration, offline grace
- Billing & Pricing — How PyLocket charges work
- REST API Reference — Full API documentation