Customers subscribe on Anytime Capital's hosted page for each plan. Your app creates the plans, sends customers to them, and keeps access in step with webhooks. Coupons and plan changes are switched on in the dashboard; no code needed for those. Endpoint details: Subscriptions & plans.
1. Create your plans
Make them in Subscriptions → Plans in the dashboard, or from code with a key that has subscriptions write. A plan's price and interval are fixed once it exists; for a new price, make a new plan.
// create-plans.mjs — run once: node create-plans.mjs
const API = "https://anytime-capital.com/api/v1";
const PLANS = [
{ key: "plan-basic-monthly", title: "Basic", amountCents: 1900, interval: "month", trialDays: 14 },
{ key: "plan-pro-monthly", title: "Pro", amountCents: 4900, interval: "month", trialDays: 14 },
{ key: "plan-pro-yearly", title: "Pro (yearly)", amountCents: 49000, interval: "year" },
];
for (const { key, ...plan } of PLANS) {
const res = await fetch(`${API}/subscription-plans`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": key, // run it twice within 24 hours and you still get one plan each
},
body: JSON.stringify(plan),
});
const created = await res.json();
if (!res.ok) throw new Error(`${created.error.code}: ${created.error.message}`);
console.log(created.id, created.title, created.url); // put each url behind its Subscribe button
}2. Send customers to subscribe
Each plan's url is its subscribe page: link your Subscribe buttons to it. Free trials start there, and the subscription appears on your Subscriptions page and in subscription.created.
3. Offer coupons
- Create a code on the Discounts page, or with
POST /api/v1/discount-codes(see Discount codes). - In the dashboard, turn on Use on subscriptions for it and choose how long it lasts: the first payment, several months, or every payment, and which plans it covers.
- The plan's subscribe page then shows Have a discount code?. You can also add a code to an existing subscriber from their details.
How long a discount lasts and new-customers-only rules are on Subscription discounts; subscription settings for a code can't be set through the API.
4. Keep access in step
Subscribe your endpoint to subscription.created, subscription.updated, subscription.paused, subscription.resumed, subscription.past_due and subscription.canceled. Events can arrive more than once and out of order, so on each one, read the subscription as it is now and store that.
// subscriptions-webhook.mjs — Node.js 18+ with Express: npm install express
import crypto from "node:crypto";
import express from "express";
const API = "https://anytime-capital.com/api/v1";
const secret = process.env.ANYTIME_WEBHOOK_SECRET; // whsec_…
const access = new Map(); // email → { plan, status, until }: use your database
/** Which statuses keep the product open. */
function hasAccess(status) {
return ["trialing", "active", "canceling", "past_due"].includes(status); // past_due: renewal being retried
}
function verify(rawBody, header) {
const parts = Object.fromEntries(String(header ?? "").split(",").map((part) => part.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
return (
typeof parts.v1 === "string" &&
parts.v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected)) &&
Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300
);
}
/** Events can arrive out of order: always store the subscription as it is now. */
async function syncSubscription(id) {
const res = await fetch(`${API}/subscriptions/${id}`, {
headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` },
});
const subscription = await res.json();
if (!res.ok) throw new Error(`${subscription.error.code}: ${subscription.error.message}`);
access.set(subscription.customer.email, {
plan: subscription.plan.id,
status: subscription.status,
allowed: hasAccess(subscription.status),
until: subscription.currentPeriodEnd,
});
}
const app = express();
app.post("/webhooks/anytime", express.raw({ type: "application/json" }), async (req, res) => {
const rawBody = req.body.toString("utf8");
if (!verify(rawBody, req.get("Anytime-Signature"))) return res.status(400).send("Invalid signature");
const event = JSON.parse(rawBody);
if (event.type.startsWith("subscription.")) {
try {
await syncSubscription(event.data.id);
} catch (error) {
console.error(error);
return res.sendStatus(500); // not 2xx, so the event is sent again later
}
}
res.sendStatus(200);
});
app.listen(3000);5. Plan changes
Move a subscriber with Change plan from their details on the Subscriptions page, now (with the unused part of the period refunded) or at the next renewal. To let subscribers switch themselves, mark the plans they may move to and turn on Subscribers can switch plans. Plan changes aren't in the API: read plan.id from the subscription, never from what you remember. See plan changes.
6. Cancel, pause and resume from your app
// From your admin screen: end, pause or resume a subscriber (subscriptions:write).
async function subscriptionAction(id, action, body) {
const res = await fetch(`https://anytime-capital.com/api/v1/subscriptions/${id}/${action}`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`,
"Idempotency-Key": `${action}-${id}-${new Date().toISOString().slice(0, 10)}`,
...(body ? { "Content-Type": "application/json" } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const subscription = await res.json();
if (!res.ok) throw new Error(`${subscription.error.code}: ${subscription.error.message}`); // e.g. CANNOT_PAUSE
return subscription;
}
await subscriptionAction("sub_2208", "cancel", { atPeriodEnd: true }); // or { atPeriodEnd: false } to end now
await subscriptionAction("sub_2208", "resume"); // undoes the cancellation set for the period's endThe subscriber gets the same emails as when you act from the dashboard. Errors such as 409 CANNOT_PAUSE or 409 SUBSCRIPTION_ENDED explain themselves: see the error code catalogue.