App Subscriptions
curl --request POST \
--url https://api.launchmystore.io/apps/billing/subscribe \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"monthly": 123,
"yearly": 123,
"currency": "<string>",
"appId": "<string>",
"planInterval": "<string>",
"successUrl": "<string>",
"cancelUrl": "<string>",
"installationId": "<string>"
}
'import requests
url = "https://api.launchmystore.io/apps/billing/subscribe"
payload = {
"monthly": 123,
"yearly": 123,
"currency": "<string>",
"appId": "<string>",
"planInterval": "<string>",
"successUrl": "<string>",
"cancelUrl": "<string>",
"installationId": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
monthly: 123,
yearly: 123,
currency: '<string>',
appId: '<string>',
planInterval: '<string>',
successUrl: '<string>',
cancelUrl: '<string>',
installationId: '<string>'
})
};
fetch('https://api.launchmystore.io/apps/billing/subscribe', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.launchmystore.io/apps/billing/subscribe",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'monthly' => 123,
'yearly' => 123,
'currency' => '<string>',
'appId' => '<string>',
'planInterval' => '<string>',
'successUrl' => '<string>',
'cancelUrl' => '<string>',
'installationId' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.launchmystore.io/apps/billing/subscribe"
payload := strings.NewReader("{\n \"monthly\": 123,\n \"yearly\": 123,\n \"currency\": \"<string>\",\n \"appId\": \"<string>\",\n \"planInterval\": \"<string>\",\n \"successUrl\": \"<string>\",\n \"cancelUrl\": \"<string>\",\n \"installationId\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.launchmystore.io/apps/billing/subscribe")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"monthly\": 123,\n \"yearly\": 123,\n \"currency\": \"<string>\",\n \"appId\": \"<string>\",\n \"planInterval\": \"<string>\",\n \"successUrl\": \"<string>\",\n \"cancelUrl\": \"<string>\",\n \"installationId\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.launchmystore.io/apps/billing/subscribe")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"monthly\": 123,\n \"yearly\": 123,\n \"currency\": \"<string>\",\n \"appId\": \"<string>\",\n \"planInterval\": \"<string>\",\n \"successUrl\": \"<string>\",\n \"cancelUrl\": \"<string>\",\n \"installationId\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"data.subscriptionId": "<string>",
"data.installationId": "<string>",
"data.status": "<string>",
"data.currentPeriodEnd": "<string>",
"data.url": "<string>",
"data.sessionId": "<string>",
"data.cancelAtPeriodEnd": true
}App Billing
App Subscriptions
Create, list, and cancel per-app billing subscriptions for an installed app.
POST
/
apps
/
billing
/
subscribe
App Subscriptions
curl --request POST \
--url https://api.launchmystore.io/apps/billing/subscribe \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"monthly": 123,
"yearly": 123,
"currency": "<string>",
"appId": "<string>",
"planInterval": "<string>",
"successUrl": "<string>",
"cancelUrl": "<string>",
"installationId": "<string>"
}
'import requests
url = "https://api.launchmystore.io/apps/billing/subscribe"
payload = {
"monthly": 123,
"yearly": 123,
"currency": "<string>",
"appId": "<string>",
"planInterval": "<string>",
"successUrl": "<string>",
"cancelUrl": "<string>",
"installationId": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
monthly: 123,
yearly: 123,
currency: '<string>',
appId: '<string>',
planInterval: '<string>',
successUrl: '<string>',
cancelUrl: '<string>',
installationId: '<string>'
})
};
fetch('https://api.launchmystore.io/apps/billing/subscribe', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.launchmystore.io/apps/billing/subscribe",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'monthly' => 123,
'yearly' => 123,
'currency' => '<string>',
'appId' => '<string>',
'planInterval' => '<string>',
'successUrl' => '<string>',
'cancelUrl' => '<string>',
'installationId' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.launchmystore.io/apps/billing/subscribe"
payload := strings.NewReader("{\n \"monthly\": 123,\n \"yearly\": 123,\n \"currency\": \"<string>\",\n \"appId\": \"<string>\",\n \"planInterval\": \"<string>\",\n \"successUrl\": \"<string>\",\n \"cancelUrl\": \"<string>\",\n \"installationId\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.launchmystore.io/apps/billing/subscribe")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"monthly\": 123,\n \"yearly\": 123,\n \"currency\": \"<string>\",\n \"appId\": \"<string>\",\n \"planInterval\": \"<string>\",\n \"successUrl\": \"<string>\",\n \"cancelUrl\": \"<string>\",\n \"installationId\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.launchmystore.io/apps/billing/subscribe")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"monthly\": 123,\n \"yearly\": 123,\n \"currency\": \"<string>\",\n \"appId\": \"<string>\",\n \"planInterval\": \"<string>\",\n \"successUrl\": \"<string>\",\n \"cancelUrl\": \"<string>\",\n \"installationId\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"data.subscriptionId": "<string>",
"data.installationId": "<string>",
"data.status": "<string>",
"data.currentPeriodEnd": "<string>",
"data.url": "<string>",
"data.sessionId": "<string>",
"data.cancelAtPeriodEnd": true
}App subscriptions are the recurring (or one-time) charges a merchant pays
for an installed app. They are distinct from the store-level platform
subscription at
Returns
Apps subscribe to these outbound webhook topics in their manifest — they do
NOT receive the raw Stripe webhook.
/api/v1/billing/* — each one is scoped to an
installation that links a single app to a single merchant.
A subscription is always created against an existing installation. If the
app is not installed yet, the API returns 404 — App is not installed.
Auth: merchant JWT (Authorization: Bearer <merchant-jwt>). All endpoints
below require a merchant session — they cannot be called with an OAuth
app token.
Charge approval flow
- App developer publishes pricing for their app:
POST /apps/billing/pricing/:appIdwith{ monthly, yearly, currency }. This provisions the Stripe Product + Price objects. - Merchant installs the app via the marketplace.
- App (or merchant UI) calls
POST /apps/billing/checkout-sessionto obtain a Stripe Checkout URL. The merchant is redirected, completes payment, and is returned tosuccessUrl. - Stripe fires
checkout.session.completed→POST /apps/billing/webhookverifies the signature and marks the installation assubscribedplus records a billing transaction. - Subsequent renewals fire
invoice.paidwebhooks; one transaction row is created per cycle.
POST /apps/billing/usage — see
Usage Records.
Setup pricing
string
required
Application UUID. The app must be owned by the calling developer or admin.
number
required
Monthly recurring price in major currency units (e.g.
9.99 for $9.99).number
required
Yearly recurring price in major currency units.
string
required
ISO-4217 currency code (e.g.
USD, EUR, INR).curl -X POST "https://api.launchmystore.io/apps/billing/pricing/<APP_ID>" \
-H "Authorization: Bearer <MERCHANT_JWT>" \
-H "Content-Type: application/json" \
-d '{ "monthly": 9.99, "yearly": 99.00, "currency": "USD" }'
{ status, type, message, data: { stripeProductId, stripePriceMonthly, stripePriceYearly } }.
Subscribe an installation
POST /apps/billing/subscribe creates a Stripe Subscription wired to the
merchant’s stored payment method. Use this when the merchant has already
saved a card on the platform; for first-time card collection, use
/checkout-session (below).
string
required
Application UUID. Must correspond to an existing installation for
the calling store; otherwise
404.string
required
Either
monthly or yearly. The Stripe Price for that interval must have
been provisioned via POST /pricing/:appId.curl -X POST "https://api.launchmystore.io/apps/billing/subscribe" \
-H "Authorization: Bearer <MERCHANT_JWT>" \
-H "Content-Type: application/json" \
-d '{ "appId": "fb7c…", "planInterval": "monthly" }'
string
Stripe Subscription id.
string
UUID of the installation now linked to this subscription.
string
Stripe status:
active, incomplete, incomplete_expired, past_due, canceled, trialing, unpaid.string
ISO timestamp of next renewal.
Hosted checkout session (charge approval URL)
POST /apps/billing/checkout-session returns a Stripe Checkout URL. This
is the canonical “charge approval URL” — redirect the merchant to it, they
pay, Stripe redirects them back to successUrl.
string
required
UUID of the installed app.
string
required
One of
monthly, yearly, one_time. one_time is used for non-recurring app charges.string
URL Stripe redirects to on payment success. Defaults to the app’s configured success URL.
string
URL Stripe redirects to if the merchant abandons checkout. Defaults to the app’s configured cancel URL.
curl -X POST "https://api.launchmystore.io/apps/billing/checkout-session" \
-H "Authorization: Bearer <MERCHANT_JWT>" \
-H "Content-Type: application/json" \
-d '{
"appId": "fb7c…",
"planInterval": "monthly",
"successUrl": "https://app.example.com/billing/return?success=1",
"cancelUrl": "https://app.example.com/billing/return?cancel=1"
}'
string
Stripe-hosted checkout URL. Open in a new tab or redirect.
string
cs_… Stripe Checkout Session id.string
UUID of the linked installation.
Cancel a subscription
POST /apps/billing/cancel cancels the Stripe Subscription tied to an
installation. The installation row remains; only the recurring charge is
stopped. Cancellation takes effect at the end of the current billing
period — the merchant retains access until then.
string
required
UUID of the installation. Get it from
GET /apps/installations or
from the subscribe / checkout-session response.curl -X POST "https://api.launchmystore.io/apps/billing/cancel" \
-H "Authorization: Bearer <MERCHANT_JWT>" \
-H "Content-Type: application/json" \
-d '{ "installationId": "d290f1ee-…" }'
string
Stripe subscription status after cancellation — typically
canceled (if immediate) or active with cancelAtPeriodEnd: true.boolean
true when the merchant keeps access until currentPeriodEnd.string
ISO end date.
Active subscriptions for a merchant
There is currently no dedicated “list app subscriptions for this merchant” endpoint. Use one of:GET /apps/installations(merchant) → each row carriesbilling: { subscriptionId, status, currentPeriodEnd, planInterval }.GET /apps/billing/transactions(merchant) → groups paid invoices per installation. See Transactions.
A dedicated
GET /apps/billing/subscriptions endpoint that returns just
the active subscription rows for the merchant is planned but not yet
exposed publicly.Webhook
POST /apps/billing/webhook (public, no auth) is the Stripe webhook
endpoint. The handler verifies the Stripe-Signature header against
STRIPE_APP_BILLING_WEBHOOK_SECRET and processes:
| Event | Effect |
|---|---|
checkout.session.completed | Activates the installation, records a paid billing transaction, fires the app/subscription/created outbound webhook to the app. |
invoice.paid | Inserts a paid transaction row for the renewal cycle. |
invoice.payment_failed | Marks the installation as past_due; fires app/subscription/past_due. |
customer.subscription.deleted | Marks the installation as cancelled; fires app/subscription/cancelled. |
Error codes
| Code | Description |
|---|---|
400 | Missing Stripe-Signature header or unverifiable signature (webhook only). |
400 | planInterval not one of monthly / yearly / one_time. |
401 | Missing or invalid merchant JWT. |
404 | App is not installed — call POST /apps/install/:appId first. |
500 | Stripe API error (logged with the underlying Stripe error code). |