License Server — Internal
The license server (littlebig-license-server) is a Cloudflare Worker that handles all licensing for LittleBig: key generation, domain activation, plugin update delivery, credit management, the product catalog, Paddle provisioning, community access and the daily check.
Most day-to-day work now happens in the Product Admin, which calls the endpoints below. The raw curl examples remain for debugging and scripts.
Base URL: https://license.littlebig.co (the old littlebig-license-server.andredaus.workers.dev address still answers)
Auth: All admin endpoints require Authorization: Bearer {ADMIN_TOKEN}.
Issue a new license
Section titled “Issue a new license”curl -X POST https://littlebig-license-server.andredaus.workers.dev/v1/admin/licenses \ -H "Authorization: Bearer {ADMIN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "product_id": "prod-wpl-01", "tier_id": "tier-wpl-silver", "customer_email": "customer@example.com", "customer_name": "Customer Name", "seat_limit": 3, "billing_cycle": "annual", "source": "manual", "notes": "" }'The server generates a LBL-XXXX-XXXX-XXXX-XXXX key and auto-seeds credits from the tier configuration. Response:
{ "id": "lic-...", "key": "LBL-XXXX-XXXX-XXXX-XXXX", "product_id": "prod-wpl-01", "tier_id": "tier-wpl-silver", "customer_email": "customer@example.com", "status": "active", "seat_limit": 3, "credits_seeded": 50}Product and tier IDs
Section titled “Product and tier IDs”| Product | product_id |
Tier | tier_id |
Credits seeded | Seats |
|---|---|---|---|---|---|
| WP Luminary | prod-wpl-01 |
Bronze | tier-wpl-bronze |
0 (own API key) | 1 |
| WP Luminary | prod-wpl-01 |
Silver | tier-wpl-silver |
50 | 3 |
| WP Luminary | prod-wpl-01 |
Gold | tier-wpl-gold |
unlimited | 10 |
List licenses
Section titled “List licenses”curl "https://littlebig-license-server.andredaus.workers.dev/v1/admin/licenses?product_id=prod-wpl-01&status=active&limit=50" \ -H "Authorization: Bearer {ADMIN_TOKEN}"Query parameters: product_id, status (active | suspended | expired), customer_email, limit (default 20), offset.
Update a license
Section titled “Update a license”Change status, expiry, seat limit, feature overrides, or notes on an existing license:
curl -X PATCH "https://littlebig-license-server.andredaus.workers.dev/v1/admin/licenses/{LICENSE_ID}" \ -H "Authorization: Bearer {ADMIN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "status": "suspended", "notes": "Chargeback — suspended pending resolution" }'Patchable fields: status, expires_at, seat_limit, features_override (JSON string), notes.
Grant or deduct credits
Section titled “Grant or deduct credits”curl -X POST https://littlebig-license-server.andredaus.workers.dev/v1/admin/credits/grant \ -H "Authorization: Bearer {ADMIN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "license_id": "lic-...", "delta": 25, "reason": "top-up after support case" }'Use a negative delta to deduct credits. The reason field is logged in credit_transactions for the audit trail.
Register a plugin version (update delivery)
Section titled “Register a plugin version (update delivery)”After CI pushes a ZIP to R2, register the version so GET /v1/update/:slug returns it:
curl -X POST https://littlebig-license-server.andredaus.workers.dev/v1/admin/versions \ -H "Authorization: Bearer {ADMIN_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "product_id": "prod-wpl-01", "version": "1.3.0", "zip_key": "wp-luminary/wp-luminary-1.3.0.zip", "changelog": "Silver tier and threat telemetry.", "min_wp": "6.4", "min_php": "8.2", "tested_wp": "6.7", "released_at": "2026-06-30T00:00:00Z" }'The zip_key is the R2 object key in the littlebig-plugin-zips bucket. The update endpoint returns a 1-hour signed URL for this key.
Validate a license (public endpoint)
Section titled “Validate a license (public endpoint)”The plugin calls this on activation and periodically to refresh seat/credit state. Not normally called manually, but useful for debugging:
curl -X POST https://littlebig-license-server.andredaus.workers.dev/v1/license/validate \ -H "Content-Type: application/json" \ -d '{ "key": "LBL-XXXX-XXXX-XXXX-XXXX", "domain": "example.com" }'Local domains (localhost, *.local, *.test, *.ddev.site, *.lndo.site, 127.0.0.1, ::1) are auto-detected and do not consume a seat.
Catalog, Paddle and community (added October 2026)
Section titled “Catalog, Paddle and community (added October 2026)”Migrations 0018 to 0022 in littlebig-core/schema/migrations extend the database. All additive.
| Migration | Adds |
|---|---|
0018_catalog |
Product status, description, attributes, community kind and slug; tier status, trial_days, attributes; price amount_minor, currency, status; admin_audit_log |
0019_product_sales_channel |
products.sold_via_paddle (1 = sold through Paddle) |
0020_paddle_provisioning |
product_paddle (Paddle product per environment), provisioning_steps |
0021_community_grants |
community_grants ledger |
0022_reconcile_runs |
reconcile_runs |
(There is no 0017: the unapplied Cloudflare Course migration was dropped. Create that product in the Product Admin.)
Paddle webhook behaviour
Section titled “Paddle webhook behaviour”transaction.completedandsubscription.createdissue a license, then enrol the buyer in the community. Plugin buyers get a key email; course and service buyers do not.subscription.canceledcancels the license and removes the community access (unless another active license grants it).- The buyer’s email is checked first. If it cannot receive mail, the license is still issued with an
ATTENTIONnote, but nothing is mailed to it, no community account is created, and the administrator gets a report email.
Secrets and bindings
Section titled “Secrets and bindings”| Name | Kind | Purpose |
|---|---|---|
ADMIN_TOKEN |
Secrets Store | Authorises /v1/admin/* |
PADDLE_API_KEY |
Secrets Store | Paddle API (sandbox) |
PADDLE_WEBHOOK_SECRET |
Worker secret | Verifies Paddle webhooks |
TOSEND_API_KEY |
Secrets Store (tosend-api-key) |
All outgoing mail |
USERCHECK_API_KEY, USERCHECK_CACHE |
Secrets Store, KV | Domain checks; the cache is shared with the auth server and the newsletter |
COMMUNITY_API_USER, COMMUNITY_API_PASSWORD |
Worker secrets | FluentCommunity / WordPress REST API |
RECONCILE_APPLY, RECONCILE_MAX_REMOVALS, REPORT_EMAIL, COMMUNITY_COURSE_OWNER_ID |
Variables | See Product Admin |
All mail goes through toSend (src/lib/mail.ts) from licenses@notify.littlebig.co, replies to the contact address. ZeptoMail is no longer configured on this Worker. If toSend fails, the failure is logged and shown on the test-email button.
bun test in packages/license-server (the Paddle webhook suite creates its own local Secrets Store entries; it needs CLOUDFLARE_ACCOUNT_ID set).
Open Issues
Section titled “Open Issues”- Scheduled daily check is off. The Workers Free plan allows 5 cron triggers and they are all used. Uncomment
[triggers]inwrangler.tomlafter upgrading. - Paddle production does not exist yet. Everything runs in the sandbox, and “publish to live” is not built.
- Paddle to license issuing in the daily check is not built: it compares subscription status only.
Other pending features:
- License portal — self-service customer view of keys, activations, and credit balance (not yet built)
- Webhook on status change — notify external systems when a license is suspended or expires
- x402 credit top-up —
POST /v1/proxy/topupstub returns 501; full USDC payment flow not yet implemented