Skip to main content

Overview

billing.io lets you create recurring billing plans and subscribe your customers to them. Payments are collected in crypto at each billing interval through automated checkout sessions. This guide covers the full lifecycle: creating plans, subscribing customers, handling renewals, checking entitlements, and managing lifecycle events.
Prerequisites — You need at least one active payment method configured before creating subscriptions. Subscription billing requires the Growth plan or above.

Step 1: Create a Customer

Every subscription is tied to a customer. Create one first.
curl -X POST https://api.billing.io/v1/customers \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@example.com",
    "name": "Jane Doe",
    "external_id": "usr_12345",
    "metadata": {
      "company": "Acme Corp",
      "plan_source": "website"
    }
  }'
Response:
{
  "id": "cus_a1b2c3d4e5f6a7b8c9d0e1f2",
  "org_id": "org_...",
  "email": "jane@example.com",
  "name": "Jane Doe",
  "external_id": "usr_12345",
  "metadata": {
    "company": "Acme Corp",
    "plan_source": "website"
  },
  "environment": "live",
  "created_at": "2025-06-15T10:00:00Z",
  "updated_at": "2025-06-15T10:00:00Z"
}
Use external_id to map billing.io customers to users in your own system. This makes it easy to look up customers by your internal ID. The combination of (org_id, email, environment) and (org_id, external_id, environment) must be unique.

Step 2: Create a Subscription Plan

A subscription plan defines the recurring billing terms: price, interval, token, chain, and optional trial period.
curl -X POST https://api.billing.io/v1/subscriptions/plans \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Pro Monthly",
    "description": "Full access to all Pro features",
    "amount_usd": 19.99,
    "interval": "monthly",
    "trial_days": 14,
    "token": "USDT",
    "chain": "tron"
  }'
Response:
{
  "id": "plan_c3d4e5f6a7b8c9d0e1f2a3b4",
  "org_id": "org_...",
  "name": "Pro Monthly",
  "description": "Full access to all Pro features",
  "amount": 19.99,
  "currency": "USD",
  "token": "USDT",
  "interval": "monthly",
  "interval_count": 1,
  "trial_days": 14,
  "status": "active",
  "environment": "live",
  "created_at": "2025-06-15T10:05:00Z",
  "updated_at": "2025-06-15T10:05:00Z"
}
Available intervals: daily, weekly, monthly, yearly
Plans can be archived with PATCH /subscriptions/plans/{id} by setting status to archived. Archived plans cannot be assigned to new subscriptions but existing subscriptions remain active.

Step 3: Create a Subscription

Subscribe a customer to a plan. If the plan has trial_days > 0, the subscription starts in trialing status. Otherwise, the first renewal checkout is created immediately.
curl -X POST https://api.billing.io/v1/subscriptions \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_id": "cus_a1b2c3d4e5f6a7b8c9d0e1f2",
    "plan_id": "plan_c3d4e5f6a7b8c9d0e1f2a3b4",
    "payment_method_id": "pm_8f3a1b2c4d5e6f7a"
  }'
Response:
{
  "id": "sub_d4e5f6a7b8c9d0e1f2a3b4c5",
  "org_id": "org_...",
  "customer_id": "cus_a1b2c3d4e5f6a7b8c9d0e1f2",
  "plan_id": "plan_c3d4e5f6a7b8c9d0e1f2a3b4",
  "payment_method_id": "pm_8f3a1b2c4d5e6f7a",
  "status": "trialing",
  "current_period_start": "2025-06-15T10:10:00Z",
  "current_period_end": "2025-06-29T10:10:00Z",
  "trial_end": "2025-06-29T10:10:00Z",
  "canceled_at": null,
  "cancel_at_period_end": false,
  "pause_start": null,
  "pause_end": null,
  "environment": "live",
  "created_at": "2025-06-15T10:10:00Z",
  "updated_at": "2025-06-15T10:10:00Z"
}
The payment_method_id field is optional. If omitted, billing.io uses your organization’s default payment method.

Step 4: Handle the Renewal Checkout Flow

When a billing period ends, billing.io automatically creates a renewal record and a checkout for the next payment. Here is how the flow works:
┌──────────────────────────────────────────────────────────────────────┐
│                     Subscription Renewal Flow                        │
│                                                                      │
│  Period End                                                          │
│      │                                                               │
│      v                                                               │
│  Renewal record created (status: pending)                            │
│      │                                                               │
│      v                                                               │
│  Checkout created automatically (status: checkout_created)           │
│      │                                                               │
│      v                                                               │
│  Customer pays the checkout                                          │
│      │                                                               │
│      ├── Payment confirmed ──> Renewal: paid ──> Period advanced     │
│      │                                                               │
│      └── Payment failed/expired ──> Renewal: failed                  │
│              │                                                       │
│              v                                                       │
│         Retry (up to max_attempts)                                   │
│              │                                                       │
│              └── All retries exhausted ──> Subscription: past_due    │
└──────────────────────────────────────────────────────────────────────┘

