Docs Subscription Management

Subscription Management

Everything you need to manage recurring billing through stryhub. From creating subscriptions to handling upgrades, cancellations, trials, and failed payments — all powered by Stripe.

Overview

stryhub gives you full subscription management on top of your existing Stripe account. You don't need to build billing infrastructure from scratch — connect your Stripe account, and stryhub handles the rest.

Key capabilities:

No code for basic setup. If you just need checkout pages and subscription tracking, connect your Stripe account and you're done. Webhooks are only needed if you want to automate access control in your SaaS (grant/revoke features based on subscription status).

How It Works

stryhub sits between Stripe and your SaaS, acting as a management layer:

Stripe
← products & payments →
stryhub
← webhooks →
Your SaaS
  1. Stripe stores your products, prices, and handles payment processing
  2. stryhub reads your products automatically, generates checkout pages, manages subscriptions, and tracks analytics
  3. Your SaaS receives webhook events from stryhub to grant/revoke access, send emails, and update your database
stryhub does not replace Stripe. It's a management layer on top of it. Your Stripe account, products, customers, and payment history remain unchanged. stryhub just makes them easier to manage.

Subscription Lifecycle

Every subscription goes through a series of states. Understanding these states is essential for managing access in your SaaS.

Status Meaning Action in your SaaS
trialing Customer is in a free trial period. No charge yet. Grant full access. Show trial expiration date.
active Subscription is paid and in good standing. Grant full access.
past_due Most recent payment failed. Stripe is retrying. Keep access (grace period). Notify customer to update payment method.
paused Billing is temporarily paused. Subscription still exists. Limit or revoke access. Show "subscription paused" message.
unpaid All retry attempts failed. No more automatic retries. Revoke access. Prompt customer to pay manually or update payment method.
canceled Subscription has been canceled. This is a terminal state. Revoke access. Offer resubscription option.
incomplete First payment attempt failed or requires authentication. Do not grant access yet. Wait for payment to complete.

State transitions

lifecycle
trialing ──> active ──> canceled
                │
                ├──> past_due ──> active (payment succeeds)
                │        │
                │        └──> unpaid ──> canceled
                │
                └──> paused ──> active (resumed)
                         │
                         └──> canceled

incomplete ──> active (payment succeeds)
     │
     └──> incomplete_expired (23h timeout)

Setting Up Products

stryhub has a complete product and price management system built into the dashboard. You can create products, set prices, customize checkout options, and get shareable payment links — all in one step, without ever touching the Stripe Dashboard.

Creating a product in stryhub

  1. In the admin dashboard, go to Programs and click the Products icon next to your program
  2. Click New Product
  3. Fill in the product details:
    • Name — the product name your customers will see
    • Description — optional description
    • Image — product image (JPEG, PNG, or WEBP, max 2MB)
    • Statement descriptor — what appears on the customer's credit card statement (max 22 characters)
    • Unit label — e.g., "license", "seat", "user"
  4. Set the price:
    • Amount — the price value (e.g., 29.90)
    • Currency — BRL, USD, or EUR
    • Type — Recurring (subscription) or One-time (single purchase)
    • Billing interval (for recurring): Daily, Weekly, Monthly, Quarterly, Yearly, or custom
  5. Configure checkout options (see below)
  6. Click Create
One step, everything ready. When you create a product in stryhub, it automatically creates the product in Stripe, sets up the price, and generates a shareable Payment Link — all at once. No need to open the Stripe Dashboard.

Checkout customization

When creating a product (or editing a payment link later), you can configure what data is collected at checkout:

Option Description
Collect name Require customer's full name
Collect phone Require phone number
Require billing address Collect full billing address
Collect tax ID Collect tax identification (CPF/CNPJ)
Custom fields (up to 2) Add custom text fields to the checkout form (e.g., "Company name", "License key")
Completion message Custom message shown after successful purchase (max 500 characters)
Payment methods Choose which methods to accept: Card, Boleto, PIX (PIX is excluded for subscriptions automatically)

