Skip to main content
In this example, we’ll set up a 30-day trial that ends when either the time limit expires or the customer runs out of credits, whichever comes first. Customers can upgrade to a paid plan at any time by adding a payment method. The trial works as follows:
  • Customers receive 1,000 free credits when they sign up. These credits expire after 30 days.
  • The trial ends when the customer runs out of credits or when 30 days have elapsed, whichever comes first.
  • Customers can use your product while they have available credits. When credits are depleted, access is blocked.
  • Customers are not invoiced during the trial. Invoices only occur after they add a payment method and upgrade to the paid plan.
  • When a customer adds a payment method, they immediately upgrade to a $20/month Pro plan that includes 20 credits per month. Additional credits can be purchased on-demand or via automatic top-ups.
Use separate plans for trial and paid tiers. This provides clear visibility into trial vs paid customers and ensures credits persist through the upgrade process.

Core approach

Two-plan model

Create two separate plans in Orb:
  1. Trial plan: No recurring charge, no credit allocation. Acts as a marker for trial customers and defines the trial period (30 days).
  2. Pro plan: $20/month recurring charge with a 20-credit monthly allocation. This is the paid tier customers upgrade to.
The trial plan does not include a credit allocation. Instead, you grant credits separately using the ledger entry API. This ensures credits persist when customers upgrade from trial to paid.
Credits granted via plan allocations expire when you change or cancel the subscription. Credits granted via the ledger entry API persist across plan changes and only expire based on their configured expiration date.
Send usage events as customers consume credits and configure your billable metric to draw down from the prepaid credit balance.

Prerequisites

Before starting a customer on a trial, set up the following:

Credit balance alerts

Create two alerts for the customer using the create alert API:
  1. credit_balance_depleted: Fires when the customer’s credit balance reaches 0
  2. credit_balance_recovered: Fires when credits are added after depletion
Set the currency of these alerts to match your pricing unit (e.g., credits). You’ll use these webhooks to block and unblock product access.

Plan creation

Create both the trial plan and Pro plan in Orb: Trial plan configuration:
  • No recurring charges
  • No credit allocations
  • Set a default subscription duration of 30 days (configure this as the plan’s default term)
  • Set external_plan_id=trial for easy reference in your code
Pro plan configuration:
  • $20 monthly recurring charge (in-advance)
  • 20-credit monthly allocation in credits currency
  • Credits expire at the end of each month (set this on the allocation)
  • Set external_plan_id=pro

Trial setup

Subscribe customer to trial plan

When a customer signs up, create a subscription to the trial plan:
subscription = orb_client.subscriptions.create(
    external_customer_id="customer-18940",
    external_plan_id="trial",
    start_date="2025-03-09T00:00:00Z",
    end_date="2025-04-08T23:59:59Z",  # 30 days from start
    net_terms=0
)
Set end_date to 30 days from the start date. This schedules automatic cancellation if the customer doesn’t upgrade.

Grant trial credits

After creating the subscription, grant credits using the create ledger entry endpoint:
orb_client.customers.credits.ledger.create_entry_by_external_id(
    external_customer_id="customer-18940",
    entry_type="increment",
    amount=1000,
    currency="credits",
    per_unit_cost_basis="0.00",  # Free trial credits
    expiry_date="2025-04-08T23:59:59Z"  # Match trial end date
)
Set per_unit_cost_basis to $0.00 for trial credits to avoid charges. Set expiry_date to match the trial end date.

Handling credit depletion

Receive webhook notification

When the customer depletes their credit balance, Orb sends a customer.credit_balance_depleted webhook to your endpoint:
{
  "type": "customer.credit_balance_depleted",
  "properties": {
    "customer_id": "customer-18940",
    "pricing_unit": {
      "name": "credits"
    }
  }
}

Block product access

Do not block access based solely on the webhook. Webhook delivery order is not guaranteed. The customer may have purchased credits between depletion and webhook delivery. Instead, query the customer’s current balance using the fetch credit balance API when you receive the webhook:
balance = orb_client.customers.credits.fetch_by_external_id(
    external_customer_id="customer-18940",
    currency="credits",
    include_all_blocks=False  # Only check currently active blocks
)

if balance["balance"] == 0:
    # Block access in your system
    block_customer_access("customer-18940")
If the balance is 0, block access in your application. Update a status field in your database that your product checks before allowing usage.

