Transactions

The single record of every movement of money.

Every movement of money is a Transaction: a card purchase, a transfer between your own accounts, or funds arriving from a linked bank account. There’s no separate object for “funding” or “transfer”: each one is a transaction, distinguished by type, channel, and counterparty.

Shape of a transaction

A settled card purchase:

{
"transaction_id": "transaction_01937a7d-ad6b-77ca-8046-ad6b7ca046ce",
"account_id": "account_019372e3-b8dc-7de2-80b6-b8dcde20b6c9",
"business_id": "business_01937b95-484e-75af-8c68-484e5afc68d3",
"business_entity_id": "business_entity_01937c9d-3a1e-72b0-8881-3a1e2b088159",
"program_id": "program_019377de-7f88-7f02-894f-7f88f0294fcb",
"version": 1,
"version_time": "2026-08-21T19:42:10Z",
"stage": 2,
"stage_time": "2026-08-21T19:42:10Z",
"status": "settled",
"type": "payment",
"direction": "outbound",
"base_amount": "108.50",
"base_amount_currency": "USD",
"channel": {
"type": "card",
"card_id": "card_019375de-a2a0-7f31-884a-a2a0f3184abd",
"cardholder_id": "cardholder_01937e19-4ebb-75bd-8250-4ebb5bd25085",
"processing_details": {
"pan_entry_mode": "contactless",
"type": "point_of_service",
"card_product_id": "card_product_01937f5d-a015-7a55-818b-a015a5518b97",
"card_class": "consumer"
},
"scheme_details": {
"network": "visa",
"funding_type": "prepaid",
"product_type": "consumer"
}
},
"counterparty": {
"type": "card",
"merchant": {
"merchant_id": "WFM-0481726",
"code": "5411",
"category": "Grocery Stores, Supermarkets",
"name": "Whole Foods Market",
"city": "San Francisco",
"country": "US",
"subdivision": "US-CA",
"zip_code": "94105"
},
"acquirer": {
"id": "FNMS-4021958",
"name": "First National Merchant Services"
}
}
}

Keys whose value is null are omitted. This transaction has no message, client_reference, or original_amount, so those keys are absent.

type: what kind of movement

TypeMeaning
paymentA card purchase, or funds received from a linked bank account.
transferA movement of funds between two of your Capital Accounts.

direction: which way it moved

inbound (money arriving) or outbound (money leaving) the account named in account_id. A transfer between two of your own accounts produces one transaction on each side: outbound on the source, inbound on the destination.

channel: how it happened

channel.typeMeaningExtra fields
cardA transaction made with a Penny-issued card.card_id, cardholder_id, processing_details, scheme_details
internalA transfer between two of your Capital Accounts.None
bankFunds received from one of your linked accounts.linked_account_id

counterparty: who’s on the other side

counterparty.typeMeaningFields
cardThe merchant and acquirer, for card transactions.merchant, acquirer
transferThe account on the other side of the movement.account_id
businessAnother business, for transactions that cross business boundaries.business_id, business_entity_id, business_name

The merchant’s category is two flat fields on the merchant object: code (the 4-digit MCC, e.g. "5411") and category (its label, e.g. "Grocery Stores, Supermarkets"). The acquirer is an external processor identifier (id, name) and doesn’t follow Penny’s ID format.

status: where it is in its lifecycle

StatusMeaning
createdThe transaction exists but hasn’t been approved, declined, or settled.
approvedThe transaction is approved and its amount is pending.
declinedThe transaction was declined.
cancelledAn approved transaction’s pending amount was released before it settled.
expiredAn approved transaction’s pending amount timed out before it settled.
partially_settledPart of the pending amount has settled.
settledThe full amount has settled.
reversedThe transaction settled, and a later matching movement in the opposite direction brought its net effect back to zero.
deprecatedThe transaction was superseded and no longer counts; all its amounts are zero.

status is derived from the transaction’s applied events, checked in priority order. Each check falls through to the next when it doesn’t apply:

elseelseelseelseyesyesyesyesdeprecated?CHECKdeclined?CHECKsettled?CHECKapproved?CHECKcreatedSTATUS · BASE CASEdeprecatedSTATUSdeclinedSTATUSreversedSETTLED, NET ZEROpartially_settledSETTLED ≠ PENDINGsettledFULLY MATCHEDexpiredHOLD TIMED OUTcancelledHOLD RELEASEDapprovedSTILL PENDING

cancelled and expired both require the transaction to be approved first: they mark the pending amount being released without settling, and differ only in whether it timed out (expired) or was released another way (cancelled). See Transaction Events for the event identifiers (transaction.created, transaction.approved, transaction.settled, …) that drive these transitions.

stage increases by one each time an event advances the transaction through its lifecycle. Use it to detect out-of-order webhook delivery: a lower stage arriving after a higher one has already been processed can be ignored.

Reading transactions

GET /transactions/
GET /transactions/by-client-reference?client_reference={client_reference}
GET /transactions/{transaction_id}
GET /transactions/{transaction_id}/events
GET /transactions/{transaction_id}/history
  • GET /transactions/ lists your business’s latest transactions. It accepts at most one scope filter per request: account_id, card_id, cardholder_id, control_account_id, or program_id. Sending more than one returns 422. You can combine the scope filter with a stage_time range, except control_account_id, which can’t be combined with stage_time filters.
  • GET /transactions/by-client-reference returns every transaction carrying the exact client_reference you supplied when you started the movement. References don’t need to be unique, so this is a paginated list.
  • GET /transactions/{transaction_id} returns the latest state. Pass stage and version to read an earlier snapshot; 0 (the default for both) selects the latest.
  • GET /transactions/{transaction_id}/events returns the lifecycle events applied to the transaction.
  • GET /transactions/{transaction_id}/history returns the full transaction snapshot at each stage, ordered by stage.

Transactions are also available under the account, card, or cardholder they belong to: GET /accounts/capital/{account_id}/transactions, GET /accounts/control/{account_id}/transactions, GET /cards/{card_id}/transactions, and GET /cardholders/{cardholder_id}/transactions. See Reading Transactions & Ledger Entries for filtering examples.

Linked accounts and funding Capital Accounts

A linked account is an external bank account you register with Penny as a source of incoming funds. When Penny receives funds from a registered linked account, it routes them to your Capital Account, where they appear as an inbound transaction with type: "payment" and a bank channel whose linked_account_id names the linked account. Details of where to send funds are coming soon.

Subscribe to linked_account.created and linked_account.updated to follow a linked account’s status (see the Event Catalog).

See Linking an Account to register, list, get, and delete a linked account, and Accounts for how Capital Accounts hold balances and how money moves between them.

Received payment details

Funds received from a linked account show up as an ordinary inbound transaction on the Capital Account; there’s no separate payment object to reconcile against. The transaction doesn’t repeat the sender’s routing or account number. Instead, read channel.linked_account_id and fetch that linked account (with ?reveal_sensitive=true if you need the unmasked account number or IBAN; see Linking an Account).

Ledger entries

Where a transaction is the record of an activity, a ledger entry (GET /ledger/) is the resulting posting against an account’s balance. Most of the time you’ll work with transactions; use the ledger when you need the append-only record of exactly what moved and when.