Skip to content

How to build a One-Click Resubscribe experience

One-Click Resubscribe lets a signed-in reader start a new subscription with a single click, without re-entering payment details. It works when the reader has a saved payment method (a “card on file”) from a previous subscription they bought through checkout. When one is present, your front end can show a single confirmation button that starts the subscription with no checkout form, no card entry, and no address collection. The subscription does not have to be the product the reader held before, so you can also use this flow to start a subscription to a different product they are eligible to buy.

This guide shows how to build that experience with two Arc XP Subscriptions public APIs:

  • GET /sales/public/v2/subscriptions/cards-on-file — lists the authenticated user’s saved payment methods.
  • POST /sales/public/v2/subscriptions/resubscribe — restarts a subscription by charging one of those saved cards.

Before you begin

These are public REST APIs, so you have the flexibility to build the resubscribe experience directly into your reader-facing front end (browser or native app). Arc XP does not provide Sales SDK methods or Themes blocks for these endpoints, so you call the two directly, as shown below.

Gateway support

These APIs work with the Stripe Intents payment gateway. If your site uses a different payment provider, use the standard checkout flow. To request support for another gateway, submit it to the Arc XP idea board.

They also calculate tax as part of the transaction and support Arc XP’s integrated tax gateways, with the exception of Taxamo. If your site uses Taxamo for tax calculation, use the standard checkout flow.

What you need

These APIs require a Bearer access token issued to a logged-in user.

  • Hostname: https://{orgname}-{sitename}-{environment}.api.arc-cdn.net (where environment is sandbox or prod).
  • Authentication: a Bearer JWT in the Authorization header, obtained from the Identity login/token APIs after the reader signs in. See the Identity documentation for how to obtain and refresh access tokens.

You will also need the SKU and price code of the product you want the reader to resubscribe to. These come from your product catalog configuration in Arc XP.

How the flow works

  1. The reader signs in, and your front end obtains their access token.

  2. Call cards-on-file to check whether the reader has a saved payment method. If the array is empty, fall back to your normal checkout flow.

  3. Display the saved card (type and last four digits) and a single Resubscribe button next to the offer.

  4. When the reader clicks the button, call resubscribe with the cardId, sku, and priceCode.

  5. On success, show a confirmation using the returned order number, amount charged, and next renewal date.

Step 1 — Get the reader’s cards on file

Call the endpoint with the reader’s access token. It returns an array of saved cards, or an empty array ([]) if the reader has none.

Terminal window
curl -X GET \
"https://{orgname}-{sitename}-{environment}.api.arc-cdn.net/sales/public/v2/subscriptions/cards-on-file" \
-H "Authorization: Bearer <ACCESS_TOKEN>"

Example response (200 OK):

[
{
"id": 88213347,
"type": "Visa",
"lastFour": "4242",
"expiration": "12/27",
"subscriptions": [
{
"subscriptionId": 5527891,
"status": "Active",
"lastSuccessfulPaymentDate": "2026-06-23T00:00:00Z"
},
{
"subscriptionId": 5019442,
"status": "Cancelled",
"lastSuccessfulPaymentDate": "2025-11-23T00:00:00Z"
}
]
}
]
FieldTypeDescription
idintegerIdentifier of the saved card. Pass this as cardId when resubscribing.
typestringThe card type, e.g. Visa, Mastercard. Unknown is returned when undetermined.
lastFourstringThe last four digits of the card number.
expirationstringThe card expiration date.
subscriptionsarrayThe subscriptions currently billed to this card. An empty array ([]) is returned when no subscriptions are associated with the card. See the fields below.

Each entry in subscriptions describes one subscription billed to the card:

FieldTypeDescription
subscriptionIdintegerThe public identifier of the subscription.
statusstringThe human-readable subscription status, e.g. Active, Cancelled.
lastSuccessfulPaymentDatestringThe date of the subscription’s most recent successful (non-refund) payment, as an ISO-8601 timestamp. null when the subscription has never had a successful payment.

Use the response to decide what to render. If exactly one card is returned, you can present a single-click button. If more than one is returned, let the reader choose which card to charge, then use that card’s id.

Understanding cards on file

The cards returned by this API were saved to Arc’s database when the reader subscribed in the past, and they reflect the details that were current at that time. Keep the following in mind when you display and act on them:

  • A past expiration date does not mean the card is invalid. Arc does not synchronize the stored expiration with the payment gateway after the card was tokenized and saved by the gateway. The card may well have been renewed at the same number, in which case the gateway holds current details even though Arc’s stored expiration looks expired. Do not filter out or block a card solely because its expiration is in the past — the resubscribe transaction can still succeed.

  • The gateway is the source of truth at charge time. Whether the charge succeeds is determined by the payment gateway when you call resubscribe, not by the expiration value returned here. Treat expiration as informational — useful for helping the reader recognize which card they’re using, not as a validity check.

  • Handle failures at the transaction, not the display. If a card truly is no longer chargeable, the resubscribe call surfaces that as an error. Let the reader attempt the resubscribe and fall back to your standard checkout flow if it fails, rather than pre-emptively hiding cards.

  • The response has one entry per saved payment method, not one per subscription. Each payment method appears only once. Because a reader can have more than one saved payment method, the same physical card can appear more than once when it was tokenized separately — and Arc XP cannot tell those entries apart. Because you cannot reliably identify duplicates, you may want to show the card type and last four digits and let the reader choose which one to use.

  • Use the per-card subscriptions to pick the best card for the restart. Each card lists the subscriptions currently billed to it, so you don’t have to treat every saved card as equivalent. When a reader has more than one card on file, prefer the card that most recently billed a real subscription — the one carrying an Active subscription, or the highest lastSuccessfulPaymentDate — as the default for the restart transaction, since it is the reader’s most current working payment method. This also helps disambiguate the duplicate entries described above: two entries for the same physical card can be told apart by the subscriptions attached to each. A card whose subscriptions array is empty ([]) has no subscription history behind it, so it is a weaker default even though it may still be chargeable.