End trial early

If the customer has depleted credits and has not added a payment method, end the trial:
# Check if customer has payment method
customer = orb_client.customers.fetch_by_external_id(
    external_customer_id="customer-18940"
)

if not customer.get("payment_provider_id"):
    # No payment method, cancel subscription
    orb_client.subscriptions.cancel(
        subscription_id=subscription["id"],
        cancel_option="immediate"
    )

Handling trial expiration

30-day limit webhook

When 30 days elapse, Orb automatically cancels the subscription (based on the end_date you set). You’ll receive a subscription.ended webhook:
{
  "type": "subscription.ended",
  "subscription": {
    "id": "sub_...",
    "external_customer_id": "customer-18940",
    "status": "ended"
  }
}

Block access on expiration

When you receive this webhook, check if the customer has upgraded to a paid plan:
# List customer's active subscriptions
subscriptions = orb_client.subscriptions.list(
    external_customer_id="customer-18940",
    status="active"
)

# Check if customer has an active Pro subscription
has_active_paid_sub = any(
    sub["plan"]["external_plan_id"] == "pro"
    for sub in subscriptions["data"]
)

if not has_active_paid_sub:
    # Trial expired without upgrade, block access
    block_customer_access("customer-18940")

Upgrading to paid plan

When a customer wants to upgrade, collect payment information using Stripe’s payment method collection flows. See Stripe’s save and reuse payment methods documentation. Once the payment method is saved as the customer’s default in Stripe, Orb syncs it automatically.

Schedule plan change

When the customer adds a payment method, upgrade them immediately to the Pro plan:
orb_client.subscriptions.schedule_plan_change(
    subscription_id=subscription["id"],
    external_plan_id="pro",
    change_option="immediate",
    billing_cycle_alignment="plan_change_date"
)
Set change_option=immediate to upgrade right away. Set billing_cycle_alignment=plan_change_date to reset the billing cycle to start today and charge the full $20 monthly fee immediately. This creates an invoice for $20 and grants the customer their first 20-credit monthly allocation.

Unblock product access

After the plan change completes, unblock access:
# Fetch updated subscription
subscription = orb_client.subscriptions.fetch(subscription_id)

if subscription["plan"]["external_plan_id"] == "pro":
    # Customer upgraded, restore access
    unblock_customer_access("customer-18940")

Credit persistence

Credits granted during the trial persist through the upgrade because you granted them via the ledger entry API, not as a plan allocation. The customer keeps any remaining trial credits plus receives their first 20-credit monthly allocation from the Pro plan.

Preventing double trials

Check subscription history

Before creating a trial subscription, verify the customer hasn’t already had a trial:
# Fetch subscription schedule to see plan history
schedule = orb_client.subscriptions.fetch_schedule(
    subscription_id=subscription["id"]
)

# Check if customer was ever on trial plan
had_trial = any(
    phase["plan"]["external_plan_id"] == "trial"
    for phase in schedule
)

if had_trial:
    # Customer already had trial, don't create another
    raise Exception("Customer already used trial")
Alternatively, maintain a had_trial boolean in your database when you create the first trial subscription.

Subscription continuity

When a customer upgrades from trial to paid, Orb maintains the same subscription resource. The subscription ID remains constant; only the plan changes. This means:
  • A customer can only have one subscription lifecycle
  • Plan changes don’t reset trial eligibility
  • Subscription history provides a complete audit trail
To check if a customer ever had a trial, list their subscriptions and examine the plan history via the fetch schedule endpoint.

Manual operations

Extending trials

Extend a trial by updating the subscription’s end_date:
orb_client.subscriptions.update(
    subscription_id=subscription["id"],
    end_date="2025-04-15T23:59:59Z"  # Extend by one week
)
If you also want to extend the credit expiration, create a new ledger entry with updated credits or modify the existing block’s expiration.

Compensatory credits

Grant additional credits during the trial without charges:
orb_client.customers.credits.ledger.create_entry_by_external_id(
    external_customer_id="customer-18940",
    entry_type="increment",
    amount=500,
    currency="credits",
    per_unit_cost_basis="0.00",
    description="Compensatory credits for service interruption",
    expiry_date="2025-04-08T23:59:59Z"
)
Set per_unit_cost_basis to $0.00 to avoid invoicing. Use the description field to document the reason for the grant.