If your code calls Stripe today, this page maps each object, call and webhook event a typical integration uses to its Anytime Capital equivalent, with the code side by side, and says plainly what has no equivalent yet. To move your customers, catalog and subscribers across, use the importer in Switch from Stripe.
At a glance
| Stripe | Anytime Capital | |
|---|---|---|
| Base URL | https://api.stripe.com/v1 | https://anytime-capital.com/api/v1 |
| Keys | sk_live_ secret, rk_live_ restricted, pk_live_ publishable | ak_live_ secret keys, each restrictable per resource. No publishable key: card details are only ever typed into our hosted or embedded checkout. See Authentication. |
| Test mode | sk_test_ keys | ak_test_ keys from test mode in the dashboard; objects and events carry livemode: false. See Test mode. |
| Request bodies | Form-encoded | JSON |
| Amounts | amount in the smallest unit | Integer cents, named for what they are: amountCents, priceCents, unitCents |
| Currency | Many | US dollars (currency: "usd"). See International customers. |
| Pagination | limit with starting_after; has_more | limit (up to 100) with cursor set to the last page's nextCursor; hasMore. Newest first. See Pagination. |
| Idempotency | Idempotency-Key header | The same header on every write, remembered 24 hours; a replay answers with Idempotent-Replayed: true. See Idempotency. |
| Errors | error.type, error.code, error.message, error.param | error.code and error.message (it names the field). See Error codes. |
| Request ids | Request-Id header | Request-Id header (req_…), also in the request log in Settings → Developers |
| Metadata | On most objects | On payment links, copied onto each payment made on them. Customers have notes. |
| Libraries | stripe for Node and others | fetch (every reference page has the Node.js version), a Node SDK preview, or a client generated from the OpenAPI document |
Objects and calls
| Stripe | Anytime Capital | Notes |
|---|---|---|
checkout.sessions.create (payment) | POST /payment-links with singleUse: true | Redirect to the link's url. success_url → successUrl, metadata → metadata, allow_promotion_codes → allowDiscountCodes, shipping_address_collection → collectShipping, customer_email → ?prefilled_email= on the url, client_reference_id → ?ref= on the url (stored as the payment's clientReference). |
checkout.sessions.create (subscription) | The subscription plan's url | Send customers to url from POST /subscription-plans or GET /subscription-plans/{id}. |
paymentLinks.create / update | POST /payment-links / POST /payment-links/{id} | A price in amountCents (no Price object). The price can't change after creation: create a new link. |
paymentLinks.update({ active: false }) | POST /payment-links/{id}/deactivate | There's no API to turn a link back on. |
paymentIntents.retrieve / list | GET /payments/{id} / GET /payments | status: "paid" for succeeded. amountCents, amountRefundedCents, netCents, metadata, clientReference, customer. |
customers.create / update / retrieve | POST /customers / PATCH /customers/{id} / GET /customers/{id} | One customer per email address; card buyers are added on their own when they first pay. |
customers.list({ email }) | GET /customers?email= | Filter by email only; there's no search query language. |
products.create + prices.create (one-time) | POST /products with priceCents | A product has one price. products.update → PATCH /products/{id}; active: false → POST /products/{id}/archive. |
prices.create (recurring) | POST /subscription-plans | interval: week, month, quarter, year; trialDays up to 90. |
subscriptions.list / retrieve | GET /subscriptions / GET /subscriptions/{id} | Filter by status, planId or customerEmail. |
subscriptions.update({ cancel_at_period_end: true }) / subscriptions.cancel | POST /subscriptions/{id}/cancel with atPeriodEnd true / false | The subscriber gets the same email as when you cancel in the dashboard. |
subscriptions.update({ pause_collection }) / resume | POST /subscriptions/{id}/pause / POST /subscriptions/{id}/resume | |
invoices.create + invoiceItems.create + finalizeInvoice + sendInvoice | POST /invoices | One call with customerName, customerEmail, lineItems, dueDate and optional taxBps, discountCents, deposit. Emailed at once unless send: false; POST /invoices/{id}/send sends it, or a reminder after that. |
refunds.create | POST /refunds | payment_intent → paymentId, amount → amountCents. Over your refund approval limit it answers 202 with status: "pending_approval" until an owner approves. |
coupons.create + promotionCodes.create | POST /discount-codes | percentOffBps (1500 is 15%) or amountOffCents, maxRedemptions, expiresAt. Deactivate with POST /discount-codes/{id}/deactivate. |
disputes.list / retrieve | GET /disputes / GET /disputes/{id} | Read only, with the automatic evidence pack. Respond in the dashboard. |
payouts.list / balance.retrieve | GET /payouts / GET /balance | Read only. Payouts go to your bank account on your schedule. |
webhookEndpoints.create | POST /webhook-endpoints | enabled_events → events (or ["*"]). The response carries the signing secret once. |
events.list / retrieve | GET /events / GET /events/{id} | The last 30 days. type takes an event type, a family such as payment.*, or up to 10 of them; createdFrom and createdTo take ISO 8601 dates. |
billing.meterEvents.create | POST /meter-events | event_name → eventName, payload.value → value, with a required idempotencyKey per event. See Meters. |
Accept a payment
A Checkout Session becomes a single-use payment link: create it from your server with the order in metadata, redirect the customer to its url, and fulfil the order on the payment.succeeded webhook, where data.metadata.orderId comes back. There is no PaymentIntent to create or confirm yourself.
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const session = await stripe.checkout.sessions.create(
{
mode: "payment",
line_items: [
{ price_data: { currency: "usd", unit_amount: 12500, product_data: { name: "Order #1042" } }, quantity: 1 },
],
success_url: "https://example.com/thanks",
metadata: { orderId: "1042" },
},
{ idempotencyKey: "order-1042-checkout" },
);
res.redirect(303, session.url);// One single-use payment link per order: its url is the hosted checkout.
const response = await fetch("https://anytime-capital.com/api/v1/payment-links", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "order-1042-link",
},
body: JSON.stringify({
title: "Order #1042",
amountCents: 12500,
singleUse: true,
successUrl: "https://example.com/thanks",
metadata: { orderId: "1042" },
}),
});
const link = await response.json();
if (!response.ok) throw new Error(`${link.error.code}: ${link.error.message}`);
res.redirect(303, link.url);curl https://api.stripe.com/v1/checkout/sessions \
-u "$STRIPE_SECRET_KEY:" \
-H "Idempotency-Key: order-1042-checkout" \
-d mode=payment \
-d "line_items[0][price_data][currency]=usd" \
-d "line_items[0][price_data][unit_amount]=12500" \
-d "line_items[0][price_data][product_data][name]=Order #1042" \
-d "line_items[0][quantity]=1" \
-d success_url="https://example.com/thanks" \
-d "metadata[orderId]=1042"curl https://anytime-capital.com/api/v1/payment-links \
-H "Authorization: Bearer $ANYTIME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042-link" \
-d '{"title":"Order #1042","amountCents":12500,"singleUse":true,"successUrl":"https://example.com/thanks","metadata":{"orderId":"1042"}}'Line items with catalog products can't be sent through the API yet: send the order's total as amountCents and describe it in title and description. Links you build from products in the dashboard list their items. For a checkout on your own page rather than a redirect, put the same link in the inline checkout.
Read a payment
const intent = await stripe.paymentIntents.retrieve("pi_3Q…");
if (intent.status === "succeeded") {
console.log(intent.amount_received, intent.metadata.orderId);
}const response = await fetch("https://anytime-capital.com/api/v1/payments/pay_7Hq2LmX9cR4tVb", {
headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` },
});
const payment = await response.json();
if (payment.status === "paid") {
// netCents is what you keep after fees; metadata came from the payment link.
console.log(payment.amountCents, payment.netCents, payment.metadata.orderId);
}curl https://api.stripe.com/v1/payment_intents/pi_3Q… -u "$STRIPE_SECRET_KEY:"curl https://anytime-capital.com/api/v1/payments/pay_7Hq2LmX9cR4tVb \
-H "Authorization: Bearer $ANYTIME_API_KEY"Customers
const [existing] = (await stripe.customers.list({ email: "jordan@example.com", limit: 1 })).data;
const customer =
existing ?? (await stripe.customers.create({ name: "Jordan Lee", email: "jordan@example.com", phone: "+1 555 0100" }));const headers = {
Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`,
"Content-Type": "application/json",
};
// One customer per email address: look before you create.
const found = await fetch("https://anytime-capital.com/api/v1/customers?email=jordan%40example.com", { headers }).then((r) => r.json());
const customer =
found.data[0] ??
(await fetch("https://anytime-capital.com/api/v1/customers", {
method: "POST",
headers: { ...headers, "Idempotency-Key": "customer-jordan" },
body: JSON.stringify({ name: "Jordan Lee", email: "jordan@example.com", phone: "+1 555 0100" }),
}).then((r) => r.json()));curl https://api.stripe.com/v1/customers \
-u "$STRIPE_SECRET_KEY:" \
-d name="Jordan Lee" -d email=jordan@example.comcurl https://anytime-capital.com/api/v1/customers \
-H "Authorization: Bearer $ANYTIME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: customer-jordan" \
-d '{"name":"Jordan Lee","email":"jordan@example.com"}'Products, prices and plans
There's no separate Price object. A product carries its one price in priceCents; a recurring price is a subscription plan, which has its own checkout url.
const mug = await stripe.products.create({ name: "Ceramic mug" });
await stripe.prices.create({ product: mug.id, unit_amount: 1800, currency: "usd" });
const membership = await stripe.products.create({ name: "Studio membership" });
await stripe.prices.create({
product: membership.id,
unit_amount: 4900,
currency: "usd",
recurring: { interval: "month", trial_period_days: 14 },
});const post = (path, body, key) =>
fetch(`https://anytime-capital.com/api/v1${path}`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify(body),
}).then((r) => r.json());
// A product carries its one price.
const mug = await post("/products", { name: "Ceramic mug", priceCents: 1800 }, "product-mug");
// A recurring price is a subscription plan; customers subscribe on its url.
const plan = await post(
"/subscription-plans",
{ title: "Studio membership", amountCents: 4900, interval: "month", trialDays: 14 },
"plan-studio-monthly",
);
console.log(plan.url);curl https://anytime-capital.com/api/v1/products \
-H "Authorization: Bearer $ANYTIME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: product-mug" \
-d '{"name":"Ceramic mug","priceCents":1800}'
curl https://anytime-capital.com/api/v1/subscription-plans \
-H "Authorization: Bearer $ANYTIME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: plan-studio-monthly" \
-d '{"title":"Studio membership","amountCents":4900,"interval":"month","trialDays":14}'Cancel a subscription
// At the end of the period…
await stripe.subscriptions.update("sub_1Q…", { cancel_at_period_end: true });
// …or now.
await stripe.subscriptions.cancel("sub_1Q…");const cancel = (atPeriodEnd) =>
fetch("https://anytime-capital.com/api/v1/subscriptions/sub_2208/cancel", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `cancel-sub_2208-${atPeriodEnd}`,
},
body: JSON.stringify({ atPeriodEnd }),
}).then((r) => r.json());
await cancel(true); // cancelAtPeriodEnd is true until the period ends
// await cancel(false); // ends it nowcurl https://anytime-capital.com/api/v1/subscriptions/sub_2208/cancel \
-H "Authorization: Bearer $ANYTIME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cancel-sub_2208" \
-d '{"atPeriodEnd":true}'Invoices
const invoice = await stripe.invoices.create({ customer: "cus_Q…", collection_method: "send_invoice", days_until_due: 30 });
await stripe.invoiceItems.create({ customer: "cus_Q…", invoice: invoice.id, amount: 450000, currency: "usd", description: "Website redesign" });
await stripe.invoices.finalizeInvoice(invoice.id);
await stripe.invoices.sendInvoice(invoice.id);// One call: created, totalled and emailed (send: false to send it later).
const response = await fetch("https://anytime-capital.com/api/v1/invoices", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "invoice-northwind-oct",
},
body: JSON.stringify({
customerName: "Northwind LLC",
customerEmail: "ap@northwind.example",
lineItems: [{ description: "Website redesign", quantity: 1, unitCents: 450000 }],
taxBps: 825,
dueDate: "2026-11-15",
}),
});
const invoice = await response.json(); // invoice.url is its pay pagecurl https://anytime-capital.com/api/v1/invoices \
-H "Authorization: Bearer $ANYTIME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: invoice-northwind-oct" \
-d '{"customerName":"Northwind LLC","customerEmail":"ap@northwind.example","lineItems":[{"description":"Website redesign","quantity":1,"unitCents":450000}],"taxBps":825,"dueDate":"2026-11-15"}'Refunds
await stripe.refunds.create(
{ payment_intent: "pi_3Q…", amount: 2500, reason: "requested_by_customer" },
{ idempotencyKey: "refund-order-1042" },
);const response = await fetch("https://anytime-capital.com/api/v1/refunds", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "refund-order-1042",
},
body: JSON.stringify({ paymentId: "pay_7Hq2LmX9cR4tVb", amountCents: 2500, reason: "requested_by_customer" }),
});
const refund = await response.json();
// 201: refunded. 202: over your approval limit, waiting for an owner (status "pending_approval").curl https://api.stripe.com/v1/refunds \
-u "$STRIPE_SECRET_KEY:" \
-H "Idempotency-Key: refund-order-1042" \
-d payment_intent=pi_3Q… -d amount=2500 -d reason=requested_by_customercurl https://anytime-capital.com/api/v1/refunds \
-H "Authorization: Bearer $ANYTIME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: refund-order-1042" \
-d '{"paymentId":"pay_7Hq2LmX9cR4tVb","amountCents":2500,"reason":"requested_by_customer"}'Webhooks
Signatures work the same way: Anytime-Signature is t=<unix seconds>,v1=<hex>, an HMAC-SHA256 of <t>.<raw body> keyed with your endpoint's whsec_… secret. Three things differ in the event itself: data is the object (not data.object), created is an ISO 8601 string (not Unix seconds), and there's no previous_attributes, so re-read the object from the API when you need what changed.
app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => {
const event = stripe.webhooks.constructEvent(req.body, req.get("Stripe-Signature"), process.env.STRIPE_WEBHOOK_SECRET);
if (event.type === "checkout.session.completed") {
fulfil(event.data.object.metadata.orderId);
}
res.sendStatus(200);
});import crypto from "node:crypto";
// Anytime-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
function verifyEvent(rawBody, header, secret) {
const pairs = header.split(",").map((part) => part.trim().split("="));
const t = pairs.find(([key]) => key === "t")?.[1] ?? "";
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const signed = pairs.some(
([key, value = ""]) =>
key === "v1" && value.length === expected.length && crypto.timingSafeEqual(Buffer.from(value), Buffer.from(expected)),
);
if (!signed || Math.abs(Date.now() / 1000 - Number(t)) > 300) throw new Error("Invalid signature");
return JSON.parse(rawBody);
}
app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => {
let event;
try {
event = verifyEvent(req.body.toString("utf8"), req.get("Anytime-Signature") ?? "", process.env.ANYTIME_WEBHOOK_SECRET);
} catch {
return res.sendStatus(400);
}
if (event.type === "payment.succeeded") {
fulfil(event.data.metadata.orderId); // event.data is the payment itself
}
res.sendStatus(200);
});The Node SDK preview has the same constructEvent(rawBody, header, secret). Endpoints must be public https:// URLs: add them in Settings → Developers or with POST /webhook-endpoints. See Verify webhook signatures for Next.js, Python and PHP.
Webhook events
| Stripe event | Anytime Capital event | data is |
|---|---|---|
checkout.session.completed, payment_intent.succeeded, charge.succeeded | payment.succeeded | the payment |
payment_intent.payment_failed, charge.failed | payment.failed | the payment |
charge.refunded | payment.refunded | the payment |
refund.created | refund.created | the refund |
refund.updated (succeeded / failed) | refund.succeeded / refund.failed | the refund |
charge.dispute.created | dispute.opened | the payment |
charge.dispute.updated | dispute.updated | the dispute |
charge.dispute.closed | dispute.closed | the payment |
invoice.created, invoice.finalized | invoice.created | the invoice |
invoice.sent | invoice.sent | the invoice |
invoice.paid | invoice.paid | the invoice |
invoice.overdue | invoice.overdue | the invoice |
invoice.voided | invoice.voided | the invoice |
customer.subscription.created | subscription.created | the subscription |
customer.subscription.updated | subscription.updated | the subscription |
customer.subscription.paused / resumed | subscription.paused / subscription.resumed | the subscription |
invoice.payment_failed on a renewal | subscription.past_due | the subscription |
customer.subscription.deleted | subscription.canceled | the subscription |
customer.created / customer.updated | customer.created / customer.updated | the customer |
payout.paid / payout.failed | payout.paid / payout.failed | the payout |
review.opened | payment.review_required | the payment |
Events with no counterpart here include checkout.session.expired, payment_intent.created, payment_intent.processing, payment_intent.requires_action, setup_intent.*, payment_method.*, customer.deleted, product.*, price.*, invoice.upcoming and customer.subscription.trial_will_end. See Webhooks for every event type.
Catch up on events
for await (const event of stripe.events.list({ type: "payment_intent.succeeded", created: { gte: 1760400000 } })) {
handle(event);
}// Newest first; follow nextCursor until hasMore is false.
let cursor;
do {
const url = new URL("https://anytime-capital.com/api/v1/events");
url.searchParams.set("type", "payment.*");
url.searchParams.set("createdFrom", "2026-10-14T00:00:00Z");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const page = await fetch(url, { headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` } }).then((r) => r.json());
for (const event of page.data) handle(event);
cursor = page.hasMore ? page.nextCursor : undefined;
} while (cursor);curl "https://anytime-capital.com/api/v1/events?type=payment.*&createdFrom=2026-10-14&limit=100" \
-H "Authorization: Bearer $ANYTIME_API_KEY"Test locally
The Anytime CLI (in preview, not on npm yet) does what the Stripe CLI's listen and trigger do: it forwards your test events to localhost, signed with a local secret in the same header format, and sends test events through the test helpers.
# Before
stripe listen --forward-to localhost:3000/webhooks
stripe trigger payment_intent.succeeded
# After (the Anytime CLI, in preview)
anytime listen --forward-to localhost:3000/webhooks
anytime trigger payment.succeededNo equivalent yet
- Collecting or charging a card through the API. There are no PaymentIntents, SetupIntents, PaymentMethods or Elements: cards are entered only in our hosted or embedded checkout, and there's no call to charge a saved card off session.
- Separate authorization and capture. There's no
capture_method: manual; payments our risk rules hold are approved in the dashboard (payment.review_required). - Creating or changing subscriptions from your server. Customers subscribe on a plan's
url. Plan changes with proration, coupons on subscriptions and several items per subscription are dashboard features; there are no subscription schedules or test clocks. - Currencies other than US dollars, and automatic tax calculation: set
taxBpson invoices yourself. - Metadata beyond payment links and payments,
expand[], search queries, and API version headers: objects carry what they need, and the Changelog and the OpenAPI document's version track changes. - Deleting customers or products, voiding invoices and answering disputes through the API. Do these in the dashboard; products are archived instead of deleted.
- Creating payouts through the API, connected accounts and card issuing. Payouts and the balance can be read:
GET /payouts,GET /balance. - Expiring a checkout. Deactivate the link instead:
POST /payment-links/{id}/deactivate. - Fraud rules through the API. They're set in the dashboard: see Fraud rules & 3D Secure.