Adding more prices to a product

You can add multiple prices to the same product — for example, a monthly and an annual plan:

  1. Go to Products in your program
  2. Click the + button next to the product
  3. Set the new price amount, currency, and interval
  4. Each price gets its own checkout link and Payment Link

Price configuration examples

Scenario Price type Interval
Monthly subscription Recurring Every 1 month
Annual subscription (discounted) Recurring Every 1 year
Quarterly billing Recurring Every 3 months
One-time purchase One-time N/A

Managing products and prices

From the Products page in the dashboard, you can:

Already have products in Stripe?

If you're connecting an existing Stripe account that already has products and prices, they sync automatically. You don't need to recreate anything.

Two-way visibility. Products created in stryhub appear in your Stripe Dashboard, and products already in Stripe appear in stryhub. Both are fully managed — you can use either interface, but stryhub is faster and easier for most tasks.

Checkout & Subscription Creation

When you create a product in stryhub, two types of checkout links are generated automatically:

1. Payment Link (direct sales)

A shareable Stripe Payment Link is created automatically for each price. You can find it in the Products page — just copy the link and share it anywhere (website, email, social media). No affiliate tracking needed.

2. Affiliate checkout link

For affiliate-tracked sales, the format is:

URL
https://app.stryhub.com/checkout/{affiliate_ref_code}/{stripe_price_id}

Affiliates see their personalized checkout links in the "My Links" page. Each link has their ref code embedded, so commissions are tracked automatically.

Both links are generated automatically. When you create a product in stryhub, the Payment Link and all affiliate checkout links are ready instantly. No extra configuration needed.

What happens after checkout

  1. Customer completes payment on the Stripe-hosted checkout page
  2. Stripe creates the subscription and charges the first invoice
  3. stryhub receives the event and creates the subscription record
  4. stryhub fires the following webhooks to your SaaS:
    • checkout.completed — checkout session completed
    • payment.completed — first payment succeeded
    • subscription.created — new subscription is active
  5. If the sale came through an affiliate link, the commission is calculated and transferred automatically

Supported payment methods

You choose which payment methods to accept when creating the product:

PIX is not supported for subscriptions. When you create a recurring price, PIX is automatically excluded from the payment methods. It's only available for one-time purchases.

Handling Webhooks

This is the most important section if you want to automate access control in your SaaS. stryhub forwards subscription events to your webhook endpoints as simplified, consistent payloads.

Subscription events reference

Event When it fires What to do in your SaaS
checkout.completed Customer completes a purchase Create user account, grant access, send welcome email
payment.completed Payment succeeds (initial or recurring) Extend access period, send receipt
subscription.created New subscription starts Store subscription ID, provision features
subscription.renewed Recurring payment succeeds Extend access period
subscription.canceled Subscription is canceled Revoke access, send cancellation email
subscription.paused Subscription billing is paused Limit or revoke access, notify user
subscription.resumed Paused subscription reactivated Restore full access
payment.failed Payment attempt fails Notify customer, show payment update prompt
subscription.past_due Invoice is overdue Warn customer, consider a grace period before revoking access
customer.created New customer registered Create customer record in your database

Example: full webhook handler

Here's a complete example of how to handle subscription events in your SaaS. This code verifies the webhook signature and routes each event to the appropriate handler.

const crypto = require('crypto');
const express = require('express');
const app = express();

const WEBHOOK_SECRET = process.env.STRYHUB_WEBHOOK_SECRET;

