Subscriptions
Integrate recurring billing with payment links, webhooks, and subscription management.
Integrate Subscriptions
This guide walks through implementing recurring billing with Bag โ from creating a subscription-enabled payment link to handling renewal webhooks and managing subscription lifecycle.
Step 1: Create a subscription payment link
Create a payment link with a recurring billing cycle. Use the dashboard or API:
import { Bag } from "@tbagtapp/sdk";
const bag = new Bag({ apiKey: process.env.BAG_API_KEY! });
const link = await bag.paymentLinks.create({
name: "Pro Plan - Monthly",
amount: 29.99,
network: "base",
description: "Monthly Pro subscription with 14-day free trial",
});curl -X POST https://getbags.app/api/payment-links \
-H "Authorization: Bearer $BAG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Pro Plan - Monthly",
"amount": 29.99,
"network": "base",
"description": "Monthly Pro subscription with 14-day free trial"
}'Configure billing cycle, trial period, and dunning behavior in the dashboard under the payment link settings.
Step 2: Handle the initial payment
When a customer subscribes, Bag creates a checkout session and processes the first payment (or starts the trial). You receive a subscription.created webhook:
{
"event": "subscription.created",
"data": {
"subscriptionId": "sub_abc123",
"paymentLinkId": "a1b2c3d4-...",
"status": "active",
"currentPeriodStart": "2026-04-01T00:00:00Z",
"currentPeriodEnd": "2026-05-01T00:00:00Z",
"customer": {
"email": "jane@example.com",
"walletAddress": "0x..."
}
}
}Store the subscriptionId in your database and grant access to the customer.
Step 3: Handle renewals
Each billing cycle, Bag automatically charges the customer and sends a webhook:
app.post("/webhooks/bag", async (req, res) => {
const { event, data } = req.body;
switch (event) {
case "subscription.renewed":
await extendAccess(data.subscriptionId, data.currentPeriodEnd);
break;
case "subscription.past_due":
await notifyCustomer(data.subscriptionId, "Payment failed, retrying...");
break;
case "subscription.canceled":
await revokeAccess(data.subscriptionId);
break;
case "subscription.renewal_due":
// Stablecoin subscriptions: customer may need to re-approve
await notifyCustomer(data.subscriptionId, "Renewal coming up");
break;
}
res.status(200).send("ok");
});Step 4: Manage subscriptions
Pause a subscription
curl -X PATCH "https://getbags.app/api/subscriptions/$SUBSCRIPTION_ID" \
-H "Authorization: Bearer $BAG_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "paused"}'Resume a subscription
curl -X PATCH "https://getbags.app/api/subscriptions/$SUBSCRIPTION_ID" \
-H "Authorization: Bearer $BAG_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "active"}'Cancel a subscription
curl -X PATCH "https://getbags.app/api/subscriptions/$SUBSCRIPTION_ID" \
-H "Authorization: Bearer $BAG_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "canceled"}'Step 5: Handle stablecoin re-approval
For USDC subscriptions, the customer approves a spending limit (e.g., 6 months). When the approval is about to expire, Bag sends a subscription.renewal_due webhook. Direct the customer to the re-approval page:
case "subscription.renewal_due":
const reauthUrl = `https://getbags.app/reauth/${data.subscriptionId}`;
await sendEmail(data.customer.email, {
subject: "Action needed: renew your subscription approval",
body: `Please re-approve your subscription: ${reauthUrl}`,
});
break;Subscription webhook events
| Event | When | Your action |
|---|---|---|
subscription.created | New subscription starts | Store subscription, grant access |
subscription.updated | Plan or details changed | Update stored plan info |
subscription.renewed | Payment succeeded | Extend access period |
subscription.renewal_due | Re-approval needed (stablecoin) | Notify customer |
subscription.past_due | Payment failed, retrying | Warn customer |
subscription.canceled | Subscription ended | Revoke access |