Skip to content

How payments flow

Whichever Stripe mode you use, the sequence is the same: someone pays, Stripe fires a webhook, Keylight mints a license, your customer gets the key.

This page walks through each step for the Connect path (the recommended one). Manual webhook mode follows the same shape but with your own checkout + webhook plumbing in front.

Customerpays onStripe checkoutStripefires webhooksession.completedKeylightmints license,signs leaseYour serverreceiveslicense.createdemail license key to customer

Step 1 - your server creates a checkout session

Section titled “Step 1 - your server creates a checkout session”

You call POST /api/:tenantId/checkout with a Bearer token (the Checkout API Key, separate from the SDK key) and a JSON body:

{
"product_id": "pro",
"key_type_id": "lifetime",
"success_url": "https://yourapp.com/thanks",
"cancel_url": "https://yourapp.com/pricing",
"customer_email": "buyer@example.com"
}

Keylight:

  1. Authenticates your Checkout API key (SHA-256 compare against tenant.checkoutApiKeyHash).
  2. Checks your account’s lifecycle state allows new licenses.
  3. Looks up the key type’s stripePriceId (created when you set a price on that key type).
  4. Creates a Stripe checkout session on your connected account with a 0.5% platform fee deducted from your payout (charged via Stripe’s application_fee_amount).

Returns { url } - redirect the customer there.

Rate limit: 20 checkouts per account per minute. Exceeding returns 429.

Normal Stripe checkout. Nothing Keylight-specific happens here. On success, Stripe redirects the customer to success_url and fires a checkout.session.completed event on the platform’s Connect webhook endpoint.

Step 3 - Keylight’s Connect webhook mints the license

Section titled “Step 3 - Keylight’s Connect webhook mints the license”

Stripe posts the event to POST /api/stripe/webhook/connect. Keylight:

  1. Verifies the Stripe-Signature header against STRIPE_CONNECT_WEBHOOK_SECRET.
  2. Looks up your account by event.account (the connected Stripe account ID).
  3. Reads session.metadata.product_id to pick the product.
  4. Mints a fresh license with the correct key type’s activation limit and duration.
  5. Stores the LicenseRecord in Postgres + Durable Object.

If you have configured a License Webhook URL in Settings → Integrations, Keylight also fires an outbound license.created webhook to that URL with the raw key in the payload:

{
"id": "5d3c7e02-2b78-4f1a-b3a4-7d2f6c2e8b9d",
"created": 1714669200,
"version": "2026-05-01",
"event": "license.created",
"tenant_id": "acme",
"key": "ACME-XXXX-XXXX-XXXX-XXXX",
"product_id": "pro",
"key_type_id": "lifetime",
"customer_email": "buyer@example.com",
"session_id": "cs_live_..."
}

Your receiver verifies the X-Keylight-Signature HMAC header using the webhook secret Keylight showed you at setup. Once verified, you email the key to the customer (or whatever else your post-purchase flow does).

See the full Webhooks overview for signature-verification snippets in Node/Python/Go, retry behavior, the deliveries log, and the full list of events Keylight can send (activation, deactivation, expiry, refund, renewal).

The customer launches your app, pastes the key, and the SDK calls /activate. First activation consumes one slot; subsequent launches re-use the cached Ed25519 lease. See How it works for the SDK’s side of the handshake.

SymptomLikely cause
401 on /api/checkoutWrong or missing Checkout API key
409 on /api/checkoutTenant not connected to Stripe; reconnect via Connect
422 on /api/checkoutKey type has no Stripe price set; fix in Products → Key types
checkout.session.completed fired but no licenseWebhook signature failed; check STRIPE_CONNECT_WEBHOOK_SECRET wasn’t rotated
license.created never arrivesLicense Webhook URL isn’t configured, or every retry failed — check Dashboard → Webhooks for the delivery row and its last_status. Keylight retries 1m/5m/30m, then gives up.