// IMPORTANT: use express.raw() to get the raw body for signature verification
app.post('/webhooks/stryhub', express.raw({ type: 'application/json' }), async (req, res) => {
  const signature = req.headers['x-stryhub-signature'];

  // 1. Verify signature
  if (!verifySignature(req.body.toString(), signature, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = JSON.parse(req.body);
  const { type, data } = event;

  // 2. Handle each event type
  switch (type) {
    case 'checkout.completed':
      // New customer just purchased — create their account
      await createUserAccount({
        email: data.customer.email,
        name: data.customer.name,
        stripeCustomerId: data.customer.stripe_customer_id,
        subscriptionId: data.subscription_id,
        plan: data.mode, // 'subscription' or 'payment'
      });
      break;

    case 'subscription.created':
      // Subscription is now active — grant access
      await grantAccess(data.customer.stripe_customer_id, data.subscription_id);
      break;

    case 'subscription.renewed':
      // Recurring payment succeeded — extend access
      await extendAccess(data.customer.stripe_customer_id, data.subscription_id);
      break;

    case 'subscription.canceled':
      // Subscription canceled — revoke access
      await revokeAccess(data.customer.stripe_customer_id, data.subscription_id);
      await sendEmail(data.customer.email, 'Your subscription has been canceled');
      break;

    case 'subscription.paused':
      // Subscription paused — limit access
      await pauseAccess(data.customer.stripe_customer_id, data.subscription_id);
      break;

    case 'subscription.resumed':
      // Subscription resumed — restore access
      await grantAccess(data.customer.stripe_customer_id, data.subscription_id);
      break;

    case 'payment.failed':
      // Payment failed — notify customer
      await sendEmail(data.customer.email, 'Your payment failed. Please update your payment method.');
      break;

    case 'subscription.past_due':
      // Invoice overdue — show warning (keep access during grace period)
      await flagPastDue(data.customer.stripe_customer_id, data.subscription_id);
      break;

    case 'customer.created':
      // New customer — store in your database
      await createCustomerRecord(data.customer);
      break;
  }

  // 3. Always respond 200 quickly
  res.status(200).json({ received: true });
});

function verifySignature(rawBody, signatureHeader, secret) {
  const parts = {};
  signatureHeader.split(',').forEach(p => {
    const [key, value] = p.split('=');
    parts[key] = value;
  });
  const signedContent = `${parts['t']}.${rawBody}`;
  const expected = crypto.createHmac('sha256', secret).update(signedContent).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(parts['v1'], 'hex'), Buffer.from(expected, 'hex'));
}
import hmac, hashlib, json, os
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = os.environ['STRYHUB_WEBHOOK_SECRET']

@app.route('/webhooks/stryhub', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Stryhub-Signature', '')

    # 1. Verify signature
    if not verify_signature(request.data, signature, WEBHOOK_SECRET):
        return jsonify({'error': 'Invalid signature'}), 401

    event = json.loads(request.data)
    event_type = event['type']
    data = event['data']

    # 2. Handle each event type
    if event_type == 'checkout.completed':
        create_user_account(
            email=data['customer']['email'],
            name=data['customer']['name'],
            stripe_customer_id=data['customer']['stripe_customer_id'],
            subscription_id=data.get('subscription_id'),
        )

    elif event_type == 'subscription.created':
        grant_access(data['customer']['stripe_customer_id'], data['subscription_id'])

    elif event_type == 'subscription.renewed':
        extend_access(data['customer']['stripe_customer_id'], data['subscription_id'])

    elif event_type == 'subscription.canceled':
        revoke_access(data['customer']['stripe_customer_id'], data['subscription_id'])
        send_email(data['customer']['email'], 'Your subscription has been canceled')

    elif event_type == 'subscription.paused':
        pause_access(data['customer']['stripe_customer_id'], data['subscription_id'])

    elif event_type == 'subscription.resumed':
        grant_access(data['customer']['stripe_customer_id'], data['subscription_id'])

    elif event_type == 'payment.failed':
        send_email(data['customer']['email'], 'Your payment failed. Please update your payment method.')

    elif event_type == 'subscription.past_due':
        flag_past_due(data['customer']['stripe_customer_id'], data['subscription_id'])

    elif event_type == 'customer.created':
        create_customer_record(data['customer'])

    # 3. Always respond 200 quickly
    return jsonify({'received': True}), 200

def verify_signature(raw_body, signature_header, secret):
    parts = dict(p.split('=', 1) for p in signature_header.split(','))
    signed_content = f"{parts['t']}.{raw_body.decode('utf-8')}"
    expected = hmac.new(secret.encode(), signed_content.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(parts['v1'], expected)
<?php
$webhookSecret = getenv('STRYHUB_WEBHOOK_SECRET');
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_STRYHUB_SIGNATURE'] ?? '';

// 1. Verify signature
if (!verifySignature($rawBody, $signature, $webhookSecret)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

$event = json_decode($rawBody, true);
$type = $event['type'];
$data = $event['data'];

// 2. Handle each event type
switch ($type) {
    case 'checkout.completed':
        createUserAccount(
            $data['customer']['email'],
            $data['customer']['name'],
            $data['customer']['stripe_customer_id'],
            $data['subscription_id'] ?? null
        );
        break;

    case 'subscription.created':
        grantAccess($data['customer']['stripe_customer_id'], $data['subscription_id']);
        break;

    case 'subscription.renewed':
        extendAccess($data['customer']['stripe_customer_id'], $data['subscription_id']);
        break;

    case 'subscription.canceled':
        revokeAccess($data['customer']['stripe_customer_id'], $data['subscription_id']);
        sendEmail($data['customer']['email'], 'Your subscription has been canceled');
        break;

    case 'subscription.paused':
        pauseAccess($data['customer']['stripe_customer_id'], $data['subscription_id']);
        break;

    case 'subscription.resumed':
        grantAccess($data['customer']['stripe_customer_id'], $data['subscription_id']);
        break;

    case 'payment.failed':
        sendEmail($data['customer']['email'], 'Payment failed. Please update your payment method.');
        break;

    case 'subscription.past_due':
        flagPastDue($data['customer']['stripe_customer_id'], $data['subscription_id']);
        break;

    case 'customer.created':
        createCustomerRecord($data['customer']);
        break;
}

// 3. Always respond 200 quickly
http_response_code(200);
echo json_encode(['received' => true]);

function verifySignature(string $rawBody, string $signatureHeader, string $secret): bool {
    $parts = [];
    foreach (explode(',', $signatureHeader) as $part) {
        [$key, $value] = explode('=', $part, 2);
        $parts[$key] = $value;
    }
    $signedContent = "{$parts['t']}.{$rawBody}";
    $expected = hash_hmac('sha256', $signedContent, $secret);
    return hash_equals($parts['v1'] ?? '', $expected);
}
?>
Respond quickly. Return a 200 status code within 15 seconds. If you need to do heavy processing (e.g., sending emails, querying external APIs), acknowledge the webhook first and process asynchronously. See the Webhook Integration guide for signature verification details and retry policy.

Customer Portal

The Stripe Billing Portal is a secure, Stripe-hosted page where your customers can manage their own subscriptions — without you building any UI.

What customers can do

Configuration

In the stryhub admin dashboard, go to Billing Portal to configure which features are available:

Setting What it controls
Allow cancellation Whether customers can cancel their subscription from the portal
Allow plan changes Whether customers can upgrade/downgrade to different prices
Allow payment updates Whether customers can update their payment method

Opening the portal for a customer

From the stryhub admin dashboard, go to Customers, click on a customer, and use the "Open Customer Portal" button. This generates a secure, one-time link that redirects the customer to their Stripe-hosted management page.

No code needed. The customer portal is fully hosted by Stripe. It automatically matches your branding (logo, colors) as configured in your Stripe Dashboard. You don't need to build or maintain any UI for subscription management.

Portal capabilities at a glance

Built-in churn prevention

When a customer attempts to cancel their subscription through the portal, Stripe can automatically present a discount offer to retain them. This is configured in the Stripe Dashboard under Billing Portal settings — you can define the discount percentage and duration (e.g., "50% off for 3 months").

Additionally, when customers do proceed with cancellation, their cancellation reasons are collected automatically. These insights are available in your Stripe Dashboard and help you understand why customers leave, enabling data-driven product improvements.

Churn prevention requires no code. Both the retention discount offer and cancellation reason collection are configured entirely in the Stripe Dashboard. The portal handles the UX — you just analyze the results.

Upgrades & Downgrades

When a customer changes their plan (e.g., from Basic to Pro), Stripe automatically handles proration — calculating the credit for unused time on the old plan and charging the difference for the new plan.

How proration works

Example: Customer on $10/month upgrades to $20/month on day 15 of a 30-day cycle:

How customers upgrade/downgrade

  1. Through the Customer Portal (if plan changes are enabled)
  2. Through the Stripe Dashboard directly
  3. Via the Stripe API from your SaaS backend

When a plan change happens, stryhub fires a subscription.created webhook with the updated subscription details (new price, new amount).

Configure available plans. In the stryhub admin under Billing Portal, you can specify which products and prices are available for plan changes. This prevents customers from switching to plans you don't intend them to see.

Cancellations

Subscriptions can be canceled in two ways:

Method Behavior Recommended for
At end of period Customer keeps access until the current billing period ends. No refund. Most use cases. Better customer experience.
Immediately Subscription ends right away. Access is revoked immediately. Policy violations, fraud, or customer requests immediate stop.

Who can cancel

Webhook fired

When a subscription is canceled, stryhub fires subscription.canceled with the customer details and cancellation timestamp. Your SaaS should revoke access when it receives this event.

End-of-period is recommended. Immediate cancellation stops access right away, which can frustrate customers who already paid for the current period. Use "cancel at end of period" unless there's a specific reason for immediate cancellation.

Pausing & Resuming

Instead of canceling, you can pause a subscription. This stops payment collection while keeping the subscription record intact — making it easy for the customer to come back.

How it works

Admin actions

From the stryhub dashboard, go to Subscriptions, find an active subscription, and click Pause. To resume, find the paused subscription and click Resume.

Webhooks fired

Free Trials

Free trials let customers try your product before being charged. During the trial, a subscription exists with status trialing.

Setting up trials

Free trials are configured at the price level in Stripe. You can set this up when creating a price in the Stripe Dashboard:

  1. Edit your product's price and look for the Free trial option
  2. Set the number of trial days (e.g., 7 or 14)
  3. Choose what happens when the trial ends without a payment method:
    • Cancel — subscription is canceled automatically
    • Pause — subscription pauses until customer adds a payment method
stryhub reads trial configuration automatically. Once you set a trial period on a price in Stripe, stryhub uses it for all checkouts with that price. No additional configuration needed in stryhub.

Trial timeline

When What happens
Trial starts Subscription created with status trialing. No charge.
3 days before trial ends Stripe sends customer.subscription.trial_will_end event. Use this to remind the customer.
Trial ends First invoice is generated. If payment succeeds, status changes to active.
Trial ends (no payment method) Subscription is canceled or paused based on your configuration.
No payment method required. You can offer free trials without requiring a credit card upfront. Stripe will attempt to charge the customer when the trial ends — if no payment method is on file, the subscription will be canceled or paused.

Failed Payments & Dunning

When a recurring payment fails, Stripe doesn't give up immediately. It uses Smart Retries — a machine-learning system that analyzes payment patterns to determine the optimal time to retry.

What happens when a payment fails

  1. Stripe attempts to charge the customer's payment method
  2. If it fails, Stripe schedules automatic retries over the next few weeks
  3. The subscription status changes to past_due
  4. stryhub fires payment.failed and subscription.past_due webhooks
  5. Stripe sends dunning emails to the customer automatically
  6. If a retry succeeds, the subscription returns to active
  7. If all retries fail, the subscription may be canceled (configurable in Stripe Dashboard)

What your SaaS should do

Event Recommended action
payment.failed Show a banner in your app: "Your payment failed. Please update your payment method." Include a link to the Customer Portal.
subscription.past_due Implement a grace period (e.g., 7 days). Keep access but show warnings. After the grace period, limit access to essential features only.
subscription.canceled Revoke access completely. Offer a link to resubscribe.

Configuring retry behavior

In your Stripe Dashboard > Settings > Billing > Automatic collection, you can configure:

Stripe handles retries automatically. You don't need to build retry logic. Smart Retries uses ML to find the best time to charge, significantly reducing involuntary churn. Your only job is to notify the customer and offer a way to update their payment method.

How Smart Retries work

Smart Retries are powered by Stripe's machine learning engine, not fixed schedules. The ML model analyzes multiple signals to determine the optimal retry timing for each individual charge:

Stripe performs up to 8 automatic retry attempts over approximately 2 weeks, with each retry timed for maximum likelihood of success.

Automatic Card Updater

One of the most common causes of involuntary churn is expired or replaced credit cards. Stripe eliminates this problem with the Automatic Card Updater.

When a customer's bank issues a new card — whether due to expiration, loss, or a routine replacement — Stripe automatically receives the updated card details directly from Visa and Mastercard networks. The subscription continues without interruption, and the customer doesn't need to do anything.

Card updates happen silently. The Automatic Card Updater works behind the scenes with no action required from you or your customers. This single feature eliminates a significant portion of involuntary churn caused by expired cards.

Global Payments & Compliance

stryhub + Stripe supports payments from customers worldwide with enterprise-grade compliance built in.

Multi-Currency Support

Accept payments in 135+ currencies with automatic currency conversion. Customers pay in their local currency while you receive funds in yours. Stripe handles all exchange rate calculations.

3D Secure & SCA Compliance

For European customers, Strong Customer Authentication (SCA) is required under PSD2. Stripe automatically triggers 3D Secure authentication when needed, ensuring your subscriptions comply with European regulations without any additional development.

Tax Automation (Stripe Tax)

Stripe Tax automatically calculates and collects the correct tax amount based on your customer's location:

Stripe monitors your tax obligations and alerts you when you approach registration thresholds in new jurisdictions. Tax-compliant invoices are generated automatically.

Migrating to stryhub

If you already use Stripe (or another platform), migrating to stryhub is straightforward. Your existing data stays intact.

Step-by-step migration

  1. Create your stryhub account at app.stryhub.com
  2. Connect your existing Stripe account — go to Settings and click "Connect Stripe Account." Your products, prices, and customers sync automatically.
  3. Sync existing data — use the "Sync Data" button in the admin dashboard to import all existing subscriptions and customers from Stripe.
  4. Configure webhook endpoints — go to Webhooks and add your SaaS endpoint to start receiving events.
  5. Update your SaaS — implement a webhook handler (see Handling Webhooks above) to manage access based on subscription events.
  6. Configure the Customer Portal — go to Billing Portal and enable the features you want (cancellation, plan changes, payment updates).
  7. Replace checkout links — swap your old checkout URLs with stryhub checkout links. Existing subscriptions continue to work — only new purchases use the stryhub checkout.
  8. Set up affiliates (optional) — create an affiliate program to grow your sales. See the Affiliate Programs guide.
Zero downtime migration. Your existing Stripe subscriptions, customers, and payment history remain completely untouched. stryhub reads your data — it never modifies or deletes anything in your Stripe account. You can run both systems in parallel during the transition.

Coming from another platform?

If you're migrating from a platform that doesn't use Stripe (e.g., Paddle, Gumroad, PayPal), you'll need to:

  1. Create a Stripe account if you don't have one
  2. Recreate your products and prices in Stripe
  3. Ask existing customers to resubscribe through the new stryhub checkout link
  4. Run both systems in parallel until all customers have migrated
Need help migrating? If you have questions about the migration process, reach out to our support team. We're happy to help you plan the transition.