When the bank sends the same transaction twice

You buy a $40 dinner on the company card. Two days later you tip and the charge settles at $48. Your bank feed has now delivered that dinner twice: once as a $40 pending authorisation, once as a $48 posted charge, with a different transaction id. Handle that carelessly and the books show $88 of dinner.

Bank feeds do this constantly. It is not a bug in the bank; it is how card networks, ACH and the feed providers between them and your books actually work. This is where the duplicates come from, and what we do so that one purchase stays one entry.

Five ways one transaction arrives twice

Pending, then posted. A card authorisation appears within minutes as a pending transaction. Two to five days later the settled version arrives with its own id, sometimes an earlier date, and sometimes a different amount: a tip added, a foreign charge settled at the final rate, a hotel hold released and re-charged. The feed usually says which pending transaction the posted one replaces. Not always: some institutions never send pending data at all, so the first you see of a charge is the settled one.

Overlapping windows. Banks post transactions days after they happen, and ACH and wires are often back-dated. A feed that only asks "what is new since I last looked" will miss a Tuesday transaction that the bank posted on Friday with Tuesday's date. So a careful sync asks for a wider window than it needs, and gets most of last week again on every pull. The same transaction, delivered on every sync until it falls out of the window.

Authorisations that vanish. The petrol station pre-authorises $150 and charges $62. The hotel holds $500 and bills $340. The rental car company holds $1 to check the card is live. The hold arrives as a pending transaction; then the bank drops it, and the feed sends a removal notice. Ignore the removal and a $150 phantom purchase sits in the ledger forever.

Two feeds, one movement. A Stripe payout appears in the Stripe feed as money leaving the balance, and in the bank feed as a deposit. A transfer from checking to savings appears in both accounts. These are not duplicates; they are the two ends of one movement, and each end has to be recorded. But if either end is treated as income or expense, the same money is counted twice.

A statement uploaded over a live feed. A founder catching up on a quarter uploads a PDF or CSV statement for an account that is also connected. Every line on it already exists.

What we do with each

The rule underneath all of it is that a transaction has one identity: the account it belongs to and the id the provider gave it. The ledger will not store two rows with the same identity. That is enforced by the database, not by code that checks first and inserts second, because a check-then-insert has a gap, and two syncs running at once will find it.

That single rule handles the overlapping window on its own. We deliberately re-fetch several days on every sync so nothing is missed, and re-delivered transactions are simply recognised and skipped. Wider window, no duplicate rows.

Pending to posted is the case that needs care, because the two rows have different ids and the first one may already have work attached: a category, a matched receipt, a note. When the posted version arrives and the amount is unchanged, the pending row is promoted in place. It keeps its id, its categorisation and its receipt; it just stops being pending. When the amount changed, the posted transaction is recorded fresh and the stale pending row is removed, because the $40 was never the real charge. Either way, one dinner.

While a transaction is pending, nothing about it is final. So pending transactions are held out of every automatic categorisation path until they settle. A pending row is a promise the bank may withdraw, and we would rather categorise the charge once, at the amount that actually cleared.

Removals are honoured exactly as the bank sends them. A dropped authorisation is deleted. We also sweep for pending rows the bank has silently stopped reporting inside the window we just fetched, and remove those the same way. Neither path ever touches a posted transaction; a settled charge is never deleted by a feed event.

Transfers and payouts are recorded at both ends and then paired. Our matcher looks for the opposite leg, same amount, close date, and links the two so they cancel. A Stripe payout is never a candidate for income; it waits, unmatched, until the bank-side deposit arrives and pairs with it.

Uploaded statements are matched line by line against what the feed already delivered before anything is created. Lines that already exist are skipped and reported back as skipped, not silently absorbed.

What it means for your books

A purchase is one entry, at the amount that cleared, on the date the bank says it cleared, with whatever category and receipt were attached while it was pending. Holds that were never charged do not appear. Money moved between your own accounts is not income or expense anywhere.

None of that is visible when it works, which is the point. If you ever see the same purchase twice in your books, or a pre-authorisation that never became a charge, that is a case we have not seen, and we want to know about it.

Common questions

Why does a card purchase show up twice in my bank feed?

Card purchases arrive first as a pending authorisation and then, days later, as a settled charge with its own id and sometimes a different amount, such as a tip added or a foreign charge settled at the final rate. Handled carelessly the books show both. We promote the pending row in place when the amount is unchanged, and replace it when it changed.

What happens to a pre-authorisation that never became a charge?

A hotel hold or a fuel pre-authorisation arrives as a pending transaction; when the bank drops it, the feed sends a removal notice. We delete the pending row and also sweep for pending rows the bank has silently stopped reporting. A settled charge is never deleted by a feed event.

Are transfers between my own accounts duplicates?

No. A transfer between two of your accounts, or a Stripe payout, appears once at each end, and each end has to be recorded. We record both and pair them so they cancel. The mistake is treating either end as income or an expense, which counts the same money twice.

Tools that go with this

See what this looks like on your books

This is how Vecty keeps books today. Connect your accounts and a bookkeeper and a licensed CPA take it from there - every transaction categorized, every filing reviewed.

Book a 30-minute walkthrough