Step 2 — Resubscribe with a saved card

When the reader confirms, POST the cardId along with the sku and priceCode of the product they are resubscribing to.

Terminal window
curl -X POST \
"https://{orgname}-{sitename}-{environment}.api.arc-cdn.net/sales/public/v2/subscriptions/resubscribe" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"cardId": 88213347,
"sku": "digital-all-access",
"priceCode": "ABC123"
}'

Request body:

FieldTypeRequiredDescription
cardIdintegerYesThe id of the card on file to charge (from Step 1).
skustringYesThe SKU of the product to resubscribe to. Must match the product resolved from priceCode. Pattern: 1–30 chars of a-z A-Z 0-9 _ -.
priceCodestringYesThe price code for the selected product. Pattern: exactly 6 chars of A-Z 0-9.

Example response (200 OK):

{
"orderNumber": "7F3K9Q2M4XB1TZ8P",
"subscriptionId": 5540123,
"currency": "USD",
"total": 14.99,
"tax": 1.02,
"nextRenewalDate": "2026-08-23T00:00:00Z"
}
FieldTypeDescription
orderNumberstringThe order number issued for the transaction. A 16-character string of 0-9 and A-Z.
subscriptionIdintegerThe identifier of the newly created subscription.
currencystringThe ISO currency code of the charge.
totalnumberThe total amount charged.
taxnumberThe tax portion of the total.
nextRenewalDatestringThe date of the next renewal (ISO 8601).

Use these values to render a confirmation screen — for example, “You’re all set. We charged your Visa ending in 4242 $14.99. Your next renewal is August 23, 2026.”

How the charge is processed

The reader does not enter an address in this flow, so Arc XP uses the newest billing address it has on file for the customer as the billing address for the order. That address is what tax calculation and any later refunds are based on.

The charge runs like a renewal. It is an off-session transaction that does not trigger a Strong Customer Authentication (SCA) step-up such as 3D Secure, so there is no redirect or challenge for the reader, and the response is either a success or a failure. In markets where SCA is enforced, such as the European Economic Area, some off-session charges will be declined when the issuer requires authentication. Handle a failure by falling back to your standard checkout flow, where the reader can complete any required authentication.

After a successful resubscribe

Once the resubscribe returns 200 OK, there are two things to be aware of.

Flush the reader’s cached entitlements

The paywall script caches the reader’s entitlements in the browser for up to 24 hours (see the paywall setup guide). Immediately after a successful resubscribe, that cache still reflects the reader’s old access, so the reader may keep hitting the paywall even though they now have an active subscription.

To pick up the new entitlements right away, clear the cache using the Sales SDK by calling ArcP.reset() after the transaction succeeds:

// After resubscribe returns 200 OK:
ArcP.reset(); // flush cached entitlements so the new subscription is recognized

This forces the paywall script to re-fetch /sales/public/v1/entitlements on the next evaluation, so the reader’s newly purchased access is recognized without waiting for the 24-hour cache to expire.

The transaction appears in CSR Admin

A successful resubscribe is a real order. It shows up in the CSR Admin UI like any other transaction, where support and customer-service staff can look it up, view the order details, and manage the subscription.

Putting it together

The snippet below shows the full front-end flow: detect a saved card, then resubscribe on confirmation.

const BASE = 'https://{orgname}-{sitename}-{environment}.api.arc-cdn.net';
async function offerOneClickResubscribe(accessToken, product) {
// Step 1: Does the reader have a saved card?
const cardsRes = await fetch(`${BASE}/sales/public/v2/subscriptions/cards-on-file`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
// fetch() does not reject on HTTP errors (e.g. 401 for an expired token, or 500),
// so guard on cardsRes.ok before treating the body as the cards array.
if (!cardsRes.ok) {
// Could not read saved cards — send the reader to the standard checkout.
return { eligible: false };
}
const cards = await cardsRes.json();
if (!cards.length) {
// No saved card — send the reader to the standard checkout.
return { eligible: false };
}
const card = cards[0];
return {
eligible: true,
label: `Resubscribe with ${card.type} ending in ${card.lastFour}`,
// Step 2: called when the reader clicks the button.
confirm: async () => {
const res = await fetch(`${BASE}/sales/public/v2/subscriptions/resubscribe`, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
cardId: card.id,
sku: product.sku,
priceCode: product.priceCode,
}),
});
if (!res.ok) {
const [error] = await res.json();
throw new Error(`Resubscribe failed (${error.code}): ${error.message}`);
}
return res.json(); // { orderNumber, subscriptionId, total, nextRenewalDate, ... }
},
};
}

Handling errors

Both endpoints return errors as an array of objects with code, httpStatus, and message. Handle at least the following:

StatusWhen it happensWhat to do
400Missing or invalid cardId, sku, or priceCode, or a SKU/price-code mismatch.Fix the request. Confirm the SKU and price code come from the same catalog entry.
401The request is not authenticated as an external (logged-in) user.Refresh the reader’s access token or prompt them to sign in again.
409A resubscribe for this card was submitted within the last 5 minutes and is still locked.Do not retry automatically. This card-level guard blocks duplicate submissions for 5 minutes. Poll the reader’s subscription status, and if no charge went through the reader can try again after the lock clears.
500An unexpected error occurred.Do not retry. Tell the reader something went wrong, that they should check their card account for a charge, and to contact support for help.