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(whereenvironmentissandboxorprod). - Authentication: a Bearer JWT in the
Authorizationheader, 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
-
The reader signs in, and your front end obtains their access token.
-
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.
-
Display the saved card (type and last four digits) and a single Resubscribe button next to the offer.
-
When the reader clicks the button, call resubscribe with the
cardId,sku, andpriceCode. -
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.
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" } ] }]| Field | Type | Description |
|---|---|---|
id | integer | Identifier of the saved card. Pass this as cardId when resubscribing. |
type | string | The card type, e.g. Visa, Mastercard. Unknown is returned when undetermined. |
lastFour | string | The last four digits of the card number. |
expiration | string | The card expiration date. |
subscriptions | array | The 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:
| Field | Type | Description |
|---|---|---|
subscriptionId | integer | The public identifier of the subscription. |
status | string | The human-readable subscription status, e.g. Active, Cancelled. |
lastSuccessfulPaymentDate | string | The 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
expirationwith 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 storedexpirationlooks expired. Do not filter out or block a card solely because itsexpirationis 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
expirationvalue returned here. Treatexpirationas 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
subscriptionsto 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 anActivesubscription, or the highestlastSuccessfulPaymentDate— 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 whosesubscriptionsarray 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.
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:
| Field | Type | Required | Description |
|---|---|---|---|
cardId | integer | Yes | The id of the card on file to charge (from Step 1). |
sku | string | Yes | The 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 _ -. |
priceCode | string | Yes | The 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"}| Field | Type | Description |
|---|---|---|
orderNumber | string | The order number issued for the transaction. A 16-character string of 0-9 and A-Z. |
subscriptionId | integer | The identifier of the newly created subscription. |
currency | string | The ISO currency code of the charge. |
total | number | The total amount charged. |
tax | number | The tax portion of the total. |
nextRenewalDate | string | The 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 recognizedThis 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:
| Status | When it happens | What to do |
|---|---|---|
400 | Missing 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. |
401 | The request is not authenticated as an external (logged-in) user. | Refresh the reader’s access token or prompt them to sign in again. |
409 | A 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. |
500 | An 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. |