Transactions & Ledger

Understand transaction state, events, and financial postings.

A financial activity in Penny can be represented in several ways depending on whether you need its current state, lifecycle history, or balance impact. There’s no separate object for “funding” or “transfer”: every movement of money, from a card purchase to an account-to-account transfer, is a Transaction, distinguished by its type, channel, and counterparty.

The diagram below follows a transfer, which produces a Transaction, Transaction Event, and Ledger Entry chain on each of its two accounts. Other activity, such as a card purchase, has a single chain on one account.

TransferSOURCETransactionOUTBOUNDTransaction EventDEBIT LIFECYCLELedger EntryDEBITTransactionINBOUNDTransaction EventCREDIT LIFECYCLELedger EntryCREDIT

Transactions

A Transaction represents the current financial and business state of an activity.

What it’s for: it’s the single record to read when you need to know what an activity represents right now (a card purchase, a transfer, funds received from a linked account) without reconstructing it from lower-level events or postings.

How it behaves:

  • type distinguishes payment (a card purchase, or funds received from a linked account) from transfer (a movement between two of your Capital Accounts).
  • direction is inbound or outbound relative to the account named on the transaction. A transfer between two of a Business Entity’s own accounts produces two transactions, one outbound on the source and one inbound on the destination, not one transaction shared between them.
  • channel records how it happened: card (with the card, cardholder, and network processing details), internal (a transfer between your Capital Accounts), or bank (funds received from a linked account, identified by linked_account_id).
  • counterparty records who’s on the other side: card (a merchant and acquirer), transfer (the account on the other side, by account_id), or business (another business, for transactions that cross business boundaries).
  • Transactions can be associated with contextual resources including Capital Accounts, Control Accounts, Cards, Cardholders, merchants, and Spend Controls. Not every transaction type uses every resource.

See Transactions for the full field reference.

Example: a transfer between two accounts

A transfer isn’t one record shared between both accounts. Each side gets its own Transaction, its own Transaction Events, and its own Ledger Entries, tied to the account it affected:

transferAccount ACAPITAL · SOURCEAccount BCAPITAL · DESTINATIONTransactionOUTBOUNDTransactionINBOUNDTransaction EventDEBIT LIFECYCLETransaction EventCREDIT LIFECYCLELedger EntryDEBITLedger EntryCREDIT

Account A sees an outbound Transaction, whose event posts a debit Ledger Entry; Account B sees an inbound Transaction, whose event posts a credit Ledger Entry. POST /accounts/capital/transfer returns both, as source_transaction and destination_transaction. Reading either account’s history on its own gives you the complete picture of what happened to its balance; you don’t need the other side’s records to reconcile one account.

Transaction Events

Transaction Events represent changes to a transaction over time, advancing it through its lifecycle. Each one carries an identifier, which is one of:

IdentifierApplies toMeaning
transaction.createdA new transactionInitializes the transaction.
transaction.request.approvedA new or existing transactionPenny’s own authorization decision approves the request: an initial hold, before final approval.
transaction.request.declinedA new or existing transactionPenny’s own authorization decision declines the request.
transaction.approvedA new or existing, not-yet-approved transactionFinal approval, decided by the card network or issuer, or applied directly when there’s no separate request phase.
transaction.declinedA new or existing transactionFinal decline.
transaction.adjustmentAn approved transactionChanges the pending amount without changing lifecycle state, for example a tip or pre-authorization adjustment.
transaction.cancelledAn approved transactionReleases the pending hold before anything settles.
transaction.expiredAn approved transactionThe authorization hold timed out before settling.
transaction.settledA new or existing, not declined/deprecated transactionFunds moved. Can post more than once for a partial settlement, and can apply directly with no prior approval event.
transaction.deprecatedAn existing transactionAdministratively supersedes the transaction, zeroing all its amounts.

Penny’s own authorization decision (transaction.request.approved/transaction.request.declined) and the final approval decided by the card network or issuer (transaction.approved/transaction.declined) are separate phases. A transaction can go through both, or go straight to final approval when there’s no separate request phase. Either approval event puts the transaction in approved status.

What it’s for: processing lifecycle changes or building event-driven integrations, where you need to react as a transaction moves from one state to the next rather than only its current snapshot.

How it behaves: the transaction’s stage increases by one each time an event advances the transaction through its lifecycle. Use stage to detect out-of-order webhook delivery: if an event arrives with a stage lower than one you’ve already processed, it’s safe to ignore. GET /transactions/{transaction_id}/events lists a transaction’s events, and GET /transactions/{transaction_id}/history returns the full transaction snapshot at each stage.

Do not treat a transaction’s current state and its event history as interchangeable: one represents the current projection, while the other describes changes over time.

How events combine into a status

The transaction’s overall status isn’t one of the event identifiers above. It’s derived from the combination of events applied so far, checked in this priority order:

deprecated
↓ (else)
declined
↓ (else)
settled → reversed (settled, but a matching movement in the opposite
direction later brought the net effect back to zero)
→ partially_settled (settled amount doesn't yet match pending)
→ settled (fully matched)
↓ (else)
approved → expired (an expired event was applied)
→ cancelled (a cancelled event was applied)
→ approved (still pending)
↓ (else)
created

For example, cancelled and expired are statuses that result from applying a transaction.cancelled or transaction.expired event to an already-approved transaction.

Ledger Entries

Ledger Entries represent the financial postings that affect balances: the resulting record once a Transaction Event posts, not the event itself.

What it’s for: reconciliation and balance analysis, where you need the raw, append-only record of exactly what moved and when, rather than inferring financial postings from the current Transaction object.

How it behaves: each Transaction Event posts exactly one Ledger Entry, against the one account its Transaction belongs to. Most activity, such as a card purchase, has a single Transaction on a single account. A transfer is the exception: it produces two independent Transactions, one per account, so their separate Transaction Events each post their own Ledger Entry. The debit on the source account and the credit on the destination account are paired only in that they trace back to the same transfer; one event never splits into two entries. Ledger Entries are read per account (GET /accounts/capital/{account_id}/ledger, and the equivalent under /accounts/control/{account_id}) or across your business with GET /ledger/.

Transaction state and ledger impact are related but serve different purposes. Reconciliation should use the financial records intended for that purpose.

Resource context

A Transaction may reference additional resources that explain the activity, including the account, card, cardholder, merchant, or authorization controls involved. GET /transactions/ accepts at most one scope filter per request (account_id, card_id, cardholder_id, control_account_id, or program_id), and control_account_id can’t be combined with stage_time filters. You can also read transactions scoped directly under an account, card, or cardholder, or find them by your own reference with GET /transactions/by-client-reference.

These relationships provide context; they do not mean every transaction type uses every resource.