One dinner, two charges, and an agent that is sure of itself
A card authorization for dinner often lands before the tip. The posted charge arrives later, with a new transaction ID, a higher amount, and sometimes a different description. Same meal. A careless tracker will insist you paid twice, or that the restaurant raised its prices between the appetizer and the check.
That bank-feed quirk is also how an AI agent turns into an expensive intern: fast, certain, and wrong. Teams are starting to hand these agents software-spend reviews and cancellation work. If the feed is only half reconciled, the automation does not get smarter. It files the bad conclusion faster.
The failure starts before anyone looks for a subscription
Plaid’s transaction documentation, which a lot of this plumbing follows, does not edit a pending charge into a posted one. The posted payment is a new transaction. The pending record is removed. When Plaid can match the pair, the posted transaction’s pending_transaction_id is the ID of the pending record that disappeared.
Name and amount are allowed to change. Plaid’s own illustration is a restaurant charge: the pending amount leaves the tip out, and the posted amount includes it. Merge two rows because the amounts look close, and you will staple the wrong payments together. That is how the agent reports two dinners, or a price hike that was a tip.
A removed pending row can mean more than one thing. It may have posted under a new ID, failed to match, or never become a charge at all. Those are different updates. Mix them up and you get different lies:
- Double-counting. Ignore the removal and keep the pending row beside the new posted row. One dinner is now two, and the duplicate was invented by the software.
- A fake cancellation. An authorization hold, common at gas stations, hotels, and rental-car counters, can expire without ever posting. Treat that removal as proof that billing stopped, and the agent will “confirm” a cancellation that never happened.
What has to be true before the agent speaks
On /transactions/sync, the path Plaid recommends for new integrations, pending_transaction_id is null when no pending transaction was supplied or Plaid could not match one. On the older /transactions/get path, Plaid describes a rarer failure: the posted transaction comes back without that field at all, while the pending row is still removed. Do not treat null and “field missing” as the same signal. Neither one is permission to invent a link.
An unresolved link can also be normal. Plaid names Capital One and USAA as institutions that do not provide pending data. The agent should say the match is unresolved. Do not hunt for a nearby amount and move on.
You cannot treat the sync stream as a list of new charges to append. Additions, modifications, and removals all have to be applied, or the local copy drifts from the account. A matched replacement and its removal are not guaranteed to land on the same page. Plaid says they should arrive within the same overall update. Do not recompute balances or fire alerts until additions, modifications, and removals from that sync have all been applied. Stopping at the first page is how the duplicate survives.
Posted does not mean frozen. Plaid says later changes are rare, but a refund or a bank’s recategorization can still alter a transaction that already posted. Modified updates are part of the feed, not optional housekeeping.
A cleaned name is not a contract
Open-banking feeds are good at amounts, dates, and the text on the line, across the accounts a customer actually connects. They are weak at what was bought, whether anyone still uses it, and why the bill moved.
A processor prefix makes this worse. That is the SQ * or PayPal tag sitting in front of the seller you actually paid. Strip it and the line gets readable. Strip too much and you erase the only detail that separated two businesses. Keep the original description beside any cleaned or assigned merchant name. If a customer corrects one match, store that correction with a scope. One mislabeled charge should not quietly relabel every similar line in the history.
A merchant category code, the MCC on the transaction, is a weak stand-in for the purchase. In its own MCC analysis, transaction-data firm Tapix found that only 53 percent of transactions could be easily and reliably categorized from the code. That figure is one vendor’s result. It is not a market census. It is still a concrete reason not to treat a code, a logo, or a tidy name as proof of what was bought. Force every charge under one brand and marketplace shopping collapses into the same blob as a membership billed by that company. A weekly coffee habit can look like a software renewal.
Tapix’s broader point fits the same hole: categories, spending summaries, subscription alerts, and even an agent that cancels a trial all depend on the quality of the transaction data underneath. Tapix also sorts “subscription management” into four jobs: a list, a forecast, a control or cancellation action, and a security audit. That is their taxonomy, not an industry standard. It is a useful split. The job everyone wants to automate first, cancellation, is the one that needs the strongest evidence. A detected pattern is not an explanation, and an explanation is not permission to act.
Short history, the wrong card, and a date that is not the purchase
A few months of records can hold a single payment for an annual software licence. Leave that one observation labelled uncertain until a renewal notice, an invoice, or a later charge supports recurrence. No history length makes every subscription detectable.
Coverage matters as much as duration. A customer can connect a main bank account while the tools are paid on another card. The product should say which accounts were checked and when they last updated.
Dates mislead quietly. Plaid warns that the date a transaction posts can differ from the date it occurred. Build a renewal calendar off the posted date alone and the inferred cycle shifts, even when the merchant did nothing unusual. Show inferred dates as estimates, separate from any date the merchant supplied.
What a price jump is allowed to mean
Suppose a service shows $12 in January, $12 in February, and $15 in March. Those figures are an illustration, not a market statistic. A tracker can flag the $3 difference. Calling it an overcharge goes past the evidence. The customer may have changed plans, added a seat, or bought something else from the same seller. Show the gap. Stop there unless a renewal email or an invoice explains it.
Foreign spend creates the same false pattern. An unchanged merchant price can still move in the customer’s home currency, so the original currency should stay visible when the feed provides it. Tapix also notes that a feed which looks reliable at home can fail on the first foreign payment, because familiar merchants stop repeating. A one-off foreign descriptor is a poor basis for a subscription call, and a home-currency wobble is not, by itself, a price rise.
A receipt can carry the plan name, the billing period, or the reason for a change, and a renewal notice may arrive before the charge posts. Match on service, account, amount, and billing period when you have them. A shared company name is not a match. Ordinary life breaks naive joins: a work inbox on a personal card, two household accounts at one provider, a forwarded receipt that duplicates a charge already in the feed. Last year’s bank line says what was paid. This year’s notice says what the merchant intends to charge next.
Let the customer mark an increase as expected, and keep that note on the payment, so the same warning does not fire every month.
Cancellation is a state, not a feeling
An agent that “handles subscriptions” is doing a different job the moment it sends a cancellation. Requested, in progress, and confirmed are not the same outcome. A message sent, a merchant asking for more information, and a confirmed end are three stories. Only the last one supports marking the subscription cancelled. Even then, access often continues until the paid period ends, and later charges should stay visible so a failed cancellation is not buried under a green check.
A missing payment is weaker evidence still. If the connected account has not refreshed, the app cannot tell whether billing stopped. Plaid’s normal production update schedule is separate from a forced refresh, so “no new record” can simply mean the provider has not checked yet. A connection gap over a renewal date is missing data. It does not prove a successful cancellation, and it is not a savings win.
When the connection stops updating, show the last successful update, name the account that needs attention, and keep the history you already have. Say plainly that recent payments may be missing.
Reading an account is not the same as moving money. In Tapix’s framing, an account-information service reads the transaction picture, while a payment-initiation service moves funds and is not part of that read. Spell out what a given connection can and cannot do, including which features work without inbox access. Bank lines and email receipts are different personal data. Explain each connection on its own.
Stopping future retrieval and deleting records already stored are separate operations. Show both. Retention limits depend on the product and the rules that apply to it. There is no single window to quote as if it covered every app.
Let the agent show its work
Someone will ask why a charge appears twice in one month. The honest answer might be one dinner and a tip, a pending row that has not been linked, or an authorization hold that expired. The reply should expose the underlying records, the original descriptions, both provider IDs, and whatever relationship is still unresolved. The sentence the model produces is not evidence.
Keep a stable internal payment reference that remembers the provider IDs it replaced, and make retries idempotent so a replay does not insert the same payment or fire the same alert twice. Storage and retry details are application choices, not a vendor recipe. The rule is the part that matters: one real payment, one counted event, one alert.
Test on labelled fictional histories, not on a live customer’s card. Useful cases include ordinary repeat purchases, incomplete data, refunds, customer corrections, an interrupted connection, and a sync update split across pages. Count misses and false alerts separately. Precision, the share of flagged subscriptions that a labelled review confirms, and recall, the share of known subscriptions the tracker finds, are sensible definitions to review by monthly versus annual billing, merchant ambiguity, and history coverage. They are proposed measures, not results from a benchmark.
Four outcomes are worth asserting before anyone ships an agent:
- One counted payment when the pending-to-posted link is present.
- An unresolved match, and no amount-only merge, when the link is null or absent.
- A disappeared hold that never posts, reported as a hold, not as a cancelled subscription.
- A replay of the same sync update, including one split across pages, that does not alert twice.
Questions worth asking before the agent acts
- Can dates, amounts, and a merchant name prove a charge is a subscription?
No. They can suggest a pattern worth investigating. They do not establish what was bought, whether it is still used, or why the amount changed.
- Why can one real payment look like two?
Under Plaid’s model, a pending charge is removed and a posted charge is created as a new transaction, sometimes with a different amount or description. Without reconciliation, both rows survive in the spending history.
- Is a missing pending link always an error?
No. The link is null when pending data was never supplied or could not be matched, and Plaid names Capital One and USAA as institutions that do not provide pending data. Do not merge on amount to paper over that.
- What should an alert say about a price increase?
Show the observed difference, keep the original currency when you have it, and state a reason only when a receipt or renewal notice supports it. Let the customer mark an expected change so the warning does not repeat.
- When is it fair to mark something cancelled?
When cancellation is shown to have succeeded. A request that was sent does not qualify, and neither does a charge that is merely absent. If the connection has not refreshed, a missing charge is missing data. It is not proof that billing stopped.
- What should the screen say when the bank connection stops updating?
The last successful update, which account needs attention, and the fact that recent payments may be missing. Existing history should stay available. Silence is not a savings win.
- What should the agent show before it acts?
The unresolved match, the original description, and both provider IDs. It should not cancel, merge, or explain a price change until a receipt supports the story or a cancellation is confirmed. The sentence it writes is not evidence.
The intern needs a paper trail
The popular pitch is that an agent will find the unused tools and cancel them before renewal. Sometimes it should. AI automation in finance falls apart when that agent treats a half-applied feed and a tidy logo as a closed case.
A smooth chat window will not decide who gets this right. The teams that do are the ones whose agents can say the match is unresolved, show the original line, and wait for a receipt. A dashboard that cannot do that is a rumor, delivered on a schedule. Do not mistake it for a control system.