Five Clocks in Every Transaction
A single payment carries five timestamps that each answer a different question, and confusing any two is how you fail an audit.
When someone asks "when did this transaction happen?" you'd expect one answer. A customer sent $10,000 on Tuesday afternoon. Simple.
You get five answers. The customer tapped submit at 2:47 PM Eastern. Your ledger recorded the debit at 2:48 PM. The ACH operator's system logged receipt of the batch file at 7:47 PM UTC.
The value date, when interest stopped accruing for the sender, is the next business day. The settlement date, when the receiving bank actually got the money, is the day after that.
Same payment. Five different correct answers to "when."
Outside fintech, most engineers don't think twice about a single created_at column. Timestamps are timestamps. Pick one and move on.
But each of those five clocks answers a different question, for a different audience, with real money attached to getting it wrong. Confuse any two and you'll misaccrue interest, break reconciliation, or hand an auditor the wrong number.
Five timestamps, five questions
Think of it like mailing a package. The date you write on the return label. The date the post office stamps it. The date FedEx scans it at the hub. The date it shows as "delivered." The date the recipient opens the box.
Five timestamps for one package, and if the insurance claim asks "when was it sent?" the answer depends on which clock the claims adjuster uses.
Request time is when the customer pressed the button. Your application server logged the HTTP request. This is the timestamp on the customer's receipt, and the only one they think exists.
Booking time is when your ledger actually posted the entry. In a well-built system, this is milliseconds after request time. In a system with queued writes or async processing, it might be seconds or minutes later.
The gap matters when a customer checks their balance between request and booking. They see stale data, and if your support team searches by booking time, they'll insist the customer is confused.
Processor timestamp is when the payment rail's system recorded the instruction. For ACH, this is when the operator received the batch file. For cards, it's when the issuer processed the authorization.
The processor runs on UTC. Your application probably does too, but your customer was thinking in Eastern time and your ops team is reading Pacific.
Value date is when money becomes "real" for accounting purposes. For the sender, interest stops accruing on the debited amount. For the receiver, interest starts.
Usually value date matches booking date. But a bank can backdate a value date to cover a processing delay, or forward-date it for a scheduled payment.
Settlement date is when actual dollars move between banks. ACH typically takes T+1 or T+2, while Fedwire settles same-day. Card transactions run in daily batches, often the next business day for the acquiring bank, with the card network settling with the issuer on its own schedule.
Each clock ticks independently. That's the engineering problem.
Where the clocks diverge
Most of the time, the five timestamps cluster close enough that nobody notices. A Fedwire sent at 11 AM settles the same day, and if the value date is today, three of the five clocks effectively agree.
The divergence happens at the edges. And the edges are where the bugs live.
Cut-off times create cliffs. Same-day ACH runs three processing windows per day, with the final submission deadline at 4:45 PM ET and settlement by 6:00 PM. Miss it by two minutes and your payment settles tomorrow instead of today.
A payment submitted at 4:44 PM makes the cut-off, but at 4:46 PM it queues for the next business day. Your booking time says Tuesday for both, while settlement says Tuesday for one and Wednesday for the other.
Wire transfers have the same cliff. Fedwire's processing day runs about 22 hours, closing around 6:45 PM ET. A wire submitted at 6:44 PM settles today, but at 6:46 PM it queues for tomorrow. Your system recorded both on the same calendar day, yet your bank posts them on different ones.
The Friday afternoon problem is the classic case. A customer sends a payment at 4 PM on Friday. Your ledger books it Friday, and the value date is Friday.
But ACH doesn't settle over weekends. So settlement happens Monday.
Who pays interest for Saturday and Sunday? The sender's account was debited (for accounting purposes) on Friday, but the receiver's bank doesn't have the money until Monday. That's three days of float.
On a $100 million corporate payment at current rates (roughly 3.6% annual), one day of float is about $9,900. Three days is roughly $30,000. That money belongs to someone, and which timestamp you use determines who.
Multiply by hundreds of large-value payments per day across a mid-size bank. A systematic one-day error in value date assignment adds up faster than most teams expect.
Time zones make it worse. A customer in Los Angeles submits a payment at 1:30 PM Pacific. That's 4:30 PM Eastern, after the same-day ACH cut-off. The customer's local clock says early afternoon while the payment rail says too late.
So your system shows "submitted at 1:30 PM" and "settles tomorrow" on the same screen. The customer calls support, support sees the booking time, and nobody can explain the gap without understanding that the cut-off runs on a different clock.
The Postgres trap
Most applications store timestamps in one of two Postgres column types: timestamp or timestamptz. The difference shapes how your data behaves across time zones, and getting it wrong introduces silent errors that may not surface for months.
timestamp (without time zone) stores exactly what you give it. Insert 2024-10-07 14:47:00 and you get back 2024-10-07 14:47:00. It has no time zone awareness.
If your application inserts Eastern time but another service reads it expecting UTC, the stored value doesn't convert. You've silently introduced a four- or five-hour error depending on daylight saving time.
timestamptz stores everything internally as UTC and converts on display based on the session's timezone setting. This is almost always what you want for event timestamps.
But value date and settlement date aren't moments in time. They're calendar days in a specific business context.
Store a value date as timestamptz and it can shift to a different calendar day depending on the reader's time zone. October 8 in UTC is still October 7 in Pacific. If your settlement reports run with a different timezone setting than the process that wrote the data, your value dates are wrong by a day.
The fix is straightforward. Event timestamps (request time, booking time, processor timestamp) go in timestamptz columns. Business dates (value date, settlement date) go in plain date columns. No time component, no zone ambiguity.
But you have to make this decision at schema design time. Migrating a live ledger's timestamp columns touches every query, every report, and every downstream consumer.
Every regulator wants a different clock
Different auditors ask different questions about the same transaction, and each question requires a different timestamp.
An AML investigator needs request time, to reconstruct when a customer initiated a suspicious transfer. A SOX auditor needs booking time, to verify when revenue was recognized in the ledger. A banking regulator checking reserve requirements needs settlement date, to determine when funds were actually available. Dispute resolution teams need all five, because the customer's story ("I sent it Tuesday afternoon") references request time, the bank's internal records reference booking time, and the settlement date determines which business day the payment counts against.
A transaction table with a single created_at column has already failed. It answered one question and made four others unanswerable. By the time an auditor asks, you can't reconstruct the missing timestamps from a single field.
Store all five, label them unambiguously, and let each one answer the question it was designed for. "When did this transaction happen?" was always five questions wearing one sentence.
Sources
- FedACH Same-Day Settlement Schedule - Three processing windows with final submission at 4:45 PM ET
- Fedwire Funds Service Operating Hours - 22-hour daily processing window
- NACHA Operating Rules - ACH network rules governing value dates and settlement timing
- PostgreSQL Documentation: Date/Time Types - Difference between timestamp and timestamptz column types
- Modern Treasury: Payment Effective Dates - How effective dates and cut-off times interact in practice
- Federal Reserve Interest Rate Data - Federal funds rate used for float cost calculations

Writes Ledger Drift. Engineer building AI systems and financial infrastructure. Open source contributor to x402, ACK, and MPP – agentic payment protocols.
Frequently Asked Questions
Built by Trio, a fintech-native engineering partner helping teams build the next generation of financial technology and infrastructure.
Subscribe to Ledger Drift for high-signal insights into how modern fintech is built, from systems to code to teams.