Listing renewals

curl "https://api.billing.io/v1/subscriptions/renewals?subscription_id=sub_d4e5f6a7b8c9d0e1f2a3b4c5&limit=10" \
  -H "Authorization: Bearer sk_live_xxx"

Retrying a failed renewal

If a renewal fails, you can manually trigger a retry to create a new checkout:
curl -X POST https://api.billing.io/v1/subscriptions/renewals/ren_1a2b3c4d5e/retry \
  -H "Authorization: Bearer sk_live_xxx"

Step 5: Check Entitlements

Entitlements let you attach feature flags to subscription plans. You can gate features in your application based on what plan a customer is on.

Create an entitlement on a plan

curl -X POST https://api.billing.io/v1/subscriptions/entitlements \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "plan_id": "plan_c3d4e5f6a7b8c9d0e1f2a3b4",
    "feature_key": "api_requests",
    "value_type": "numeric",
    "value_numeric": 10000
  }'

Check a customer’s entitlement

Use the check endpoint to determine if a customer has access to a specific feature at runtime:
curl "https://api.billing.io/v1/subscriptions/entitlements/check?customer_id=cus_a1b2c3d4e5f6a7b8c9d0e1f2&feature_key=api_requests" \
  -H "Authorization: Bearer sk_live_xxx"
Response:
{
  "has_access": true,
  "feature_key": "api_requests",
  "value_type": "numeric",
  "value_boolean": null,
  "value_numeric": 10000,
  "value_string": null,
  "plan_id": "plan_c3d4e5f6a7b8c9d0e1f2a3b4",
  "subscription_id": "sub_d4e5f6a7b8c9d0e1f2a3b4c5"
}
Entitlements support three value types:
Value TypeFieldUse Case
booleanvalue_booleanFeature on/off flags (e.g., advanced_analytics: true)
numericvalue_numericUsage limits (e.g., api_requests: 10000)
stringvalue_stringTier labels or config (e.g., support_level: "priority")

Step 6: Handle Lifecycle Events

Subscriptions move through a defined state machine. Set up webhooks to react to lifecycle transitions.

Subscription status flow

            ┌────────────────────────────────────────────┐
            │                                            │
            v                                            │
  ┌──────────────┐     ┌─────────┐     ┌──────────┐     │
  │   trialing   │ ──> │  active  │ ──> │ past_due │ ────┘
  └──────────────┘     └─────────┘     └──────────┘  (payment recovered)
                          │    │            │
                          │    │            v
                          │    │     ┌──────────┐
                          │    └───> │  paused   │
                          │         └──────────┘
                          │              │
                          v              v
                    ┌──────────┐   ┌──────────┐
                    │ canceled  │   │ expired   │
                    └──────────┘   └──────────┘

Key webhook events

EventDescriptionRecommended Action
subscription.renewedPeriod payment confirmedExtend access for the new period
subscription.past_duePayment failed after retriesWarn customer, offer retry
subscription.pausedSubscription pausedRestrict feature access
subscription.canceledSubscription canceledRevoke access at period end

Handling lifecycle transitions

app.post("/webhooks/billing", express.raw({ type: "application/json" }), async (req, res) => {
  const event = JSON.parse(req.body);

  switch (event.type) {
    case "subscription.renewed": {
      // Payment was successful -- extend access
      const { subscription_id, customer_id } = event.data;
      await extendAccess(customer_id, subscription_id);
      break;
    }

    case "subscription.past_due": {
      // Payment failed -- notify the customer
      const { customer_id, subscription_id } = event.data;
      await sendPaymentFailedEmail(customer_id);
      await flagAccountForRetry(subscription_id);
      break;
    }

    case "subscription.paused": {
      // Subscription paused -- restrict access
      const { customer_id } = event.data;
      await restrictAccess(customer_id);
      break;
    }

    case "subscription.canceled": {
      // Subscription canceled -- schedule access revocation
      const { customer_id, current_period_end } = event.data;
      await scheduleAccessRevocation(customer_id, current_period_end);
      break;
    }
  }

  res.status(200).json({ received: true });
});

Managing subscription state

Cancel at end of period or immediately:
# Cancel at end of current period
curl -X PATCH https://api.billing.io/v1/subscriptions/sub_d4e5f6a7b8c9d0e1f2a3b4c5 \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"action": "cancel"}'

# Cancel immediately
curl -X PATCH https://api.billing.io/v1/subscriptions/sub_d4e5f6a7b8c9d0e1f2a3b4c5 \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"action": "cancel_immediately"}'

Next Steps