SubscriptionPro — Merchant API Integration Guide
Version: 1.0 | Last Updated: April 26, 2026
This guide explains how to build a Merchant API that connects your product to SubscriptionPro. After integrating, your customers can purchase your product
through SubscriptionPro, and licenses will be generated automatically.
How It Works (Big Picture)
sequenceDiagram
participant C as Customer
participant SP as SubscriptionPro
participant M as Your Merchant API
C->>SP: Clicks "Buy Product"
SP->>M: Redirect customer to /authorize
M->>C: Shows "Authorize Access" page
C->>M: Clicks "Authorize"
M->>SP: Redirects back with ?code=xxx
SP->>M: POST /token (exchange code for access_token)
M->>SP: Returns access_token
Note over SP: Customer completes payment on SP
SP->>M: POST /license/activate (with access_token)
M->>SP: Returns license_key, status, expiry
Note over SP: Order created with license
In simple terms:
1. Customer clicks "Buy" on SubscriptionPro
2. Customer is redirected to your website to authorize access
3. After authorization, SubscriptionPro gets a token from your API
4. After payment, SubscriptionPro calls your API to generate a license
5. The license is shown to the customer in their order dashboard
What You Need to Build
Your API needs these 8 endpoints . Below is exactly what each one should do.
# Method Endpoint Purpose
1 GET /api/authorize Show authorization page to customer
Customer clicks "Authorize", redirect back with
2 GET /api/authorize/confirm
code
3 POST /api/token Exchange auth code for access token
4 POST /api/license/activate Generate a license after payment
5 POST /api/license/validate Check if a license key is valid
6 GET /api/profile Get customer profile
7 GET /api/billing Get billing history
8 POST /api/cancel Cancel subscription and revoke license
[!TIP] The paths after /api/ can be customized. You'll configure them in the SubscriptionPro merchant dashboard when setting up your product.
Step 1: Database Tables
You'll need these tables in your database:
customers — Stores customer data from SubscriptionPro
CREATE TABLE customers (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255) UNIQUE,
access_token VARCHAR(255) UNIQUE NULL,
product_identifier VARCHAR(255) NULL,
sp_customer_id BIGINT UNSIGNED NULL COMMENT 'Customer ID from SubscriptionPro',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL
);
oauth_codes — Temporary authorization codes
CREATE TABLE oauth_codes (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(255),
client_id VARCHAR(255) DEFAULT 'subscriptionpro',
product_identifier VARCHAR(255) NULL,
customer_id BIGINT UNSIGNED NULL,
used BOOLEAN DEFAULT FALSE,
expires_at TIMESTAMP NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL
);
licenses — License keys generated for customers
CREATE TABLE licenses (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
customer_id BIGINT UNSIGNED,
plan_name VARCHAR(255),
license_key VARCHAR(255) UNIQUE,
product_identifier VARCHAR(255),
status VARCHAR(50) DEFAULT 'active' COMMENT 'active, cancelled, expired, revoked',
activated_at TIMESTAMP NULL,
expires_at TIMESTAMP NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL
);
billing_records — Payment history
CREATE TABLE billing_records (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
customer_id BIGINT UNSIGNED,
plan_name VARCHAR(255),
amount DECIMAL(10,2),
transaction_id VARCHAR(255),
status VARCHAR(50) DEFAULT 'paid',
billed_at TIMESTAMP NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL
);
Step 2: Build Your Endpoints
2.1 — Authorization Page
GET /api/authorize
SubscriptionPro redirects the customer's browser to this URL. You show a page asking them to authorize access.
Query Parameters:
Parameter Type Description
Your product's unique ID in
product_identifier string
SubscriptionPro
redirect_uri string URL to redirect back to after authorization
What to do:
1. Show an HTML page with your branding
2. Display what permissions SubscriptionPro is requesting
3. Include an "Authorize" button that submits to /api/authorize/confirm
4. Include a "Deny" button that redirects back to redirect_uri
Example response: Return an HTML page (not JSON).
2.2 — Confirm Authorization
GET /api/authorize/confirm
Called when the customer clicks "Authorize". You generate a one-time code and redirect back to SubscriptionPro.
Query Parameters: Same as above ( product_identifier , redirect_uri )
What to do:
1. Generate a random code (e.g., 40 characters)
2. Save it to oauth_codes table with used = false and expires_at = now + 10 minutes
3. Redirect the customer to: {redirect_uri}?code={code}&product_identifier={product_identifier}
Example (PHP/Laravel):
public function authorizeConfirm(Request $request)
{
$code = Str::random(40);
OauthCode::create([
'code' => $code,
'product_identifier' => $request->product_identifier,
'used' => false,
'expires_at' => now()->addMinutes(10),
]);
$separator = str_contains($request->redirect_uri, '?') ? '&' : '?';
return redirect(
$request->redirect_uri . $separator
. "code={$code}&product_identifier={$request->product_identifier}"
);
}
2.3 — Token Exchange
POST /api/token
SubscriptionPro's backend calls this to exchange the one-time code for a long-lived access token.
Request Body (JSON):
{
"code": "abc123xyz...",
"product_identifier": "your-product-id",
"customer_id": 42
}
What to do:
1. Look up the code in oauth_codes — check it's not used, not expired
2. Mark the code as used = true
3. Create or update a customer record with a new access_token
4. Return the token
Response (JSON):
{
"access_token": "your_generated_token_here",
"refresh_token": "optional_refresh_token",
"token_type": "Bearer",
"customer_id": 1,
"expires_in": 31536000
}
[!IMPORTANT] The access_token is stored by SubscriptionPro and used for all future API calls. Make it long-lived (1 year recommended).
2.4 — License Activation (Most Important)
POST /api/license/activate
Called automatically by SubscriptionPro after a successful payment. This is where you generate the license key.
Request Body (JSON):
{
"access_token": "the_customers_token",
"plan_name": "Monthly Plan",
"product_identifier": "your-product-id",
"amount": 9.99
}
What to do:
1. Validate the access_token — find the customer
2. Generate a unique license key
3. Save it to licenses table with status = active
4. Return the license details
Response (JSON):
{
"success": true,
"license_key": "ABCDE-FGHIJ-KLMNO-PQRST",
"plan_name": "Monthly Plan",
"status": "active",
"activated_at": "2026-04-26T12:00:00.000000Z",
"expires_at": "2027-04-26T12:00:00.000000Z"
}
[!CAUTION] If this endpoint fails or returns an error, the customer's order will be created without a license . Make sure this endpoint is reliable.
Example (PHP/Laravel):
public function licenseActivate(Request $request)
{
$customer = Customer::where('access_token', $request->access_token)->first();
if (!$customer) {
return response()->json(['error' => 'Invalid access token'], 401);
}
// Generate a unique license key (format: XXXXX-XXXXX-XXXXX-XXXXX)
$licenseKey = strtoupper(implode('-', str_split(Str::random(20), 5)));
$license = License::create([
'customer_id' => $customer->id,
'plan_name' => $request->plan_name,
'license_key' => $licenseKey,
'product_identifier' => $request->product_identifier,
'status' => 'active',
'activated_at' => now(),
'expires_at' => now()->addYear(), // or addMonth() for monthly
]);
return response()->json([
'success' => true,
'license_key' => $licenseKey,
'plan_name' => $request->plan_name,
'status' => 'active',
'activated_at' => $license->activated_at->toISOString(),
'expires_at' => $license->expires_at->toISOString(),
]);
}
2.5 — License Validation
POST /api/license/validate
Checks if a license key is valid. Can be called by anyone with the key (no auth required).
Request Body:
{
"license_key": "ABCDE-FGHIJ-KLMNO-PQRST"
}
Response (valid):
{
"valid": true,
"license_key": "ABCDE-FGHIJ-KLMNO-PQRST",
"status": "active",
"plan_name": "Monthly Plan",
"product_identifier": "your-product-id",
"activated_at": "2026-04-26T12:00:00.000000Z",
"expires_at": "2027-04-26T12:00:00.000000Z",
"message": "License is valid and active."
}
Response (invalid):
{
"valid": false,
"message": "License key not found."
}
2.6 — Customer Profile
GET /api/profile
Returns customer info and their active license. Pass access_token as a Bearer header or query parameter.
Response:
{
"id": 1,
"name": "John Doe",
"email": "john@[Link]",
"product_identifier": "your-product-id",
"sp_customer_id": 42,
"active_license": {
"license_key": "ABCDE-FGHIJ-KLMNO-PQRST",
"plan_name": "Monthly Plan",
"status": "active",
"expires_at": "2027-04-26T12:00:00.000000Z"
}
}
2.7 — Billing History
GET /api/billing
Returns all billing records for the customer.
Response:
{
"billing": [
{
"id": 1,
"plan_name": "Monthly Plan",
"amount": 9.99,
"transaction_id": "TXN_ABC123",
"status": "paid",
"billed_at": "2026-04-26T12:00:00.000000Z"
}
]
}
2.8 — Cancel Subscription
POST /api/cancel
Called when a customer cancels their subscription on SubscriptionPro. You should revoke their active licenses.
Request Body:
{
"access_token": "the_customers_token"
}
Response:
{
"success": true,
"message": "Subscription cancelled successfully."
}
Step 3: Configure Your Product in SubscriptionPro
When creating your product in the SubscriptionPro merchant dashboard, enable "API Integration" and fill in these URLs:
Field What to enter Example
Webhook (Base URL) Your API's base URL [Link]
Authorization URL Path for OAuth page authorize
License Activation
Path for license generation license/activate
URL
Payment URL Path for payment recording payment
Profile URL Path for customer profile profile
Billing URL Path for billing history billing
Cancel URL Path for cancellation cancel
[!NOTE] The Webhook base URL is combined with each path. For example, if your webhook is [Link] and license activation URL
is license/activate , SubscriptionPro will call [Link] .
Step 4: Test Your Integration
Quick Test with cURL
1. Get a Token (simulate):
curl -X POST [Link] \
-H "Content-Type: application/json" \
-d '{"code":"test123", "product_identifier":"your-product-id", "customer_id": 1}'
2. Activate a License:
curl -X POST [Link] \
-H "Content-Type: application/json" \
-d '{
"access_token": "TOKEN_FROM_STEP_1",
"plan_name": "Monthly Plan",
"product_identifier": "your-product-id",
"amount": 9.99
}'
3. Validate the License:
curl -X POST [Link] \
-H "Content-Type: application/json" \
-d '{"license_key": "LICENSE_KEY_FROM_STEP_2"}'
Error Handling
All endpoints should return proper HTTP status codes:
Status Meaning When to use
200 Success Request completed successfully
400 Bad Request Missing or invalid parameters
401 Unauthorized Invalid or missing access token
Status Meaning Resource
When not found (e.g., invalid license
to use
404 Not Found
key)
500 Server Error Something went wrong on your end
Error response format:
{
"error": "Description of what went wrong"
}
Security Best Practices
[!WARNING] Follow these to keep your API secure:
1. Always validate access tokens — Never trust the token without checking your database
2. Use HTTPS — All API calls should be over HTTPS in production
3. Expire authorization codes quickly — 10 minutes maximum
4. Mark codes as used — Prevent code replay attacks
5. Generate unique license keys — Use cryptographically random strings
6. Rate limit your endpoints to prevent abuse
Complete Laravel Starter
Want a head start? Here's a complete Laravel controller you can copy:
# Create a new Laravel project
composer create-project laravel/laravel my-merchant-api
cd my-merchant-api
# Create your migrations
php artisan make:migration create_customers_table
php artisan make:migration create_oauth_codes_table
php artisan make:migration create_licenses_table
php artisan make:migration create_billing_records_table
# Create your models
php artisan make:model Customer
php artisan make:model OauthCode
php artisan make:model License
php artisan make:model BillingRecord
# Create the controller
php artisan make:controller MerchantController
Then add your routes in routes/[Link] :
use App\Http\Controllers\MerchantController;
Route::get('/authorize', [MerchantController::class, 'showAuthorizePage']);
Route::get('/authorize/confirm', [MerchantController::class, 'authorizeConfirm']);
Route::post('/token', [MerchantController::class, 'token']);
Route::post('/license/activate', [MerchantController::class, 'licenseActivate']);
Route::post('/license/validate', [MerchantController::class, 'licenseValidate']);
Route::get('/profile', [MerchantController::class, 'profile']);
Route::get('/billing', [MerchantController::class, 'billing']);
Route::post('/cancel', [MerchantController::class, 'cancel']);
Need Help?
Check the Demo Merchant API source code for a complete working example
Contact SubscriptionPro support for integration assistance
Make sure your Webhook base URL is accessible from the internet (not localhost)