Stripe Integration Guide
Connect your Stripe account to ExisOne to automatically generate and email license keys when customers purchase on your website.
1. Get Your Stripe API Keys
- Log into Stripe Dashboard.
- Go to Developers > API keys.
- Copy your Publishable key (
pk_test_...orpk_live_...). - Copy your Secret key (
sk_test_...orsk_live_...).
2. Configure Stripe Webhook
- In Stripe Dashboard, go to Developers > Webhooks.
- Click Add endpoint.
- Set the endpoint URL to:
https://www.exisone.com/api/stripe/webhook - Select these events to listen for:
checkout.session.completed- Primary event for paymentspayment_intent.succeeded- Backup for one-time paymentsinvoice.paid- For subscription renewals
- Click Add endpoint, then copy the Signing secret (starts with
whsec_).
3. Register Your Stripe App in ExisOne
- In ExisOne, go to Stripe Integration > Stripe Apps and Events.
- Click Add App and enter:
- Publishable key - from step 1
- Secret key - from step 1
- Webhook signing secret - from step 2
- Default Currency - the currency Stripe will charge customers in for one-time Checkout sessions (e.g.
usd,aud,eur). See Currency Selection below.
- Set Is Live to match your keys (unchecked for test keys, checked for live keys).
- Check Active and Default.
- Save the app.
Currency Selection
The Default Currency dropdown is populated from the EO_Currency reference table. It controls the currency used when ExisOne creates a one-time Stripe Checkout session for this app. Stripe requires that the currency match what your Stripe account is configured to accept in your country/region — if a currency is rejected at checkout, verify your Stripe account settings.
- One-time payments use this default currency. It is stored per Stripe app, so different apps under one tenant can charge in different currencies.
- Per-request override: the
POST /api/stripe/checkout/sessionendpoint accepts an optionalcurrencyfield that overrides the app default for a single session. Useful for showing a storefront price in the visitor's local currency. - Subscriptions use the currency embedded in the Stripe
price_…ID you map under Subscription Price Mappings. The Default Currency setting is ignored for subscription checkouts. - Zero-decimal currencies (JPY, KRW, and a few others) are flagged in the currency table; ExisOne automatically sends integer amounts to Stripe for those rather than multiplying the product price by 100.
- Adding currencies: if you need a code that isn't in the dropdown, insert a row into
EO_CurrencywithIsActive = 1. Codes must be lowercase ISO 4217 (Stripe requirement).
Price on your Product is a raw decimal — if you switch Default Currency from usd to aud, a product priced at 99.00 will charge 99 AUD, not a converted amount. Update product prices if you change currency.
ExisOne uses the webhook signing secret to verify incoming webhooks are from your Stripe account.
App Status Indicators
After saving, the Stripe Apps table shows a Status column to help you verify your configuration:
| Status | Meaning | Action Required |
|---|---|---|
| Ready | Secret key and webhook signing secret are both configured and can be decrypted | None - your app is ready to process payments |
| Partial | Secret key is configured, but webhook signing secret is missing | Add your webhook signing secret (whsec_...) from Stripe |
| Not configured | Secret key is missing or cannot be decrypted | Enter your secret key (sk_test_... or sk_live_...). If you already entered it, ensure you have an active tenant encryption key. |
4. Configure Your Product in ExisOne
For each product you sell, configure these settings on the Products page:
- Auto Email: Check this box to automatically email license keys after purchase.
- Default License Days: Set to 0 for Perpetual, or a number of days for time-limited licenses.
- Email Template (optional): Customize the email sent to customers.
4b. Configure Subscription Price Mappings (Optional)
If you want to sell subscription-based licenses where ExisOne creates the Stripe checkout session, you can map your products to Stripe Price IDs:
- In Stripe Dashboard, create a recurring price for your product under Products.
- Copy the Price ID (starts with
price_...). - In ExisOne, go to Stripe Apps and Events.
- Scroll to Subscription Price Mappings.
- Click Save after entering:
- Stripe App - Select your Stripe app
- Product - Select your ExisOne product
- Stripe Price ID - Paste the Price ID from Stripe
- Display Name (optional) - e.g., "Monthly", "Annual"
- Display Price (optional) - e.g., 9.99
You can map multiple prices to the same product for different tiers (e.g., "Starter" at $9.99/month and "Pro" at $29.99/month).
5. Find Your Product ID
You'll need your Product ID to include in Stripe checkout sessions. Find it on the Products page - it's displayed in the ID column.
42, you'll use "productId": "42" in your Stripe metadata.
6. How the Payment Flow Works
Your Website Stripe ExisOne
| | |
|-- Create checkout session ----->| |
| (using Stripe API directly) | |
| metadata: { productId: "42" } | |
| | |
|<---- Return checkout URL -------| |
| | |
| Customer completes payment | |
| | |
| |-- Webhook ------------------ >|
| | |
| | Verify signature (matches |
| | your stored webhook secret) |
| | |
| | Extract productId from |
| | metadata |
| | |
| | Generate license key |
| | |
| | Email key to customer |
| | |
7. Integrate Stripe on Your Website
Use Stripe's API directly on your website. The key is to include your ExisOne productId in the checkout session metadata.
Server-Side (Node.js Example)
const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');
app.post('/create-checkout-session', async (req, res) => {
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{
price_data: {
currency: 'usd',
unit_amount: 2999, // $29.99 in cents
product_data: {
name: 'Pro License',
},
},
quantity: 1,
}],
metadata: {
productId: '42', // Your ExisOne Product ID - REQUIRED
quantity: '1' // Number of licenses to generate
},
customer_email: req.body.email, // Optional: pre-fill customer email
success_url: 'https://yoursite.com/success',
cancel_url: 'https://yoursite.com/cancel',
});
res.json({ url: session.url });
});
Client-Side (JavaScript)
<button onclick="buyLicense()">Buy Pro License - $29.99</button>
<script>
async function buyLicense() {
// Call your server to create checkout session
const response = await fetch('/create-checkout-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: customerEmail })
});
const { url } = await response.json();
window.location.href = url; // Redirect to Stripe Checkout
}
</script>
metadata.productId field is required. Without it, ExisOne cannot determine which product to generate a license for.
Required Metadata Fields (One-Time Payments)
| Field | Required | Description |
|---|---|---|
productId | Yes | Your ExisOne Product ID (from Products page) |
quantity | No | Number of license keys to generate (default: 1) |
7b. Subscription Licenses
For subscription-based licensing (e.g., annual renewals), ExisOne generates a new license key on each billing cycle - both on initial signup and every renewal.
productId in the subscription metadata, not just the checkout session metadata. The subscription metadata persists and is read on each renewal.
Server-Side Subscription Example (Node.js)
const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');
app.post('/create-subscription-checkout', async (req, res) => {
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [{
price: 'price_YOUR_RECURRING_PRICE_ID', // Created in Stripe Dashboard
quantity: 1,
}],
// IMPORTANT: Use subscription_data.metadata for renewals to work
subscription_data: {
metadata: {
productId: '42', // Your ExisOne Product ID - REQUIRED
quantity: '1' // Number of licenses per renewal
}
},
customer_email: req.body.email,
success_url: 'https://yoursite.com/success',
cancel_url: 'https://yoursite.com/cancel',
});
res.json({ url: session.url });
});
How Subscription Licensing Works
Initial Subscription Renewals (Annual)
| |
|-- checkout.session.completed --> |-- invoice.paid ----------------->
| |
| ExisOne reads productId from | ExisOne fetches subscription
| session metadata | from Stripe, reads productId
| | from subscription metadata
| |
| Generates license key | Generates NEW license key
| Emails to customer | Emails to customer
| |
Required Metadata Fields (Subscriptions)
| Field | Location | Required | Description |
|---|---|---|---|
productId | subscription_data.metadata | Yes | Your ExisOne Product ID - must be on the subscription for renewals |
quantity | subscription_data.metadata | No | Number of license keys per billing cycle (default: 1) |
price_xxx) to use in your code.
7c. Using Stripe Payment Links (No Website Required)
If you want to sell a license without building a checkout flow on your website, you can create a Stripe Payment Link directly in the Stripe Dashboard and email it to your customer. ExisOne will still auto-generate and deliver the license key as long as you attach the right metadata to the link.
Payment Links are ideal for:
- One-off invoices or custom quotes sent by email.
- Sales you handle manually (phone, chat, in-person).
- A simple "Buy Now" URL embedded in a blog post or social media bio.
How to create the link
- In Stripe Dashboard, go to Payment Links > + New.
- Select the Stripe Product and Price you want to sell (or create a new one).
- Leave Collect customer email enabled (it is by default) — ExisOne uses this to deliver the license key.
- Expand Advanced options > Metadata and add the following keys:
| Key | Required | Description |
|---|---|---|
productId | Yes | Your ExisOne Product ID (from the Products page). |
quantity | No | Number of license keys to generate (default: 1). |
planId | No | ExisOne plan ID to override the product's default license duration. |
Save the link, copy the URL, and send it to your customer. When they complete payment, Stripe fires a checkout.session.completed webhook that ExisOne receives; the metadata you set on the Payment Link is forwarded on that session automatically, and the license key is generated and emailed to the address the customer entered at checkout.
- The Payment Link must be created under the same Stripe account whose webhook signing secret is registered in ExisOne — otherwise the webhook won't match any registered Stripe app and no key will be generated.
- The Payment Link's currency is set in Stripe when you build the link. The Default Currency setting on the ExisOne Stripe app only affects sessions that ExisOne creates itself (via
POST /api/stripe/checkout/session); it does not override Payment Link currency. - If you change the Payment Link's Stripe Product/Price, double-check that
productIdstill points at the correct ExisOne product.
8. Test Your Integration
- Make sure you're using test mode keys (starting with
pk_test_andsk_test_). - Trigger a checkout from your website.
- Complete payment using Stripe test card:
4242 4242 4242 4242(any future expiry, any CVC). - Check Stripe Apps and Events in ExisOne - you should see the webhook event with status "verified".
- Verify the license key appears under License Keys in the ExisOne dashboard.
- Confirm the customer received the license key email (if Auto Email is enabled).
You can also use ExisOne's built-in Test Purchase page to verify webhook connectivity before integrating with your website.
9. Notes
- One-time payments: The
productIdandquantityare read from the Checkout Session metadata. - Subscriptions: Initial purchase reads from session metadata. Renewals read from subscription metadata (set via
subscription_data.metadata). Without this, renewal keys won't be generated. - Perpetual (0 days) overrides any incoming days; otherwise an explicit plan days value overrides product defaults.
- Emails use the product's template (placeholders:
{{ActivationKey}},{{ProductName}},{{CustomerEmail}},{{PaymentProcessorCustomerName}}). - Trials: validation without a key returns a trial if the device was never activated for that product and the product has TrialDays > 0.
Troubleshooting
Status shows "Partial" (missing webhook signing secret)
This means your secret key is configured but the webhook signing secret is missing. To fix this:
- Go to Stripe Dashboard > Webhooks (use test or live mode as appropriate).
- If you don't have a webhook endpoint yet, click Add endpoint:
- Set the URL to:
https://www.exisone.com/api/stripe/webhook - Select events:
checkout.session.completed,payment_intent.succeeded,invoice.paid - Click Add endpoint
- Set the URL to:
- Click on your endpoint, then find Signing secret and click Reveal.
- Copy the value (starts with
whsec_...). - In ExisOne, go to Stripe Apps and Events, click Edit on your app.
- Paste the signing secret into the Webhook Signing Secret field and click Save.
The status should change from "Partial" to "Ready".
Status shows "Not configured" (missing secret key)
This means the secret key is missing or cannot be decrypted. To fix this:
- Go to Stripe Dashboard > API keys.
- Copy your Secret key (
sk_test_...orsk_live_...). - In ExisOne, go to Stripe Apps and Events, click Edit on your app.
- Paste the secret key into the Secret Key field and click Save.
If you still see "Not configured" after saving, you may need to generate a tenant encryption key first. Go to Settings > Tenant Keys and ensure you have an active key.
Subscription renewals not generating license keys
If initial subscription purchases generate keys but renewals don't, the issue is likely missing subscription metadata:
- Ensure you're using
subscription_data.metadata(not justmetadata) when creating the checkout session. - The
productIdmust be on the subscription object itself, not just the checkout session. - Verify the subscription has metadata by checking in Stripe Dashboard: Customers > [Customer] > Subscriptions > [Subscription] > Metadata.
- For existing subscriptions missing metadata, you can add it via Stripe Dashboard or API:
stripe subscriptions update sub_xxx --metadata[productId]=42 - Make sure your webhook endpoint is listening for
invoice.paidevents.
Signature verification fails ("no app matched signature")
- Ensure the webhook Signing secret (
whsec_...) is copied from the exact Stripe endpoint that targetshttps://YOUR-SITE/api/stripe/webhook(test vs live must match). - Re-enter the secret in Stripe Apps and Events and Save; avoid leading/trailing spaces.
- We tolerate Stripe API version mismatches during verification. Optionally, set your Stripe webhook endpoint API version to match your library (e.g.,
2024-04-10) for stricter validation. - Timestamp tolerance is 300s. Use a brand-new checkout rather than resending very old events.
Diagnostics
Verify that your saved secrets decrypt correctly for the right tenant:
GET /api/stripe/diagnostics
Authorization: ExisOneApi <your_token>
Both HasSecret and HasWebhookSigningSecret should be true for your app. If not, generate/activate a tenant key (Tenant Keys page), then re-save your secrets.
Event log
- View events in the UI or
GET /api/stripe/events. - Status values:
failed(includes first error detail),received,verified.
10. Setup Checklist
Before going live, verify all items are complete:
| Step | Where | Status |
|---|---|---|
| Stripe API keys obtained | Stripe Dashboard | |
| Webhook endpoint created pointing to ExisOne | Stripe Dashboard > Webhooks | |
| Stripe app registered with keys and webhook secret | ExisOne > Stripe Apps | |
| Product created in ExisOne | ExisOne > Products | |
| Product has Auto Email enabled | ExisOne > Products | |
| Email template configured | ExisOne > Products > Email Template | |
| Subscription price mappings configured (if using subscriptions) | ExisOne > Stripe Apps > Price Mappings | |
| Website integration includes productId in metadata | Your website code | |
| Test purchase completed successfully | Your website (test mode) | |
| Webhook received and verified in ExisOne | ExisOne > Stripe Events | |
| License key generated and emailed | ExisOne > License Keys |
11. Go Live
- In Stripe Dashboard, switch to live mode and create a new webhook endpoint pointing to
https://www.exisone.com/api/stripe/webhook. - In ExisOne, add a new Stripe app with your live keys (
pk_live_...,sk_live_...) and the live webhook signing secret. Check Is Live and Active. - Update your website to use your live Stripe secret key.
- Make a real purchase (you can refund it) to verify the complete flow works in production.