# Card-on-file console for subscription billing teams

> A worklist of customers whose saved cards are expiring, duplicated, or missing, so your team can fix them before the next rebill fails.

- Workflow type: app
- Services: Adyen, HubSpot, Slack Bot
- Categories: Operations, Finance
- Published: 2026-08-14

## What it does

- Opens on a worklist of customers whose saved card is about to expire, is duplicated, or is missing entirely, so your team has a queue to work through instead of a blank search box
- Shows every card a customer has on file with the brand, last four digits and expiry date, and flags the ones that need attention
- Lets the team remove a dead or duplicate card behind a confirmation, retry an outstanding amount against a card that should work, or send the customer a payment link when nothing works
- Writes every action back to the customer record in your CRM and posts a recap of the day's recoveries and clean ups to your retention channel

## What you'll need

- An Adyen account that stores cards on file for your customers
- A HubSpot account where your customers live, plus a field on the contact or company that holds each customer's Adyen shopper id
- A Slack workspace and a channel for your retention or billing team
- Confirmation of which Adyen merchant account and currencies you bill in

## Prompt

I want an internal app for our subscription operations team to keep customer cards on file healthy and to rescue rebills that are about to fail. The people using it are billing and retention ops, and the point is to work a queue of at-risk customers before the next rebill breaks, not to react after a decline.

Design around this constraint. Adyen can only list stored payment methods for one shopper at a time, keyed by shopperReference, and there is no operation that enumerates every shopper. So HubSpot is the customer index, not Adyen. During setup the app should ask which HubSpot property holds the Adyen shopper reference on a contact or a company (for example a custom property called adyen_shopper_reference), and which Adyen merchant account and default currency to bill in, and save that configuration. The app always reads the shopper reference off the HubSpot record and never tries to list customers from Adyen.

The landing page is a worklist, not a search box. It lists customers who need attention with a reason on each row: card expiring in the next sixty days, duplicate tokens on the same shopper (same brand and same last four), or no usable card on file at all. Build the worklist by iterating the HubSpot records that carry a shopper reference using Search Contacts and Search Companies, then fetching tokens per shopper with Adyen List Stored Payment Methods. Persist the last seen token state per shopper (token id, brand, last four, expiry month and year, and when it was fetched) so the worklist renders from stored state instead of calling Adyen for every customer on every page load. Give the team a Refresh action for a single customer and for the whole queue, and show the last refreshed time on each row. Let them filter by reason and sort so the queue can actually be worked down.

There is also a search view where the team can find any customer by name, email or company domain using HubSpot Search Contacts and Search Companies. Opening a customer from either the worklist or search shows the detail view: the HubSpot record summary plus every card Adyen holds for that shopper from List Stored Payment Methods, each showing brand, last four digits and expiry, with badges for expiring soon, duplicate, and no usable card.

Per card the team can do three things. First, delete a dead or duplicated token with Adyen Delete Stored Payment Method, always behind a confirmation dialog that names the exact card being removed, because deletion is irreversible. Second, retry an outstanding amount against a chosen card with Adyen Make a Payment: the team enters an amount and currency, and the app sends the amount as an object in minor units (value 1000 with currency EUR means 10.00), passes the shopper reference together with the stored payment method id because a retry against a stored token is a merchant initiated transaction, generates a fresh reference for every attempt, and sets an Idempotency-Key header so a double click cannot charge the customer twice. Disable the retry button while a request is in flight and surface the result plainly, including the refusal reason when the payment is refused. Third, when no card works, fall back to Adyen Create Payment Link for the outstanding amount so the customer can pay themselves, and display the link so the team can copy and send it. Use Adyen List Payment Methods to show which methods are actually available for the customer's country and currency when building a link or picking how to retry.

Every action writes a HubSpot note with Create Note, associated to that contact or company, so the account owner sees the history in the CRM. The note should say which token was deleted, which retry was attempted against which card and what the result was, or which payment link was generated with its amount and URL, plus who on the team did it and when. Keep an in-app log of the same actions.

At the end of a working session the team can send a recap to the retention channel with Slack Bot Send a Message, built from that action log: retries attempted and how many recovered, total value recovered, dead or duplicate tokens deleted, payment links sent, and how many customers still have no usable card. Let the user choose the channel.

Also support one off requests to remove a customer's card on file, since people ask for that under privacy rules, using the same confirmation step and the same note trail.

## How to customize

- Change the expiring soon window from sixty days to whatever matches your billing cycle
- Choose which customers land in the worklist, for example only active subscribers or only accounts above a certain value
- Pick the Slack channel and decide whether the recap goes out daily or at the end of each working session

## FAQ

### Will this work with a free HubSpot account?

Yes. The app searches your contacts and companies and writes notes to them, which works on every HubSpot plan. You do need one custom field on the contact or company to hold each customer's Adyen shopper id, and custom fields are available on free accounts.

### Can someone accidentally charge a customer twice by clicking retry twice?

No. Each retry is tagged so that a repeated click is treated as the same attempt rather than a second charge, and the button is disabled while a retry is running.

### Can we undo deleting a card?

No. Removing a saved card is permanent, and the customer would have to enter their details again. That is why every deletion asks you to confirm and names the exact card being removed.

### Does the app store our customers' card numbers?

No. Card details stay with Adyen. The app only ever displays the card brand, the last four digits and the expiry date, which is what your team needs to spot a problem.

### Why do we need to put the Adyen shopper id on the HubSpot record?

Adyen can only look up saved cards for one customer at a time, so it cannot produce a list of everyone you bill. Your CRM is the customer list, and the shopper id is what links a HubSpot record to the right cards in Adyen.

Use this prompt in General Input: https://www.generalinput.com/prompts/card-on-file-console-for-subscription-billing-teams