# Balances With compute-on-write rollup calculations, balances always reflect the most up-to-date state of an account. ## Accounts and Balances Balances are auto-calculated sums of the entries for a given account. Every balance record maintains a `drBalance` for entries on the DEBIT side of the ledger and a `crBalance` for entries on the CREDIT side of the ledger. In addition, a `normalBalance` is calculated as the difference of `credits - debits` for credit normal accounts or as `debits - credits` for debit normal accounts. See [Chart of Accounts: Credit Normal and Debit Normal](https://www.twisp.com/docs/accounting-core/chart-of-accounts.md#credit-normal-and-debit-normal). ### A Balance for Every Journal, Currency, and Layer Accounts have separate balances for every journal, currency, and for each of the three layers: `SETTLED`, `PENDING`, and `ENCUMBRANCE` (see [Layered Accounting](https://www.twisp.com/docs/accounting-core/layered-accounting.md)). In a simple one-journal, one-currency ledger that only uses the `SETTLED` and `PENDING` layers, accounts will materialize two balances (one for each layer). More complex ledgers which have multiple journals and currencies will compute more balances for each account. For example, accounts in a ledger with 2 journals, 2 currencies, and using all 3 layers would have 12 balances (2 * 2 * 3). ## Balance Calculations Balances in Twisp are derived from entries in response to changes in the ledger. These balance calculations are computed on write, not on read. This means that we can query balances like any other piece of data in the system and have 100% certainty that the balance reflects the current state of the ledger. ### Calculating Normal Balance To illustrate how normal balances are calculated, let's look at some example tables. Say we have a ledger with two accounts: 1. **Cash** (debit normal) 2. **Revenue** (credit normal) Next, let's assume the following entries have posted to our ledger: | Entry ID | Account | Amount | Direction | |----------|---------|--------|-----------| | 1 | Revenue | $500 | CREDIT | | 2 | Cash | $500 | DEBIT | | 3 | Revenue | $400 | DEBIT | | 4 | Cash | $400 | CREDIT | | 5 | Revenue | $250 | CREDIT | | 6 | Cash | $250 | DEBIT | Given this set of entries, can calculate the normal balances for the Cash and Revenue accounts by taking the sum of their credits and debits, and subtracting one from the other according to their normal balance type. Here's what the balances table would look like: **Balances** | Account | CR Balance | DR Balance | Normal Balance | |---------|------------|------------|----------------| | Cash | $400 | $750 | $350 | | Revenue | $750 | $400 | $350 | Note how the sum of all credits equals the sum of all debits, so we know that this balance sheet is consistent with double-entry accounting principles. The normal balance gives us useful context-dependent information about the account. For the Cash account, it tells us how much cash we currently have on hand. For the Revenue account, it tells us the net revenues we've earned so far. ## Debits and Credits (DR/CR) Each entry in a journal either debits or credits an account. Along with a DEBIT/CREDIT direction, the amount of the entry is a signed number. Both negative and positive debits or credits are possible. The primary reason for this setup is to _make the debit and credit balances for an account meaningful_. Consider an example where we would like our credit balance to accurately reflect all deposits against an account: | Entry ID | Account ID | Type | Debit | Credit | |----------|------------|---------|-------|----------| | 1 | f29f83 | DEPOSIT | - | $1000.00 | DEBIT BALANCE: **$0.00**\ CREDIT BALANCE: **$1000.00**\ NORMAL BALANCE: **$1000.00** In this case our credit balance is $1000. In other words, we've deposited $1000 to this account. We'll assume that the account in question is a credit normal account, meaning that the normal balance is calculated by subtracting debits from credits. Now, let's say a mistake was made and that transaction amount should have actually been $1200 and we need to correct this. Because the ledger is immutable and append-only, we cannot erase or change the amount of the original transaction. To rectify this situation in a way that accurately records the history of events, we _void the transaction_ and repost the corrected version. | Entry ID | Account ID | Type | Debit | Credit | |----------|------------|--------------|-------|------------| | 1 | f29f83 | DEPOSIT | - | $1000.00 | | 2 | f29f83 | VOID_DEPOSIT | - | $(1000.00) | | 3 | f29f83 | DEPOSIT | - | $1200.00 | DEBIT BALANCE: **$0.00**\ CREDIT BALANCE: **$1200.00**\ NORMAL BALANCE: **$1200.00** In this scenario above, the credit balance is now $1200. Exactly what we'd expect. Let's consider an alternate solution: what if we had simply utilized debits and credits to achieve the same resulting account balance? | Entry ID | Account ID | Type | Debit | Credit | |----------|------------|--------------|----------|----------| | 1 | f29f83 | DEPOSIT | - | $1000.00 | | 2 | f29f83 | VOID_DEPOSIT | $1000.00 | - | | 3 | f29f83 | DEPOSIT | - | $1200.00 | DEBIT BALANCE: **$1000.00**\ CREDIT BALANCE: **$2200.00**\ NORMAL BALANCE: **$1200.00** In this incorrect version, the _credit balance_ is now $2200 along with a _debit balance_ of $1000. This effectively lost meaning of the sum total debit and credit balances, because money wasn't _actually_ moving out of the account. We were just cancelling > **Note:** > > For the sake of simplicity in illustration, we did not specify the layer used. We can assume that they all occurred on the "SETTLED" layer. > > Learn more about layers in [Layered Accounting](https://www.twisp.com/docs/accounting-core/layered-accounting.md). --- # Chart of Accounts The chart of accounts is the basis for creating balance sheets, P&L reports, and for understanding the balances for the customer and business entities your business services. ## Accounts Overview [Accounts](https://www.twisp.com/docs/reference/graphql/types/object.md#account) are a named **store of value** in that every account has a [Balance](https://www.twisp.com/docs/reference/graphql/types/object.md#balance), as well as a **record of activity** in the form of ledger entries posted to them. In Twisp, accounts record activity and materialize balances across multiple **layers** (see [Layered Accounting](https://www.twisp.com/docs/accounting-core/layered-accounting.md)) and have full support for multiple [Journals](https://www.twisp.com/docs/reference/graphql/types/object.md#journal) Like every other record in the system, accounts are stored as immutable documents with all changes to them stored in a versioned history. Say we had a wallet product. We can model each customer's wallet as an account, and the full collection of those accounts plus any other accounts we need to track money makes up our **chart of accounts**. | Name | Dr Balance | Cr Balance | Normal Balance | | --- | --- | --- | --- | | Ali | $2.30 | $9.20 | $6.90 | | Bea | $5.00 | $6.00 | $1.00 | | Cal | $1.80 | $8.50 | $6.70 | | ... | ... | ... | ... | ## Organizing Accounts with Sets Many accounting systems model the chart of accounts as a single hierarchical tree, with "parent" accounts and "child" or sub-accounts. This is especially useful to do things like roll up balances across multiple accounts. However, a single structure for _every_ account can be limiting, as some use cases call for multiple independent ways of organizing accounts. In Twisp, we use a more flexible concept of [AccountSets](https://www.twisp.com/docs/reference/graphql/types/object.md#account-set), which are custom groups of accounts that aggregate balances and provide a unified interface into the entries for all accounts. | Account Set | Accounts | Debit Balance | Credit Balance | Normal Balance | |-------------|----------|---------------|----------------|----------------| | A | Ali, Cal | $4.10 | $17.70 | $13.60 | | B | Bea | $5.00 | $6.00 | $1.00 | | ... | ... | ... | ... | ... | One important feature of sets is that they can **contain other sets**. By nesting sets in this way, can model more complex structures for a chart of accounts. Let's take the above accounts and imagine that they represent FBO accounts that we manage on behalf of our customers. With account sets, we could create a chart of accounts with these relationships: ```mermaid graph BT ASS[/Assets\] CAS[/Cash\] ACH[ACH Receivable] INV[Investments] LIA[/Liabilities\] FBO[/FBO Accounts\] Ali Bea Cal ACH --> CAS --> ASS INV --> ASS Ali & Bea & Cal --> FBO --> LIA ``` In this chart, there are only four accounts: "ACH Receivable", "Investments", and the FBO accounts for Ali, Bea, and Cal. Organizing these accounts into various sets lets us materialize balances for different combinations of accounts. > **Note:** > > Entries can only be posted to _accounts_, not to account _sets_. ## Balances Roll Up Entries All accounts have corresponding [Balances](https://www.twisp.com/docs/reference/graphql/types/object.md#balance) to summarize the amounts of entries posted to the account. For example, Bea's account could have the following entries: | Amount | Direction | Posted On | |--------|-----------|------------| | $4.00 | CREDIT | 2022-09-15 | | $2.00 | CREDIT | 2022-09-19 | | $5.00 | DEBIT | 2022-09-20 | Adding these up, we can see how her account reflects a **debit balance** of **$5.00** and a **credit balance** of **$6.00**, resulting in a **normal balance** of **$1.00**. We can use account sets to roll up balances for different groupings of accounts. For example, if the "FBO Accounts" set corresponds the balance of a single account at a banking partner, it can be used to do reconciliation confirming that the balances of your FBO accounts match up to the balance shown by the bank. > **Note:** > > The balance of account set X is alway equal to the sum of all entries posted to accounts in X plus the balances of all account sets in X. To illustrate this concept, let's see how each of the account sets in the chart above would calculate their balances. To begin, we need to know the balances for all accounts. We'll focus here on just normal balances to keep things simple. | Account | Normal Balance | |----------------|----------------| | ACH Receivable | $14.60 | | Investments | $72.67 | | Ali | $6.90 | | Bea | $1.00 | | Cal | $6.70 | Next, we can calculate each account set's balance by adding up the balances of its member accounts and account sets. | Account Set | Members | Member Balances | Account Set Balance | |--------------|-------------------|---------------------|---------------------| | Cash | ACH Receivable | $14.60 | $14.60 | | FBO Accounts | Ali, Bea, Cal | $6.90, $1.00, $6.70 | $14.60 | | Assets | Cash, Investments | $14.60, $72.67 | $82.27 | | Liabilities | FBO Accounts | $14.60 | $14.60 | ## Credit Normal and Debit Normal In double-entry accounting, accounts are often differentiated between "credit normal" and "debit normal". This means is that the balance for an account is either computed as `credits - debits` for credit normal accounts, and as `debits - credits` for debit normal accounts. Debit normal accounts are often used for asset and expense account types where debits indicate money flowing _in_ to the account and thus _increasing_ its balance, and credits represent money flowing _out_ and _decreasing_ the balance. Credit normal accounts are often used for accounts which track things like revenue, liability, and equity where credits _increase_ the balance and debits _decrease_ the balance. > **Note:** > > Read more about how account balances are calculated on the [Balances](https://www.twisp.com/docs/accounting-core/balances.md) page. Twisp imposes no strict rules about which accounts can or should be credit or debit normal, as the design for a product ledger's chart of accounts will vary depending on the specifics needs of the product. Some users may decide to impose the standard 5 account types: assets, liabilities, expenses, revenue, and equity. Others may need a modified structure, or may only need credit normal accounts. The accounting core can structured to accommodate any variety. ## Creating Accounts You can provision Twisp with a chart of accounts for a number of business use cases. Twisp can generate a chart of accounts suitable for representing economic activities for use cases such as: - Digital Banking and Card Issuing - Acquiring, Marketplace, Vertical SaaS - Lending - Currency Exchange > **Note:** > > You have full control over the chart of accounts and can create new settlement and other accounts to represent your own funds flows. An example of commonly-used accounts include a number of settlement accounts: | Account | Purpose | |----------------------|----------------------------------------------------------------------------------------------------| | Suspense Account | The suspense account is posted to when transactions cannot be booked against a valid open account. | | ACH Settlement | The ACH settlement account for settling ACH transactions. | | Bill Pay Settlement | An account for settling bill payments. | | Checks | Settlement account for checks that are posted to the system. | | ACH Reconciliation | An account for ACH reconciliations losses | | Charge off | Accounts for charging off accounts that we’re closing due to losses. | | Fraud Loss | Fraud losses are booked here. | | Courtesy Credit | Account used by customer service/experience for courtesy funds. | | Levies, garnishments | Account used for dealing with account levies and wage garnishments. | | Card Disputes | Account for issuing funds for card disputes. | | ACH Disputes | Account for ACH disputes. | --- # Encoded Transactions Design accounting logic with tran codes to create composable transaction types. ## Transactions and Entries _Transactions_ record all accounting events in the ledger. **In Twisp, the only way to write to a ledger is through a transaction.** Every transaction writes two or more entries to the ledger in standard double-entry accounting practice. Twisp expands upon the basic principle of an accounting transaction with additional features like transaction codes and correlations. An _entry_ represents one side of a transaction in a ledger. In other systems, these may be called "ledger lines" or "journal entries". Entries always have an account, amount, and direction (CREDIT or DEBIT). In addition, Twisp uses the concept of "entry types" to assign every entry to a categorical type. Twisp enforces double-entry accounting, which in practice means that entries can only be entered in the context of a Transaction. Posting a transaction will create _at least 2_ ledger entries. In addition, we run validity checks against transactions to ensure that they do not introduce inconsistencies into the accounting core. For example, we ensure that the debit and credit entries written by a transaction sum to zero so that value is never lost or created from nothing. **By establishing a strict definition of how entries are written to the ledger, we ensure a high level of integrity and consistency in the ledger record.** ## Composable Double-Entry Accounting Every transaction entered must use a transaction code to indicate what _kind_ of transaction it is, which in turn determines how entries are written to the ledger. This applies both a strong categorization scheme to transactions as well as a valuable internal reference of transaction types for product engineers to draw upon. We think that transaction codes (tran codes) are the optimal way for engineers working on financial products do double-entry accounting. They encode the basic patterns for a type of transaction as a predictable and repeatable formula. You can think of tran codes as functions which define how a transaction acts upon the ledger. To better understand how tran codes work, let's look at an example. ## Tran Codes in Practice While the full API for tran codes allows for a large degree of flexibility, we'll focus on a simple case for the sake of illustration. Let's say we're building a product that needs to support tracking ACH credits, and reflecting when money is deposited to a customer's bank account. This transaction can be encoded with a tran code. When a new transaction is posted using this tran code, we want to make sure that: 1. A credit entry is written to the "ACH Settlement" account 2. A debit entry is written to the customer's account 3. The amount used for both entries is equal We'll use the entry type `ACH_CR` for the credit entry, and `ACH_DR` for the debit entry to be extra-clear about what these entries represent. Here's how we would create the tran code using GraphQL: **Request** ```graphql mutation BasicTranCode { createTranCode( input: { # Unique ID for the tran code tranCodeId: "53c7a411-9070-42c8-81cf-ea37ebe63182" # Unique name for the tran code code: "ACH_CREDIT" # Short description of what it does description: "An ACH credit into an account." # Params define the inputs to a transaction params: [ { name: "account", type: UUID, description: "Deposit account ID." } { name: "amount", type: DECIMAL } { name: "effectiveDate", type: DATE } ] # Values supplied to the Transaction transaction: { journalId: "uuid('822cb59f-ce51-4837-8391-2af3b7a5fc51')" effective: "params.effectiveDate" } # The ledger Entries to create entries: [ { # ID for the ACH settlement account accountId: "uuid('8cd11607-1104-4270-9482-ae4b8053fd5a')" # The `params` object allows runtime access to input values units: "params.amount" currency: "'USD'" entryType: "'ACH_DR'" direction: "DEBIT" layer: "SETTLED" } { accountId: "params.account" units: "params.amount" currency: "'USD'" entryType: "'ACH_CR'" direction: "CREDIT" layer: "SETTLED" } ] } ) { tranCodeId code entries { accountId units entryType direction layer } } } ``` **Response** ```json { "data": { "createTranCode": { "tranCodeId": "53c7a411-9070-42c8-81cf-ea37ebe63182", "code": "ACH_CREDIT", "entries": [ { "accountId": "uuid('8cd11607-1104-4270-9482-ae4b8053fd5a')", "units": "params.amount", "entryType": "'ACH_DR'", "direction": "DEBIT", "layer": "SETTLED" }, { "accountId": "params.account", "units": "params.amount", "entryType": "'ACH_CR'", "direction": "CREDIT", "layer": "SETTLED" } ] } } } ``` Did you notice that `params.amount` value for the `amount` of each entry? This is a CEL expression which means "use the `amount` field on the `params` argument provided when a new transaction is posted. Because tran codes are essentially **templates for transactions**, this lets us dynamically set the amount field when we actually go to post a transaction with this tran code. With this tran code defined, we can now post a transaction to perform a deposit of $12.87: **Request** ```graphql mutation BasicTransaction { postTransaction( input: { transactionId: "a71dd074-3b4e-465b-80f4-9dc111a8ecb4" tranCode: "ACH_CREDIT" params: { account: "63e766a5-4a04-4aee-a4d6-aa49350f13c6" amount: "12.87" effectiveDate: "2022-09-21" } } ) { transactionId tranCode { code } entries(first: 4) { nodes { accountId units currency direction layer } } } } ``` **Response** ```json { "data": { "postTransaction": { "transactionId": "a71dd074-3b4e-465b-80f4-9dc111a8ecb4", "tranCode": { "code": "ACH_CREDIT" }, "entries": { "nodes": [ { "accountId": "8cd11607-1104-4270-9482-ae4b8053fd5a", "units": "12.87", "currency": "USD", "direction": "DEBIT", "layer": "SETTLED" }, { "accountId": "63e766a5-4a04-4aee-a4d6-aa49350f13c6", "units": "12.87", "currency": "USD", "direction": "CREDIT", "layer": "SETTLED" } ] } } } } ``` By providing just a `tranCode` and `params` to the `postTransaction` call, we are able to make use of the predefined tran code to write two complete entries to the ledger. This is just a teaser of what you can do with tran codes. With their flexibility, you can encode nearly any kind of transaction your product needs to support. ### Example Tran Codes The set of tran codes you need will be specific to your company and product. There are often many "archetypal" tran codes which are commonly used. Some examples include: | TranCode | Description | Types of Entries Written | |------------------|----------------------------|------------------------------------------| | WIRE_TRANSFER | Bank-to-bank wire transfer | WIRE_OUTGOING_DR, WIRE_INCOMING_CR | | CARD_HOLD_CANCEL | Card hold cancellation | CARD_HOLD_CANCEL_DR, CARD_HOLD_CANCEL_CR | | DEPOSIT | Bank deposit | DEPOSIT_DR, DEPOSIT_CR | | BILL_PAYMENT | Bill payment | BILL_PAYMENT_DR, BILL_PAYMENT_CR | ## Timing and Sequencing Accurate recording of the times and sequences of events in a ledger is critical for auditing and reconciliation. ### Effective Dates There are two significant time-based values on a ledger transaction: the `created` timestamp and an `effective` date. | Field | Description | |-----------|---------------------------------------------------------| | Created | The wall time the transaction was posted to the ledger. | | Effective | The accounting date to which this transaction applies. | These two values often are not the same. For example, an ACH transaction may post over the weekend, but the `effective` accounting date of the transaction may be the following Monday. ### Entry Sequences Ledger entries are always posted in the order in which they are defined within a tran code. When a transaction is posted, it writes this ordering as a `sequence` onto every entry written. Within the context of a transaction, we can thus see a clear incremented sequence of all entries. Because transactions are written atomically at the database layer, every entry is posted at the same clock time. ## Embedding Meaning & Context When attempting to trace money movement, having as much contextual information as possible is useful to get a complete picture of what happened and when. In addition to the meaning and context implied by tran codes and entry types, additional information can be embedded into transactions through correlations and metadata. ### Correlation Identifiers With transactional workflows it is often necessary to group a set of related transactions. For example: during card processing there is often a hold, then a hold release or expiration, and finally a settlement. In Twisp, correlation identifiers are used to group these transactions together. When a transaction is posted without a `correlationId`, it uses its own `transactionId` as the `correlationId`. Then, future related transactions can be posted with the same `correlationId` to indicate their relationship to the original. This is very useful for events like holds, auths, auth reversals, etc. The transactions from the card processing example above might look like this: | ID | Amount | Description | Correlation ID | |----|--------|-------------------------|----------------| | 1 | $50 | Place card hold | 1 | | 2 | $50 | Release card hold | 1 | | 3 | $50 | Settle card transaction | 1 | Because transactions (2) and (3) are _related_ to transaction (1), they share the same correlation ID. This way, we can easily observe the entire history of a multi-transaction event by querying the correlated transactions. ### Transaction Metadata Transactions contain a `metadata` field which can store arbitrary structured data about the transaction in JSON format. This can be a highly useful way to embed application- and product-specific data in the transaction itself. It has no effect on the accounting operations. --- # The Accounting Core An accounting engine for building products that work with money. We built the Accounting Core to serve as an engine for all kinds of financial products. It is designed to be flexible enough to cover any use case that you can imagine, and comes pre-packaged with a set of sensible defaults to give you a head start. When you provision a new instance of the Twisp Accounting Core, you get a **transactions ledger** for double-entry accounting, a **chart of accounts** to represent any economic activity, and **layered balances** for tracking settled, pending, and planned funds flows. All of this is accessible via a straighforward [GraphQL API](https://www.twisp.com/docs/reference/graphql.md). Armed with these primitives, designers and developers will have a clear mental model for how to interact with, iterate on, and scale their products with foundational ledger tooling that gives confidence when working with mission-critical financial data. Continue reading to learn more about how each part of accounting core works: - [**Ledgers**](https://www.twisp.com/docs/accounting-core/ledgers-in-twisp.md)\ The accounting core is built upon a single source-of-truth ledger. - [**Encoded Transactions**](https://www.twisp.com/docs/accounting-core/encoded-transactions.md)\ Design accounting logic with tran codes to create composable transaction types. - [**Chart of Accounts**](https://www.twisp.com/docs/accounting-core/chart-of-accounts.md)\ A chart of accounts models all of the economic activity that your ledger records. - [**Layered Accounting**](https://www.twisp.com/docs/accounting-core/layered-accounting.md)\ Layers help clarify the true state of funds in accounts. - [**Balances**](https://www.twisp.com/docs/accounting-core/balances.md)\ With compute-on-write rollup calculations, balances always reflect the most up-to-date state of an account. --- # Layered Accounting Layers help clarify the true state of funds in accounts. ## The Three Layer Model Twisp utilizes a layered model of accounting to distinguish transactions between three important categories: 1. The **settled** layer is for transactions that have fully settled. 2. The **pending** layer includes holds and pending transactions which have been authorized but not yet settled. 3. The **encumbrance** layer contains expected, planned, and scheduled future transactions. By segmenting transactions in this way, it provides the data integrity needed for more accurate and useful balance calculations and funds flow modelling. It also unlocks features to support a variety of use cases without adding complexity. For example, we can use the pending layer to verify that an account will have enough funds after the holds and pending transactions have cleared. With the encumbrance layer, we can add transactions scheduled in the future and goals or budgeting tools to set money aside in an account. Without explicit layers, chaos reigns. Many DIY ledgers that we've seen lack the concept of layers, or only apply the concept partially. This can make it incredibly difficult to reason about the state of a ledger or perform basic accounting operations like cash reconciliation. > **Note:** > > Not every product will need to use all three layers, but they are always available when and if new versions of your product do need to make use of them. ## Layered Balances Each account can have a different aggregate balance in each layer depending on how transactions have been posted. To calculate the account balance for a layer, we sum all entries on that layer: $$ Where $b(l)$ is the balance for a layer and $e_l$ is the set of all entries on that layer. All accounts and account sets a debit balance (sum of debit entries), credit balance (sum of credit entries), and normal balance. Learn more about balances in [Balances](https://www.twisp.com/docs/accounting-core/balances.md). ## Layers in Practice To demonstrate how layers work in practice, let's consider an account set that has entries posted to its sub-accounts across all three layers. We'll follow the state changes to the account set and its balance as additional entries are written. For simplicity's sake, we'll assume that all amounts are in USD. At first, the only entry is on the settled layer. **Request** ```graphql fragment F_BalanceAmount_Standard on BalanceAmount { normalBalance { formatted(as: { locale: "en-US" }) } drBalance { formatted(as: { locale: "en-US" }) } crBalance { formatted(as: { locale: "en-US" }) } } fragment F_Transaction_Standard on Transaction { transactionId effective tranCode { tranCodeId code version } } # Using fragment: F_AccountSet_Summary query GetBertAccountSummary($set_bertId: UUID!) { accountSet(id: $set_bertId) { accountSetId name balance(currency: "USD") { currency available(layer: ENCUMBRANCE) { ...F_BalanceAmount_Standard } settled { ...F_BalanceAmount_Standard } pending { ...F_BalanceAmount_Standard } encumbrance { ...F_BalanceAmount_Standard } } entries(first: 10) { nodes { direction layer entryType account { accountId code name } amount { units currency formatted(as: { locale: "en-US" }) } transaction { ...F_Transaction_Standard } } } } } ``` **Response** ```json { "data": { "accountSet": { "accountSetId": "65bc724c-6767-4f35-90f9-279a12f95fd4", "name": "Bert", "balance": { "currency": "USD", "available": { "normalBalance": { "formatted": "$100.00" }, "drBalance": { "formatted": "$100.00" }, "crBalance": { "formatted": "$0.00" } }, "settled": { "normalBalance": { "formatted": "$100.00" }, "drBalance": { "formatted": "$100.00" }, "crBalance": { "formatted": "$0.00" } }, "pending": { "normalBalance": { "formatted": "$0.00" }, "drBalance": { "formatted": "$0.00" }, "crBalance": { "formatted": "$0.00" } }, "encumbrance": { "normalBalance": { "formatted": "$0.00" }, "drBalance": { "formatted": "$0.00" }, "crBalance": { "formatted": "$0.00" } } }, "entries": { "nodes": [ { "direction": "DEBIT", "layer": "SETTLED", "entryType": "RECORD_TX_DR", "account": { "accountId": "f3d6f928-9bfe-4029-ad08-6473acf38465", "code": "BERT.CHECKING", "name": "Bert's Checking Acct." }, "amount": { "units": "100", "currency": "USD", "formatted": "$100.00" }, "transaction": { "transactionId": "d52e6593-4973-4522-a065-d6eef9428308", "effective": "2022-10-31", "tranCode": { "tranCodeId": "15a1b0c5-bad0-4ac1-ac0a-a1a078fc14ae", "code": "RECORD_TX", "version": 1 } } } ] } } } } ``` **Variables** ```json { "set_bertId": "65bc724c-6767-4f35-90f9-279a12f95fd4" } ``` Because this entry is on the settled layer, the balance for that layer reflects the amount of that entry. > **Note:** > > The "normal balance" for an account is different for credit normal and debit normal accounts. > > Learn more about [Calculating Normal Balance](https://www.twisp.com/docs/accounting-core/balances.md#calculating-normal-balance). The pending and encumbrance layers have no entries, so they are currently at $0. Let's see how things change when entries on the pending layer are written. In this case, the entries are modeling a basic "hold-settle" pattern: a hold is placed on the pending layer, and then settled to the settled layer and cleared from the pending layer. **Request** ```graphql fragment F_BalanceAmount_Standard on BalanceAmount { normalBalance { formatted(as: { locale: "en-US" }) } drBalance { formatted(as: { locale: "en-US" }) } crBalance { formatted(as: { locale: "en-US" }) } } fragment F_Transaction_Standard on Transaction { transactionId effective tranCode { tranCodeId code version } } # Using fragment: F_AccountSet_Summary query GetBertAccountSummary($set_bertId: UUID!) { accountSet(id: $set_bertId) { accountSetId name balance(currency: "USD") { currency available(layer: ENCUMBRANCE) { ...F_BalanceAmount_Standard } settled { ...F_BalanceAmount_Standard } pending { ...F_BalanceAmount_Standard } encumbrance { ...F_BalanceAmount_Standard } } entries(first: 10) { nodes { direction layer entryType account { accountId code name } amount { units currency formatted(as: { locale: "en-US" }) } transaction { ...F_Transaction_Standard } } } } } ``` **Response** ```json { "data": { "accountSet": { "accountSetId": "65bc724c-6767-4f35-90f9-279a12f95fd4", "name": "Bert", "balance": { "currency": "USD", "available": { "normalBalance": { "formatted": "$118.45" }, "drBalance": { "formatted": "$138.95" }, "crBalance": { "formatted": "$20.50" } }, "settled": { "normalBalance": { "formatted": "$118.45" }, "drBalance": { "formatted": "$118.45" }, "crBalance": { "formatted": "$0.00" } }, "pending": { "normalBalance": { "formatted": "$0.00" }, "drBalance": { "formatted": "$20.50" }, "crBalance": { "formatted": "$20.50" } }, "encumbrance": { "normalBalance": { "formatted": "$0.00" }, "drBalance": { "formatted": "$0.00" }, "crBalance": { "formatted": "$0.00" } } }, "entries": { "nodes": [ { "direction": "CREDIT", "layer": "PENDING", "entryType": "RECORD_SETTLE_PENDING_TX_CR", "account": { "accountId": "f3d6f928-9bfe-4029-ad08-6473acf38465", "code": "BERT.CHECKING", "name": "Bert's Checking Acct." }, "amount": { "units": "20.50", "currency": "USD", "formatted": "$20.50" }, "transaction": { "transactionId": "b04b5df6-e9f7-4358-a353-cb064527bb91", "effective": "2022-11-05", "tranCode": { "tranCodeId": "673649ee-6aca-471a-8f55-86dc5cc4f5f2", "code": "RECORD_SETTLE_PENDING_TX", "version": 1 } } }, { "direction": "DEBIT", "layer": "SETTLED", "entryType": "RECORD_SETTLE_TX_DR", "account": { "accountId": "f3d6f928-9bfe-4029-ad08-6473acf38465", "code": "BERT.CHECKING", "name": "Bert's Checking Acct." }, "amount": { "units": "18.45", "currency": "USD", "formatted": "$18.45" }, "transaction": { "transactionId": "b04b5df6-e9f7-4358-a353-cb064527bb91", "effective": "2022-11-05", "tranCode": { "tranCodeId": "673649ee-6aca-471a-8f55-86dc5cc4f5f2", "code": "RECORD_SETTLE_PENDING_TX", "version": 1 } } }, { "direction": "DEBIT", "layer": "PENDING", "entryType": "RECORD_PENDING_TX_DR", "account": { "accountId": "f3d6f928-9bfe-4029-ad08-6473acf38465", "code": "BERT.CHECKING", "name": "Bert's Checking Acct." }, "amount": { "units": "20.50", "currency": "USD", "formatted": "$20.50" }, "transaction": { "transactionId": "f55074da-15d9-5b2d-826f-37c71b9a5db2", "effective": "2022-11-03", "tranCode": { "tranCodeId": "0b92fef4-7337-4d5d-9d6c-441da46cc34e", "code": "RECORD_PENDING_TX", "version": 1 } } }, { "direction": "DEBIT", "layer": "SETTLED", "entryType": "RECORD_TX_DR", "account": { "accountId": "f3d6f928-9bfe-4029-ad08-6473acf38465", "code": "BERT.CHECKING", "name": "Bert's Checking Acct." }, "amount": { "units": "100", "currency": "USD", "formatted": "$100.00" }, "transaction": { "transactionId": "d52e6593-4973-4522-a065-d6eef9428308", "effective": "2022-10-31", "tranCode": { "tranCodeId": "15a1b0c5-bad0-4ac1-ac0a-a1a078fc14ae", "code": "RECORD_TX", "version": 1 } } } ] } } } } ``` **Variables** ```json { "set_bertId": "65bc724c-6767-4f35-90f9-279a12f95fd4" } ``` Notice that the entries on the pending layer _only affected the pending-layer balance_. The settled balance was only changed by the additional settled-layer entry. Let's see how the balances change with a few more entries across all layers: **Request** ```graphql fragment F_BalanceAmount_Standard on BalanceAmount { normalBalance { formatted(as: { locale: "en-US" }) } drBalance { formatted(as: { locale: "en-US" }) } crBalance { formatted(as: { locale: "en-US" }) } } fragment F_Transaction_Standard on Transaction { transactionId effective tranCode { tranCodeId code version } } # Using fragment: F_AccountSet_Summary query GetBertAccountSummary($set_bertId: UUID!) { accountSet(id: $set_bertId) { accountSetId name balance(currency: "USD") { currency available(layer: ENCUMBRANCE) { ...F_BalanceAmount_Standard } settled { ...F_BalanceAmount_Standard } pending { ...F_BalanceAmount_Standard } encumbrance { ...F_BalanceAmount_Standard } } entries(first: 10) { nodes { direction layer entryType account { accountId code name } amount { units currency formatted(as: { locale: "en-US" }) } transaction { ...F_Transaction_Standard } } } } } ``` **Response** ```json { "data": { "accountSet": { "accountSetId": "65bc724c-6767-4f35-90f9-279a12f95fd4", "name": "Bert", "balance": { "currency": "USD", "available": { "normalBalance": { "formatted": "$78.45" }, "drBalance": { "formatted": "$183.77" }, "crBalance": { "formatted": "$105.32" } }, "settled": { "normalBalance": { "formatted": "$73.63" }, "drBalance": { "formatted": "$118.45" }, "crBalance": { "formatted": "$44.82" } }, "pending": { "normalBalance": { "formatted": "$0.00" }, "drBalance": { "formatted": "$20.50" }, "crBalance": { "formatted": "$20.50" } }, "encumbrance": { "normalBalance": { "formatted": "$4.82" }, "drBalance": { "formatted": "$44.82" }, "crBalance": { "formatted": "$40.00" } } }, "entries": { "nodes": [ { "direction": "DEBIT", "layer": "ENCUMBRANCE", "entryType": "ASSIGN_TO_BUDGET_DR", "account": { "accountId": "d7284018-0f6f-4a53-87b1-23f0d22f0883", "code": "BERT.BUDGET", "name": "Bert's Budget" }, "amount": { "units": "44.82", "currency": "USD", "formatted": "$44.82" }, "transaction": { "transactionId": "95d5b790-d709-4f41-8361-43029b3e1af3", "effective": "2022-11-07", "tranCode": { "tranCodeId": "95ede9f4-b3f6-4cc3-ab18-338fd9f41e8b", "code": "ASSIGN_TO_BUDGET", "version": 1 } } }, { "direction": "CREDIT", "layer": "SETTLED", "entryType": "RECORD_TX_CR", "account": { "accountId": "f3d6f928-9bfe-4029-ad08-6473acf38465", "code": "BERT.CHECKING", "name": "Bert's Checking Acct." }, "amount": { "units": "44.82", "currency": "USD", "formatted": "$44.82" }, "transaction": { "transactionId": "1b76219c-31cc-4776-9fa0-546b6ffe8c27", "effective": "2022-11-07", "tranCode": { "tranCodeId": "15a1b0c5-bad0-4ac1-ac0a-a1a078fc14ae", "code": "RECORD_TX", "version": 1 } } }, { "direction": "CREDIT", "layer": "ENCUMBRANCE", "entryType": "ALLOC_BUDGET_CR", "account": { "accountId": "d7284018-0f6f-4a53-87b1-23f0d22f0883", "code": "BERT.BUDGET", "name": "Bert's Budget" }, "amount": { "units": "40", "currency": "USD", "formatted": "$40.00" }, "transaction": { "transactionId": "6403c5e0-9ee9-4ac4-ab2d-b05a2264c3e6", "effective": "2022-11-10", "tranCode": { "tranCodeId": "2e92e3aa-9871-4c47-8c9a-5d76e6340769", "code": "ALLOC_BUDGET", "version": 1 } } }, { "direction": "CREDIT", "layer": "PENDING", "entryType": "RECORD_SETTLE_PENDING_TX_CR", "account": { "accountId": "f3d6f928-9bfe-4029-ad08-6473acf38465", "code": "BERT.CHECKING", "name": "Bert's Checking Acct." }, "amount": { "units": "20.50", "currency": "USD", "formatted": "$20.50" }, "transaction": { "transactionId": "b04b5df6-e9f7-4358-a353-cb064527bb91", "effective": "2022-11-05", "tranCode": { "tranCodeId": "673649ee-6aca-471a-8f55-86dc5cc4f5f2", "code": "RECORD_SETTLE_PENDING_TX", "version": 1 } } }, { "direction": "DEBIT", "layer": "SETTLED", "entryType": "RECORD_SETTLE_TX_DR", "account": { "accountId": "f3d6f928-9bfe-4029-ad08-6473acf38465", "code": "BERT.CHECKING", "name": "Bert's Checking Acct." }, "amount": { "units": "18.45", "currency": "USD", "formatted": "$18.45" }, "transaction": { "transactionId": "b04b5df6-e9f7-4358-a353-cb064527bb91", "effective": "2022-11-05", "tranCode": { "tranCodeId": "673649ee-6aca-471a-8f55-86dc5cc4f5f2", "code": "RECORD_SETTLE_PENDING_TX", "version": 1 } } }, { "direction": "DEBIT", "layer": "PENDING", "entryType": "RECORD_PENDING_TX_DR", "account": { "accountId": "f3d6f928-9bfe-4029-ad08-6473acf38465", "code": "BERT.CHECKING", "name": "Bert's Checking Acct." }, "amount": { "units": "20.50", "currency": "USD", "formatted": "$20.50" }, "transaction": { "transactionId": "f55074da-15d9-5b2d-826f-37c71b9a5db2", "effective": "2022-11-03", "tranCode": { "tranCodeId": "0b92fef4-7337-4d5d-9d6c-441da46cc34e", "code": "RECORD_PENDING_TX", "version": 1 } } }, { "direction": "DEBIT", "layer": "SETTLED", "entryType": "RECORD_TX_DR", "account": { "accountId": "f3d6f928-9bfe-4029-ad08-6473acf38465", "code": "BERT.CHECKING", "name": "Bert's Checking Acct." }, "amount": { "units": "100", "currency": "USD", "formatted": "$100.00" }, "transaction": { "transactionId": "d52e6593-4973-4522-a065-d6eef9428308", "effective": "2022-10-31", "tranCode": { "tranCodeId": "15a1b0c5-bad0-4ac1-ac0a-a1a078fc14ae", "code": "RECORD_TX", "version": 1 } } } ] } } } } ``` **Variables** ```json { "set_bertId": "65bc724c-6767-4f35-90f9-279a12f95fd4" } ``` Again, notice that the **layered balances** are only aggregating entries from their layer. ## Calculating an Available Balance While keeping separate balances for each layer is useful for tracking the current, planned, and predicted state of accounts, it is also possible to calculate a balance across layers. The **available** balance is a special balance that rolls up the balances of the other layer balances. If we show the available balance from the example above, this totaling is clearly visible. **Request** ```graphql fragment F_BalanceAmount_Standard on BalanceAmount { normalBalance { formatted(as: { locale: "en-US" }) } drBalance { formatted(as: { locale: "en-US" }) } crBalance { formatted(as: { locale: "en-US" }) } } fragment F_Transaction_Standard on Transaction { transactionId effective tranCode { tranCodeId code version } } # Using fragment: F_AccountSet_Summary query GetBertAccountSummary($set_bertId: UUID!) { accountSet(id: $set_bertId) { accountSetId name balance(currency: "USD") { currency available(layer: ENCUMBRANCE) { ...F_BalanceAmount_Standard } settled { ...F_BalanceAmount_Standard } pending { ...F_BalanceAmount_Standard } encumbrance { ...F_BalanceAmount_Standard } } entries(first: 10) { nodes { direction layer entryType account { accountId code name } amount { units currency formatted(as: { locale: "en-US" }) } transaction { ...F_Transaction_Standard } } } } } ``` **Response** ```json { "data": { "accountSet": { "accountSetId": "65bc724c-6767-4f35-90f9-279a12f95fd4", "name": "Bert", "balance": { "currency": "USD", "available": { "normalBalance": { "formatted": "$78.45" }, "drBalance": { "formatted": "$183.77" }, "crBalance": { "formatted": "$105.32" } }, "settled": { "normalBalance": { "formatted": "$73.63" }, "drBalance": { "formatted": "$118.45" }, "crBalance": { "formatted": "$44.82" } }, "pending": { "normalBalance": { "formatted": "$0.00" }, "drBalance": { "formatted": "$20.50" }, "crBalance": { "formatted": "$20.50" } }, "encumbrance": { "normalBalance": { "formatted": "$4.82" }, "drBalance": { "formatted": "$44.82" }, "crBalance": { "formatted": "$40.00" } } }, "entries": { "nodes": [ { "direction": "DEBIT", "layer": "ENCUMBRANCE", "entryType": "ASSIGN_TO_BUDGET_DR", "account": { "accountId": "d7284018-0f6f-4a53-87b1-23f0d22f0883", "code": "BERT.BUDGET", "name": "Bert's Budget" }, "amount": { "units": "44.82", "currency": "USD", "formatted": "$44.82" }, "transaction": { "transactionId": "95d5b790-d709-4f41-8361-43029b3e1af3", "effective": "2022-11-07", "tranCode": { "tranCodeId": "95ede9f4-b3f6-4cc3-ab18-338fd9f41e8b", "code": "ASSIGN_TO_BUDGET", "version": 1 } } }, { "direction": "CREDIT", "layer": "SETTLED", "entryType": "RECORD_TX_CR", "account": { "accountId": "f3d6f928-9bfe-4029-ad08-6473acf38465", "code": "BERT.CHECKING", "name": "Bert's Checking Acct." }, "amount": { "units": "44.82", "currency": "USD", "formatted": "$44.82" }, "transaction": { "transactionId": "1b76219c-31cc-4776-9fa0-546b6ffe8c27", "effective": "2022-11-07", "tranCode": { "tranCodeId": "15a1b0c5-bad0-4ac1-ac0a-a1a078fc14ae", "code": "RECORD_TX", "version": 1 } } }, { "direction": "CREDIT", "layer": "ENCUMBRANCE", "entryType": "ALLOC_BUDGET_CR", "account": { "accountId": "d7284018-0f6f-4a53-87b1-23f0d22f0883", "code": "BERT.BUDGET", "name": "Bert's Budget" }, "amount": { "units": "40", "currency": "USD", "formatted": "$40.00" }, "transaction": { "transactionId": "6403c5e0-9ee9-4ac4-ab2d-b05a2264c3e6", "effective": "2022-11-10", "tranCode": { "tranCodeId": "2e92e3aa-9871-4c47-8c9a-5d76e6340769", "code": "ALLOC_BUDGET", "version": 1 } } }, { "direction": "CREDIT", "layer": "PENDING", "entryType": "RECORD_SETTLE_PENDING_TX_CR", "account": { "accountId": "f3d6f928-9bfe-4029-ad08-6473acf38465", "code": "BERT.CHECKING", "name": "Bert's Checking Acct." }, "amount": { "units": "20.50", "currency": "USD", "formatted": "$20.50" }, "transaction": { "transactionId": "b04b5df6-e9f7-4358-a353-cb064527bb91", "effective": "2022-11-05", "tranCode": { "tranCodeId": "673649ee-6aca-471a-8f55-86dc5cc4f5f2", "code": "RECORD_SETTLE_PENDING_TX", "version": 1 } } }, { "direction": "DEBIT", "layer": "SETTLED", "entryType": "RECORD_SETTLE_TX_DR", "account": { "accountId": "f3d6f928-9bfe-4029-ad08-6473acf38465", "code": "BERT.CHECKING", "name": "Bert's Checking Acct." }, "amount": { "units": "18.45", "currency": "USD", "formatted": "$18.45" }, "transaction": { "transactionId": "b04b5df6-e9f7-4358-a353-cb064527bb91", "effective": "2022-11-05", "tranCode": { "tranCodeId": "673649ee-6aca-471a-8f55-86dc5cc4f5f2", "code": "RECORD_SETTLE_PENDING_TX", "version": 1 } } }, { "direction": "DEBIT", "layer": "PENDING", "entryType": "RECORD_PENDING_TX_DR", "account": { "accountId": "f3d6f928-9bfe-4029-ad08-6473acf38465", "code": "BERT.CHECKING", "name": "Bert's Checking Acct." }, "amount": { "units": "20.50", "currency": "USD", "formatted": "$20.50" }, "transaction": { "transactionId": "f55074da-15d9-5b2d-826f-37c71b9a5db2", "effective": "2022-11-03", "tranCode": { "tranCodeId": "0b92fef4-7337-4d5d-9d6c-441da46cc34e", "code": "RECORD_PENDING_TX", "version": 1 } } }, { "direction": "DEBIT", "layer": "SETTLED", "entryType": "RECORD_TX_DR", "account": { "accountId": "f3d6f928-9bfe-4029-ad08-6473acf38465", "code": "BERT.CHECKING", "name": "Bert's Checking Acct." }, "amount": { "units": "100", "currency": "USD", "formatted": "$100.00" }, "transaction": { "transactionId": "d52e6593-4973-4522-a065-d6eef9428308", "effective": "2022-10-31", "tranCode": { "tranCodeId": "15a1b0c5-bad0-4ac1-ac0a-a1a078fc14ae", "code": "RECORD_TX", "version": 1 } } } ] } } } } ``` **Variables** ```json { "set_bertId": "65bc724c-6767-4f35-90f9-279a12f95fd4" } ``` We can query the available balance with a configuration option to roll up balances: * across all layers, * over the settled and pending layers, * or just the settled layer (at which point it is equivalent to the settled balance). One way to think of the available balance is as a function that takes a layer and sums some or all of the other layer balances depending on which layer is provided. a(l)= \\begin{dcases} b(S),& \\text{if } l=S\\\\ b(S) + b(P),& \\text{if } l=P\\\\ b(S) + b(P) + b(E),& \\text{if } l=E\\\\ \\end{dcases} $$ Where $a(l)$ is the available balance at a given layer, $b(l)$ is the balance for a layer, and $S, P, E$ represent the Settled, Pending, and Encumbrance layers. --- # Ledgers in Twisp The accounting core is built upon a single source-of-truth ledger. ## What is a Ledger? Financial ledgers are the foundation of systems that track money. Double-entry accounting has been the de facto mechanism to record financial transactions for over 500 years, and ledgers are at the heart of this system. - Banks track debits and credits across many accounts, with multiple financial instruments, all the while analyzing this data to surface financial insights to consumers. - Payment infrastructure companies need robust multi-tenant account servicing built on many FBOs, made available to many clients via a secure API. - Accounting software depends on strong transactional guarantees and zero-downtime data access to ensure accuracy and reliability. Financial ledgers and the data derived from them are the engine of all such systems. ## Ledgers in Twisp At Twisp, we set out to rethink the underlying technology for financial ledger systems by combining the operational and scaling characteristics of a distributed database with the correctness guarantees offered by relational databases. > **Note:** > > To learn more about the infastructure that powers our ledgers, read the [Infrastructure](https://www.twisp.com/docs/infrastructure.md) section. The ledger for our accounting core is designed to be flexible enough to support any financial product, yet structured enough to provide a reliable and trustworthy store of financial data. ## Architecture With your Twisp account, you can provision multiple instances of the accounting core. Each instance is powered by a single ledger. A ledger is composed of journals, accounts, entries, transactions, and balances. ### Journals Journals allow for the organizing of transactions within separate "books". In many cases, users only need a single journal. For this reason, Twisp always contains a default journal with code `DEFAULT`. Journals can be used for a variety of functions. For example, users may create separate journals for different currencies, or product-specific journals. ### Accounts A chart of accounts models all of the economic activity that your ledger provides. The chart of accounts is the basis for creating balance sheets, P&L reports, and for understanding the balances for the customer and business entities your business services. Read more about accounts in [Chart of Accounts](https://www.twisp.com/docs/accounting-core/chart-of-accounts.md). ### Layered Entries An entry represents one side of a transaction in a ledger. In other systems, these may be called "ledger lines" or "journal entries". Twisp enforces double-entry accounting, which in practice means that entries can only be entered in the context of a Transaction. Posting a transaction will create _at least 2_ ledger entries. Entries always have an account, amount, direction (CREDIT or DEBIT), and type for denoting an particular entry category (e.g. "TRANSFER_DR" for an entry representing the debit side of a transfer). In addition, every entry is assigned to a layer (SETTLED, PENDING, and ENCUMBRANCE) to differentiate between entries in various stages of a transaction lifecycle. Read more about how layers work in [Layered Accounting](https://www.twisp.com/docs/accounting-core/layered-accounting.md). ### Transactions Transactions record all accounting events in the ledger. In Twisp, the only way to write to a ledger is through a transaction and every transaction writes two or more entries to the ledger in standard double-entry accounting practice. Twisp expands upon the basic principle of an accounting transaction with additional features like transaction codes and correlations. Transaction Codes (tran codes) are how financial engineers do double-entry accounting: they encode the basic patterns for a type of transaction as a predictable and repeatable formula. Read more about the power of transactions and tran codes in [Encoded Transactions](https://www.twisp.com/docs/accounting-core/encoded-transactions.md). ### Balances Balances are auto-calculated sums of the entries for a given account. Each account can have balances across all three layers, and every balance record maintains a separate debit and credit balance amount. Read more about how balances are calculated in [Balances](https://www.twisp.com/docs/accounting-core/balances.md). ## Architected from Principles Our ledger has been designed in adherence to strong, time-tested accounting principles. - **Enforce double-entry principles**.\ In other words, ensure that value (money) is never created or destroyed. There is always a debit and credit side for every transaction. - **History must be fully preserved.**\ Ledgers are immutable and append-only. There is always a full history of changes to provide a clear, unbroken lineage of the current state. - **Contextualize every transaction**.\ Much financial activity happens across multiple phases of a lifecycle, with different transactions at each phase. Proper use of correlations, layers, transaction metadata means that no useful context is lost across that lifecycle. - **Design for composability**.\ With transaction codes (tran codes) and automations, higher-order accounting logic can be encoded into a simplified interface. This way, your designers and engineers can focus on the business logic of your product, not the accounting details. - **When in doubt, rely upon industry standards**.\ There are times to be inventive, and times to trust the system. We've learned from experience and research to make use of well-established accounting patterns like layers and typed entries. --- # Twisp The accounting engine for financial products. Welcome to the Twisp Docs. Here you can read about what the Twisp Accounting Core is, why it works the way that it does, and how you can interact with it via the GraphQL API. - [**Introduction**](https://www.twisp.com/docs/introduction.md)\ Get familiar with the basics of what Twisp is and why we built it. - [**Accounting Core**](https://www.twisp.com/docs/accounting-core.md)\ Explore the features in the accounting core: ledgers, transactions, accounts, and more. - [**Processors**](https://www.twisp.com/docs/processors.md)\ Process external payment networks and financial protocols, handling communication with ACH, wire transfers, and card networks. - [**Infrastructure**](https://www.twisp.com/docs/infrastructure.md)\ Read about the underlying systems that give Twisp a unique technological edge. - [**API Reference**](https://www.twisp.com/docs/reference.md)\ Browse the endpoints and type schema made available through our GraphQL API. --- # Computation Engine The computation infrastructure supports protocol workflows and an extensive financial calculation runtime via CEL. Protocol workflows provide the ability to model sophisticated state-machine-like computations on ledger data and calculations, including the ability to communicate with external systems. With the workflow engine, it is possible to model sophisticated financial protocols and their interaction with ledgers. Examples of these protocols include ISO 8583, ACH, and external webhook processing. Finally, Twisp's built-in expression language, which is highlighted later in this document, is populated with an extensive function library. In addition to a traditional standard library, Twisp also includes advanced financial functionality to assist in complex financial systems. ## Workflows as State Machines A state machine is an abstraction used to design algorithms and protocols. A state machine simply reads a set of inputs and changes to a different state based on those inputs. A state is a description of the status of a system waiting to execute a transition. Twisp provides a mechanism to define state machines called protocol workflows. The combination of immutable ledgers, the calculation runtime, and protocol workflows allows Twisp to process all manner of complex financial protocols and translate them into ledger entries with full historical lineage and integrity guarantees. The input for protocol workflows can be triggered by events resulting from ledger operations (create, read, update, delete) or API calls to Twisp (including webhooks from external systems). One can think of the operational trigger events for protocol workflows with the same mental model of triggers in monolithic relational databases. Workflows in Twisp occur in the context of the same atomic interactive transaction that triggered them. This provides strong transactional and data integrity guarantees for the resulting data. The output from workflow transitions and the resulting state from the calculation engine is also persisted to ledgers. This allows Twisp to define chains of transactionally guaranteed protocol workflow computation with full lineage. Since each transaction in Twisp has dedicated compute and memory, there is little concern of noisy neighbor problems or node overloading. ## Calculation Runtime The calculation runtime is the primary mechanism Twisp uses to both populate ledgers and derive useful information from ledger data. It is an expression based language and runtime that allows processing over schema-based input and that results in schema-based output. It is purpose-built to operate with all manners of persisted ledger data with strong integrity guarantees including a powerful type system, strong type checking, and predictable runtime performance. Calculation expressions are powered by [Common Expression Language (CEL)](https://github.com/google/cel-spec) along with our financial type system and function library. This provides an expressive and powerful mechanism to derive and populate ledger data. All data resulting from expressions in the calculation runtime have strong integrity guarantees, and result in strictly-typed schema-based output that is purpose built for persisting to ledgers. The calculation engine supports use cases such as calculating balances, fees, and interest on any dimension; extracting, aggregating, and bucketing ledger data; real-time analytics for business intelligence or user insights; and all manner of user defined functionality powered by the expressive syntax and extensive function library. ## Function Library The ledger core and calculation runtime plus standard library and protocol workflow provide an extremely powerful set of primitives which lie at the foundation of the Twisp accounting core. To make these primitives even more powerful for financial applications, Twisp includes a financial type system and extensive function library. This functionality is surfaced within the calculation runtime as types within the strict type system and functions that operate over those types within expressions. The Twisp financial type system and function library includes sophisticated types such as full-featured money and currency types, along with functions to make complex calculations on those types, such as interest, fees, rounding, balances, and the like. The function library also includes parsers for well known external formats, such as ISO8583, ACH, and many more. --- # Infrastructure Underneath the accounting core is a purpose-built ledger database and computation engine. ## Architectural Components There are four primary components to the infrastructure which powers the Twisp Accounting core: * The [Financial Ledger Database](https://www.twisp.com/docs/infrastructure/ledger-database.md) is a cloud-native, append-only ledger data store with strong guarantees about the lineage of data. * The [Computation Engine](https://www.twisp.com/docs/infrastructure/computation-engine.md) powers massively scalable data operations, protocol workflows, and an extensive financial calculation runtime. * [Security configurations](https://www.twisp.com/docs/infrastructure/security-and-auth.md) that provide strong access controls through explict authorization policies. * The [Local Environment](https://www.twisp.com/docs/infrastructure/local-environment.md) streamlines development and CICD integration testing. ## Background: Decoupling Storage, Computation, and Security In most modern systems, data storage is decoupled from computational processes. To manage storage of this data, applications contain logic to replicate storage data structures, apply transactional logic, and then execute remote calls to persist the resulting data in external, decoupled storage systems. Decoupled storage and computation is ubiquitous in nearly all distributed architectures. This decoupling of storage and computation makes the process of guaranteeing the integrity of a value calculated in one system, then persisted in another, into a complex distributed systems problem. For financial ledger systems where we must take great care to guarantee not only the integrity of financial data but also calculations that result from that data, this is a problem. However, with Twisp, just as referential integrity offers integrity over relations, the financial ledger database offers integrity, full transparency, and lineage of both the underlying data along with all calculations derived from that data. This property allows us to declaratively define strict, well-defined, and atomic processes that compute ledger calculations, and with those procedures defined, the underlying system transactionally guarantees the accuracy of the resulting calculation data. Additionally, we treat security and authentication/authorization concerns as first-class citizens with their own dedicated resources. All access controls are determined by a centralized set of security policies. There are two primary reasons for this design: 1. Keeping all security protocols in a single place makes it easy for auditors to review every policy and quickly identify any areas of concern. 2. Storing policies in separate locations runs the risk of introducing conflicts between policy definitions and causing unexpected errors. --- # The Twisp Financial Ledger Database The ledger database is an immutable, append only data structure that provides strong gaurantees about the lineage of data. ## Overview: Ledgers à la Twisp At Twisp, we set out to rethink the underlying technology for financial ledger systems by combining the operational and scaling characteristics of a distributed database with the correctness guarantees offered by relational databases. The result is the **Twisp Financial Ledger Database** (FLDB). The Twisp ledger database is transactionally guaranteed, secure, and highly scalable; it is tailor-built for financial system use cases. It is designed to easily support the massively multi-tenant workloads that modern financial applications require. With the advent of serverless and infrastructure-as-code, the ability to define fully functioning systems with high-level code is now a reality. Twisp uses an infrastructure-as-code approach to creating resources and managing access to those resources. ## Comparison to Other Databases Like traditional relational databases, the Twisp FLDB enforces the structure of data with strict user-defined schemas, indexes, and referential integrity. Users familiar with the tabular model of SQL will find many of the same principles and abstractions apply. The internal structure of the database is not limited to tabular data, however. Like document databases, ledgers records can contain nested sub-documents as well as collection data types like lists and JSON. Sub-documents still must conform to a strictly defined schema. In this way, the database supports both modalities of data modeling: tabular and document-based. Because schema migrations are so simple, it is easy to adapt the data model as new requirements emerge. ## Database Features ### Append-Only Immutability The lineage of data in a financial system is critical. When it comes to monetary values, we need to be able to track with confidence where the money came from, where it went, and how a balance grew from $0 to $1. At the most basic level of the database structure, we can make these same guarantees about any data that enters the system. We do this through a simple mechanism: immutability. Immutability means that for an operation that results in new data, the system simply records that new piece of data. All previous data still remains recorded unchanged as well. You can think of this as a log of all changes to a system. Once we have this append-only log, we can then apply strong cryptographic guarantees to the log so the underlying data remains tamper proof and unchanged. We call this log and strong cryptographic guarantees an immutable ledger of changes to the system. Twisp provides this as a primitive to make use of these same guarantees for their application data. Therefore, all data are recorded with full lineage in the system. In practice, this means that a `history` node is available on every record in the database to expose the chain of changes to the state of any record. Read more about append-only immutability at [Versions And History](https://www.twisp.com/docs/reference/ledger/versions-and-history.md). ### Strong Data Integrity A transactional MVCC write-ahead log (WAL) tracks all data that is managed by the system. The data blocks of the WAL are chained together with cryptographic hash functions. This cryptographically sealed chain allows Twisp to verify the integrity of any data in the system using cryptographic verification. The system utilizes a digest hash value that represents the full hash chain of the WAL at any committed value plus a Merkle audit proof to cryptographically verify the integrity of any data in the system. This allows users to verify and prove the integrity of any record in the database at any time. ### Index-First Design All reads to data stored must occur via an index. This eliminates an entire class of problems that can occur in production systems with table scans and unclear query planning, and promotes well-understood data access patterns. There is no limit the number of indexes on data, as we recognize the tradeoff that more indexes allows for the possibility of write amplification. The index system is core to Twisp’s infrastructural design, allowing for massive scalability and zero-downtime migrations. Index features: - **Strongly consistent**\ All database operations through indexes guarantee data integrity. - **Strong partition and sorting controls**\ The declarative schema supports compound keys and multi-field sorting. - **Compatible with all fields**\ Any part of the schema can be used to create the index key, including the JSON and List collection types. - **Filterable with CEL expressions**\ Partial indexes can be created to index only those records matching a specified condition. - **Unlimited definitions**\ Ledger definitions can include as many indexes as needed. - **Online, zero-downtime migrations**\ Indexes can be re-partitioned on the fly using the migration system. ### Index-Based Relations Using properly constructed indexes, ledgers support all the standard relation types: - One-to-one - One-to-many - Many-to-many The database implements full referential integrity: records can refer to other records via an index, even across ledgers. Twisp ensures that a referenced (parent) record cannot be deleted without any referencing (child) records first being deleted. In addition to preserving referential integrity, the GraphQL schema will automatically support join operations between referenced ledgers. ### Referential Integrity A powerful feature of relational databases are tools to enforce the integrity and structure of the data they store. Strict schemas require a homogenous shape to all data stored in a table. Rather than depending on an application to enforce a data schema, the database enforces it instead. With this, the database system can now run migrations and schema changes to reorganize the data into evolving structure for you, with strong guarantees that data is always in a well known shape. In addition to the schema structure of individual data elements, referential integrity can enforce the relationships between different related pieces of data. The relationships between these pieces of data are often referred to as foreign key (FK) constraints. These constraints provide a mechanism that allows a data element to reference another separate but related piece data via a FK relationship. Once this relationship is established the system guarantees that one piece of data that is in relation to another will always exist by preventing any changes, whether updates or deletes, that might violate the relationship. The system is maintaining referential integrity. ### Type-Safe Schemas Within the database, every record conforms to the same schema of typed fields defined within a declarative schema. Fields can be typed to contain a single **scalar** type like strings or signed integers, **collection** types like lists or JSON objects, or they can contain **sub-documents** with their own fields. This table lists all currently supported types for ledger fields. | Type | Description | |-------------|-------------------------------------------------------------------------| | `int` | 64-bit signed integer | | `uint` | 64-bit unsigned integer | | `double` | 64-bit IEEE floating-point number | | `bool` | boolean value true or false | | `string` | Strings of Unicode code points | | `timestamp` | ISO-8601 timestamp with nanosecond precision and full time zone support | | `bytes` | Byte sequences (base64 encoded) | | `uuid` | 128-bit RFC4122 Universally Unique Identifier | | `json` | holds arbitrary JSON (use "{}" to structure insert values) | | `[]` | list of scalar values (example: [string]) | Note the two collection types `json` and **list** (`[]`). Lists can be used to store variable-length ordered collections of a scalar types. `json` types can store arbitrary JSON documents (up to X bytes) Twisp combines the flexibility of document databases like MongoDB while maintaining the schematic integrity and operational model of a table-driven relational database like PostgreSQL. In practice, this means that you can create nested document structures while maintaining type safety and predictable data shapes. Sub-documents can be used to store related fields that might otherwise add noise to the base document. Because they are typed as well, sub-documents are part of the explicitly declared schema and behave in just the same way as fields in the base-level document do. They can even be used in indexes. ### Zero-Downtime Migrations With declarative schemas we can simply change the schema definition and Twisp will create, validate, and execute changesets to migrate your schema from version to version, all in an online fashion with zero down time. > **Note:** > > Zero downtime means that existing resources can continue to be accessed and used. Newly added/removed elements will be in a `DELETE_ONLY` or `WRITE_ONLY` state as they're added to the system and in some cases backfilled with data. The table below shows all supported operations for migrations. | Migration | Support | |----------------------|----------------------------------------| | Add | Supported | | Drop | Supported | | Add Index | Supported | | Drop Index | Supported | | Add Schema Elements | Supported | | Drop Schema Elements | Supported | | Add References | Supported | | Drop References | Supported | | Add Joins | Supported | | Drop Joins | Supported | | Add Calculation | Supported, replays based on `Position` | ### Transaction Layer The transaction layer provides MVCC interactive transactions for database operations in the system. It allows for strictly serializable transaction isolation levels for the strongest consistency guarantees. The interface to the transaction layer is provided via transaction scopes. Transaction scopes provide an interface to the transaction layer and a clear mental model for determining how transactions for specific data should interact. --- # Local Environment The local environment helps streamline development and CICD integration testing with Twisp running on your own machine. ## Starting the environment The following shell commands can be used to get up and running: ```sh docker pull public.ecr.aws/twisp/local:latest docker run -p 3000:3000 -p 8080:8080 -p 8081:8081 public.ecr.aws/twisp/local:latest ``` You should see the startup logs in your terminal. The console should now be available in the browser at . ## Work through the Twisp tutorial After starting up your local environment check out the Twisp [tutorial](https://www.twisp.com/docs/tutorials/twisp-101.md) for more information on using Twisp. ## Environment details The local Twisp container image includes a number of services and data persistence options. Container images are available for both `amd64` and `arm64` architectures. ### Available services - HTTP console port `3000` - HTTP API port `8080` - GRPC API port `8081` - Health check command `/healthcheck` > Note: the Twisp Local Instance does not require HTTP `Authorization` headers for access. ### Data persistence - Set `DB_PATH` environment variable to `-e DB_PATH=/data` - Mount a volume: `-v volume:/data` ### Account management - Execute multi-tenant API calls by posting with a specific `X-Twisp-Account-Id` HTTP header - If an `X-Twisp-Account-Id` header is not provided, the system will default to `X-Twisp-Account-Id: 000000000000` - The console invokes the API on behalf of the default account --- # Security and Auth Security policies control access to encrypted resources. Ensuring the proper security of data is foundational to the Twisp infrastructure. Out of the box, data is [securely encrypted both at rest and in transit](https://www.twisp.com/docs/infrastructure/security-and-auth.md#encryption) and all access is authenticated via JWT tokens issued by the [OpenID Connect 1.0 protocol](https://openid.net/connect/). Users can configure additional access settings by defining [tenants](https://www.twisp.com/docs/infrastructure/security-and-auth.md#provisioning-tenants) and [clients](https://www.twisp.com/docs/infrastructure/security-and-auth.md#creating-clients-and-policies) through GraphQL mutations to set precise permissions on specific operations. ## The authentication & authorization flow Twisp applies policies to an authenticated principal before allowing access to resources. In a simplified form, the stages for the authentication process are: 1. A principal issues an HTTPS request with their OIDC JWT and Twisp account IDs in the headers. 2. The principal name is extracted from the JWT. 3. Twisp finds the corresponding policies to apply using the principal name. 4. The resulting policies are evaluated for authorization. ### Making an authenticated request All HTTPS requests to the [GraphQL API](https://www.twisp.com/docs/reference/graphql.md) must provide the following headers to allow for authorization: ``` Authorization: Bearer x-twisp-account-id: ``` The JWT can be issued either with OpenID Connect or AWS IAM. ### Authorizing a principal Within a client configuration, the **security principal** is the authenticated identity accessing the system. The client authorization system retrieves [policies](https://www.twisp.com/docs/infrastructure/security-and-auth.md#creating-clients-and-policies) based on the principal name. For OIDC this is typically the issuer found in the `iss` claim. For AWS IAM it is the IAM role, user, or other AWS identity placed in the `sub` claim when Twisp vends a token. ## Provisioning Tenants Each cloud environment requires its own tenant. Use the admin GraphQL namespace to create tenants from an existing account: ```graphql mutation CreateTenants { admin { staging:createTenant( input: { id: "d9d8f1c0-0299-4d5b-b2b6-85beafdda28b" accountId: "TwispStaging" name: "staging" description: "staging environment for Twisp" } ) { accountId name } production:createTenant( input: { id: "848df974-4133-4ee4-ab45-86e5e29b6822" accountId: "TwispProd" name: "production" description: "production environment for Twisp" } ) { accountId name } } } ``` Once a tenant exists, clients may be created within that tenant to govern access. ## Creating Clients and Policies Authentication to Twisp works with any OIDC-compliant token supplied through the `Authorization: Bearer ` header. A corresponding client must exist in Twisp for the principal identified in the token so Twisp knows which policies to apply. ### Third-party OIDC client example For tokens issued by an external identity provider, set the client `principal` to the OIDC issuer URL (`iss` claim). The following mutation creates per-user policies for tokens issued by Google: ```graphql mutation CreateGoogleCloudClient { auth { createClient( input: { name: "michael gcloud readonly" principal: "https://accounts.google.com" policies: [ { actions: [SELECT] resources: ["*"] effect: ALLOW assertions: { isMike: "context.auth.claims.email == 'michael@twisp.com'" } } { actions: [SELECT, INSERT, UPDATE, DELETE] resources: ["*"] effect: ALLOW assertions: { isJarred: "context.auth.claims.email == 'jarred@twisp.com'" } } ] } ) { principal } } } ``` ### AWS IAM principal example Twisp can exchange a presigned AWS STS `GetCallerIdentity` request for an OIDC token. The resulting token uses the AWS identity as the principal. Create a client for that principal to grant access: ```graphql mutation CreateIAMAuthClient { auth { createClient( input: { principal: "arn:aws:iam::012345678901:role/example-role" name: "example role policy" policies: [ { effect: ALLOW actions: [SELECT, INSERT, UPDATE, DELETE] resources: ["*"] } ] } ) { principal } } } ``` ### Understanding policies Each policy defines an `effect` (`ALLOW` or `DENY`), the `actions` permitted or denied, the `resources` in scope, and optional CEL `assertions` that must evaluate to `true`. > **Logical combination of policies** > > Policies are evaluated as a chain of logical `AND` statements scoped to the requested resource and action. A principal must have *at least one* `ALLOW` policy for a resource/action pair. A single `DENY` policy on that pair blocks the operation. Values for `resources` and `actions` support `*` and `?` wildcards. Actions include: - `db:Select` (`SELECT` in GraphQL) : read a document - `db:Insert` (`INSERT`) : create a document - `db:Update` (`UPDATE`) : change document fields - `db:Delete` (`DELETE`) : remove a document Resource identifiers follow the format `namespace.ledger..propertyName`. The current namespace is `financial`. Assertions use [Common Expression Language (CEL)](https://www.twisp.com/docs/reference/cel.md) and can access `context.auth.claims` (token claims) and `context.document` (the document being acted on). If any assertion evaluates to `false`, the policy is skipped. ## Using OpenID Connect tokens Any JWT generated by an OpenID Connect 1.0 capable issuer is supported by Twisp for authentication. When an API endpoint receives the JWT, it validates the token signature against the issuer and—if valid—invokes the endpoint with a security principal set to the issuer `iss` claim. All claims are embedded in `context.auth.claims` for policy evaluation. ## Issuing tokens with AWS Identity and Access Management (IAM) Twisp can vend an OpenID Connect token in exchange for an authenticated AWS IAM role or user. This allows services running in AWS to retrieve a Twisp-issued token for access. The issuer of these tokens is `https://auth.${AWS::Region}.prod.twisp.com/token/iam`, and the resulting principal name equals the original IAM identity stored in the token `sub` field. Twisp also provides a hosted service to exchange a presigned `GetCallerIdentity` request for a token, making it convenient to obtain OIDC tokens inside AWS environments. ## Cloud endpoints - AWS Twisp Token: `https://auth.us-east-1.cloud.twisp.com/token/iam` - Financial GraphQL: `https://api.us-east-1.cloud.twisp.com/financial/v1/graphql` - gRPC: `https://api.us-east-1.cloud.twisp.com:50051` ## Encryption ### Encryption in transit Data is encrypted in transit via HTTPS connections for all external and internal API operations. This protects against man-in-the-middle attacks and prevents eavesdropping on Twisp traffic. ### Encryption at rest All data stored in Twisp is encrypted at rest. Encryption keys are stored in [AWS Key Management Service](https://aws.amazon.com/kms/), so even if a malicious actor gains access to the storage layer they cannot read the data without the proper keys. --- # System Architecture Twisp is a purpose-built system that provides a mission-critical, high performance, core ledger for constructing and running financial products, empowering organizations to have complete control over their crucial financial data. ## Integrating with Twisp Integrating with the Twisp Accounting Core streamlines financial data flows, structures ledger data, automates financial processes, and helps you design and build seamless financial products. In this document, we will discuss how to integrate your system with Twisp, covering common components in your stack and the interfaces available to connect those components to the Twisp Accounting Core. [Architecture diagram available in the HTML documentation.] ## Your System Your system is the foundation for your integration with Twisp. It includes the frontend, infrastructure, services, and vendors that you interact with. ### Frontend Your frontend is the user interface that your customers use to interact with your system. By combining fine grained security policies with OIDC authentication, it's possible to use our GraphQL API directly from frontend applications. This provides a scalable and performant mechanism to enable your customers to view and interact with their financial data stored in Twisp's ledger database. ### Infrastructure Your infrastructure is the backbone of your system. It includes servers, streaming platforms, databases, warehouses, and networking components. These lower-level technology components often require access direct access to financial data stored in the Twisp Ledger Database. Real-time streaming and batch access to this data is provided by Twisp data connectors. ### Services Your services are self-contained, loosely coupled software components that provide specific functionality to power your business. To integrate with Twisp, you'll need to identify the specific financial services that these systems provide, such as brokerage, checking, savings, credit, or loan products. The logic for that product is then setup in the Twisp Accounting Core, and services then integrate directly via Twisp API interfaces with GraphQL, gRPC, or REST for data exchange. ### Vendors Your vendors are the third-party tools and services that you use to support your system. Twisp has an expanding library of pre-built vendor and protocol integrations that are connected directly to the Accounting Core. This tooling allows simple plug-and-play integration of your Accounting Core with existing investments. ## Interfaces Interfaces are the methods by which your system interacts with Twisp. They include the API, data, integrations, and protocols that enable communication between your system and Twisp's accounting engine and ledger database. ### API Twisp includes a number of modern API interfaces, allowing your teams to integrate with technologies best suited to your organization. | Technology | Description | |---|---| | GraphQL | GraphQL is an open-source query language for APIs (Application Programming Interfaces) and a runtime for executing those queries with existing data. It was developed by Facebook and released in 2015. GraphQL enables clients to request specific data from a server, allowing them to retrieve only the information they need and nothing more. | | gRPC | gRPC (Google Remote Procedure Call) is an open-source high-performance framework developed by Google. It allows you to define services and message types using Protocol Buffers (protobuf) and enables efficient communication between distributed systems. | | REST | REST (Representational State Transfer) is an architectural style for designing networked applications. It provides a set of principles and constraints for building web services that are scalable, stateless, and interoperable. Additionally, the OData protocol allows Twisp to easily integrate with services such as Salesforce out of the box. | ### Data Twisp provides various data integration interfaces to facilitate the seamless processing and transfer of data. These interfaces cater to both real-time and batch scenarios, allowing you to effectively work with data in Twisp. #### Real-Time Data Integration Real-time data integration involves the continuous or near-instantaneous processing and transfer of data between systems as it becomes available. Twisp supports the following real-time data interfaces: * **Streaming**: Streams provide near real-time change data capture capabilities, allowing you to stream data to various technologies such as AWS Kinesis, Kafka, or webhooks. This ensures that you can stay up-to-date with the latest changes in your data. * **Webhooks**: Twisp supports webhooks for real-time HTTPS data integration. You can configure webhooks to receive instant notifications or data updates from external systems, enabling seamless communication and data synchronization. #### Batch Data Integration Batch data integrations involve processing and transferring data in bulk at scheduled intervals or on-demand. Twisp supports the following batch data interfaces: * **SQL**: The SQL interface enables you to perform ad-hoc queries for generating reports, exporting data, or integrating with popular business intelligence tools like Tableau or PowerBI. * **Warehousing**: Twisp aggregates all committed financial data in near real-time and stores it in the industry-standard [Parquet](https://parquet.apache.org) file format. This format is compatible with leading data warehousing systems like Redshift, Snowflake, or BigQuery. You can easily ingest this data into your preferred data warehousing system for further analysis and reporting. By leveraging these data integration interfaces in Twisp, you can effectively manage your data, whether it's processing real-time updates or performing bulk transfers at your convenience. ### Integrations Integrations are the connections between the Accounting Core and other financial systems and business tools. Twisp includes an expanding number of plug-and-play integrations. If an integration you are interested in is not included in this list, please reach out and we can prioritize development. | Vendor | Description | |---------|-----------------------------------------------------------------------| | Fiserv | Twisp can integrate with a number of Fiserv core systems. | | Lithic | Twisp natively processes Lithic transaction webhooks, including ASA. | | Marqeta | Twisp natively processes Marqeta transaction webhooks, including JIT. | | Stripe | Twisp natively processes Stripe Issuing and Treasury webhooks. | ### Protocols Protocols are industry-standard rules and formats that govern structured communication between financial systems. Our platform includes plug-and-play protocol adapters that process, store, and generate data while adhering to strict security and compliance standards. If you're interested in a protocol not listed below, please contact us, and we will prioritize its development. | Protocol | Description | | --- | --- | | ISO 8583 | Seamlessly integrate with several card networks, directly consuming ISO 8583 messages and converting them into transaction entries within our platform. | | ISO 20022 | Process ISO 20022 messages, encompassing Swift and FedNOW transactions. Additionally, generate ISO 20022 messages from ledger data. | | NACHA | Proess and generate NACHA ACH files. | | X9 | X9 Cash image files serve as the standard method for check clearance in the US. Twisp can efficiently consume X9 files and track check transactions in their native format. | | BAI2 | Comprehensive support for financial institutions generating or processing BAI2 files. | ## Core The Twisp Accounting Core is the heart of your integration with Twisp. It includes the accounting engine, ledger database, product logic, and security policies that enable secure, reliable, and compliant financial operations. ### Accounting Engine The accounting engine is the core of the Twisp Accounting Core. It includes the chart of accounts, transaction codes, and ledger entries that enable processing and accounting for financial transactions. ### Ledger Database The ledger database is where your financial data is stored within Twisp. Twisp is built on a production hardened, high performance, horizontally scalable database storage system: [AWS DynamoDB](https://aws.amazon.com/dynamodb). The ledger database leverages DynamoDB using a formally verified transaction layer, which provides transactionally consistent, snapshot isolated, immutable storage, providing a complete historical lineage of any object. ### Product Logic Product logic is the essential business logic that powers your financial products and services. With Twisp, you have complete control over product development in the Accounting Core, allowing you to design and create a wide range of financial products. Twisp also offers pre-built financial product definitions that can be customized or extended according to your specific requirements. | Product | Description | | --- | --- | | Brokerage | Brokerages have unique accounting needs, including the representation of numerous positions within a single account. Twisp enables you to execute the trading lifecycle and reconcile trades with a clearinghouse, all while operating at high scale during market hours. | | Cards | Simplify your card issuing program by leveraging Twisp's card issuing workflows and transaction codes. Twisp integrates seamlessly with issuer processors, providing robust accounting control over the cards you issue. You can enable features such as spend limits, wallets, and organizational structures tailored to your needs. | | Credit | Credit card programs require specific functionalities beyond debit cards. Twisp's basic card program can be enhanced with SCRA processes, available credit balance tracking, and customizable interest rate computations. | | Deposits | Twisp supports various deposit-taking modalities. Whether you offer a card product on top of FBO (For Benefit Of) accounts or provide demand deposit accounts (DDAs), Twisp's deposit program can be tailored to suit your requirements. | | Lending | Enhance your origination and servicing capabilities by incorporating Twisp's lending components. Account for payments, generate or regenerate amortization tables, and enable portfolio monitoring directly from the accounting core. | | Mortgages | Manage mortgage accounting processes for origination and servicing directly from the Accounting Core. | | Payments | Twisp's multi-currency design enables you to manage global payments accounting with FX (Foreign eXchange.) | | Wallets | Simplify the management of your organization's spend program with Twisp's powerful wallet management. Configure your organizational hierarchy, assign spend limits, and Twisp's wallet program will effectively manage spending. If you choose to issue cards associated with wallets, Twisp seamlessly integrates with various card module partners to provide the necessary capabilities. | ### Security Policies Security policies are the rules and procedures that ensure the security and compliance of your system and financial data. Twisp supports unlimited fine grained access control policies that authorize all operations executed against the accounting core. Additionally, Twisp is a credential-less system, leveraging your existing identity or cloud providers identities and roles for all authentication. --- # Transaction Isolation and Consistency Twisp provides snapshot isolation and strong consistency to ensure correctness and prevent anomalies in ledger systems. ## Transaction Isolation in Twisp In systems that demand correctness and high concurrency, transaction isolation plays a critical role in ensuring consistency and preventing anomalies. Twisp leverages an ACID **Multi-Version Concurrency Control (MVCC)** transaction layer to implement robust isolation levels tailored to the needs of ledger systems. Twisp's MVCC provides: - **Interactive transactions**: Supports all-or-nothing semantics for transactions, ensuring atomicity and isolation. - **Snapshot Isolation**: Transactions operate on a consistent and isolated view of data, ensuring transactions operate on immutable data snapshots. - **Anomaly prevention**: Guarantees that transactional operations produce fully consistent results without partial or invalid reads along with write skew prevention. - **Strong consistency**: All committed balances and database states are always readable in a strongly consistent manner, ensuring no anomalies or partial updates. > **Note:** > > Twisp runs and maintains a formal model of our MVCC transaction layer. Learn more about the formal modelling process we conducted with our partner [Galois](https://galois.com/blog/2024/02/galois-twisp-avoiding-foolishness-in-distributed-systems/). ### Isolation Levels Overview Twisp supports four distinct transaction isolation levels, balancing between performance and correctness: 1. **Read Committed**: - Ensures that the most recently committed data is visible to a transaction. - Use cases: Workflows requiring real-time, consistent reads that can tolerate a non-snapshot view of data. 2. **Snapshot Isolation (System Default)**: - Guarantees that transactions operate on a point-in-time consistent snapshot of the data. - Use cases: Workflows requiring strong correctness without paying a performance penalty. 3. **Repeatable Read**: - Guarantees that read data has not changed on transaction commit providing read stability. - Use cases: High-consistency workflows for complex, interactive, multi-row updates. 4. **Serializable**: - In addition to read stability, also provides phantom avoidance that ensures scans would not return additional data on transaction commit. - Use cases: Workflows where absolute correctness outweighs performance concerns. > **Note:** > > Transaction isolation settings can be set with the [`@tx` directive](https://www.twisp.com/docs/reference/graphql/directives.md#tx) in GraphQL. ### Preventing Transaction Anomalies In high-concurrency environments, transaction anomalies can corrupt data or lead to business-critical errors. Twisp's isolation levels guard against common database anomalies. The following anomalies are thoroughly tested in every build via our conformance test suites: - **G0**: Write Cycles (dirty writes) - **G1a**: Aborted Reads (dirty reads, cascaded aborts) - **G1b**: Intermediate Reads (dirty reads) - **G1c**: Circular Information Flow (dirty reads) - **OTV**: Observed Transaction Vanishes - **PMP**: Predicate-Many-Preceders - **P4**: Lost Update - **G-single**: Single Anti-dependency Cycles (read skew) - **G2-item**: Item Anti-dependency Cycles (write skew on disjoint read) - **G2**: Anti-Dependency Cycles (write skew on predicate read) By addressing these anomalies, Twisp's isolation levels ensure correctness and make ledger operations easy to reason about, even under high concurrency. ## Strongly Consistent for All Operations Twisp ensures **strong consistency** across all database operations, guaranteeing that every transaction reflects an accurate state of the ledger. This robust consistency model removes ambiguity, preventing the risks of stale or partial reads. > **Note:** > > Customer defined __search__ index documents are replicated in an eventually consistent manner to [OpenSearch](https://opensearch.org/). ### Balance Calculations For high-volume, concurrent entry posting to concurrent-enabled accounts or aggregates, Twisp implements a non-blocking, transactionally consistent, and deterministic balance update process. The following balance types are provided to allow context-specific balance retrieval consistency without compromising correctness. | **State** | **Description** | **Use Case** | |-----------|-----------------|--------------| | **Provisional** | Includes in-flight, uncommitted transactions along with committed changes. | Enforce velocity controls on to-be-committed transactions. | | **Prepared** | Combines finalized balances with committed, but not-yet-finalized entries. Guaranteed to provide the exact same results as the final balance. | Systems requiring transactionally consistent balances on concurrent-enabled accounts. | | **Final** | Reflects the fully committed and finalized state of the account. Finalized balances are cached. | Systems that require the highest performance balance reads and can tolerate slightly stale balance values. | > **Note:** > > For concurrent-disabled accounts, the `Prepared` balance state is skipped and the balance update process proceeds directly to `Final` from `Provisional`. --- # A Short History of Twisp Why we did it. Monolithic relational databases are the traditional foundation of financial core ledger systems. Nevertheless, the process of building and operating mission-critical financial ledgers on these databases is a journey fraught with engineering challenges. In addition, the databases underlying these systems are often difficult to operate at scale and a poor choice for multi-tenant systems. Distributed database technologies have proven themselves to solve these operational issues. However, **the design choices that allow these systems to achieve horizontal scale make them an even more complicated fit for financial ledgers.** At Twisp, we set out to rethink the underlying technology for financial ledger systems by combining the operational and scaling characteristics of a distributed database with the correctness guarantees offered by relational databases. The result is the Twisp Financial Ledger Database (FLDB). The Twisp FLDB is a transactionally guaranteed, secure, and highly scalable financial ledger database built for financial system use cases. It is designed to easily support massively multi-tenant workloads. Additionally, we set out to fully leverage the modern cloud. With the advent of serverless and infrastructure-as-code, the ability to define fully functioning systems with high-level code is now a reality. Twisp systems are repeatable, autoscaling, fully managed, pay-per-use, and a boon to developer productivity. > **Tip:** > > Read more about the infrastructure model in the [Infrastructure Docs](https://www.twisp.com/docs/infrastructure.md). At its heart, Twisp is an infrastructure company. We built the FLDB because we wanted it to exist in the world. As we released this database into the wild, we found out that many companies didn't just need a ledger database—they needed an [accounting core](https://www.twisp.com/docs/accounting-core.md). Lots of companies have re-invented a ledger and a system for accounting. Many of these systems suffer from problems of scale, complexity, and a faulty data model which can make seemingly simple tasks like calculating balances into confusing, costly chores. This is why you [shouldn't build your own ledger](https://www.twisp.com/docs/introduction/no-diy-ledger.md). --- # Introduction What is Twisp? Twisp is an accounting engine for building financial products. We provide a financial ledger database with financial primitives for engineering systems that deal with money. ... and what is a Financial Ledger? Financial ledgers are the foundation of systems that track money. Some examples: - A neobank tracks debits and credits across many accounts, with multiple financial instruments, all the while analyzing this data to surface financial insights to consumers. - A payments infrastructure company needs robust multi-tenant account servicing built on many FBOs, safely made available to many clients via API. Financial ledgers and the data derived from them are the engine of all such systems. Developing, securing, operating, and scaling these financial ledgers on a general purpose database is a difficult and time consuming proposition... which is why we built Twisp. Upon provisioning a **Twisp core ledger**, you gain access to the following features: 1. **Transaction Ledger**: Facilitates double-entry accounting, ensuring comprehensive and accurate financial recording. 2. **Journals**: Enables tracking of different currencies and monitoring activities over specific time periods. 3. **Accounts**: Represents a chart of accounts for tracking any economic activity, offering a structured view of your financial data. 4. **Transaction Codes**: Provides a method for categorizing and tracking various transaction types within the system, enhancing transactional clarity. 5. **Layered Balances**: Maintains `available`, `settled`, and `encumbrance` balances, enabling precise and timely balance reporting. This includes: - Account Hierarchies and Roll-ups: Streamlines account management and analysis. - Dimensional Balance Tracking: Provides multi-dimensional views of your financial status. - Velocity Balances: Tracks account balance changes over time. 6. **Workflows**: Automates common processes for increased efficiency, including: - Card Authorization & Settlements: Manages the lifecycle of card transactions. - ACH Transaction Processing: Handles the flow of Automated Clearing House transactions. - mRDC Check Deposits: Facilitates mobile Remote Deposit Capture operations. 7. **High-Scale API**: Serves end customers directly with a robust API, which includes: - Activity Feed: Offers real-time updates on account activities. - Enrichment: Enhances transaction data for a more detailed understanding. - Reconciliation: Matches transactions back to the ledger to ensure accuracy and eliminate customer confusion. --- # DIY Ledgers are a Problem We built a ledger database and accounting engine (so that you don't have to). The Twisp founding team has spent a cumulative total of many years working on and with financial software systems for neobanks and other fintech companies. In that time, we've learned a few things: 1. Designing, building, and managing a central accounting ledger is hard. 2. But it can appear simple, so many startups (and mature companies) decide to build their ledger in-house. 3. Most of the time, this does not go well. Accounting systems often suffer from the full range of technical debt problems common to long-lived software: data model lock-in, performance bottlenecks due to poor scaling properties, esoteric design features and programming patterns unique to one or two instrumental early engineers, etc. However, these problems be especially painful and frustrating when they apply to your internal ledger because it is both a _vital part of the business_ and also (most likely) _not a core competency_. Our friend [Matt Brown](https://www.matttbrown.com/) from Matrix Partners calls this ["undifferentiated heavy lifting"](https://www.matttbrown.com/notes/undifferentiated-heavy-lifting), and it is particularly applicable to the ledger problem: > Ledgers are a critical part of the tech and financial stack. Fundamentally, they're databases that contain debits and credits and bake in business-specific assumptions and logic to derive balances and other financial statements. However, they're also incredibly complex, requiring high uptime and performance with strict security, data integrity, audit, and regulatory requirements. Multiple business processes are built on them, so failures not only degrade UX but can cost significant sums of money. The other thing we learned is that ledgers are (mostly) a solved problem. Double-entry accounting has been around for centuries. The algorithms and architectures needed to support highly availalble, reliable, and performant ledger-type data stores have been honed for decades. There is no need to re-invent the wheel. At Twisp, we offer a simple solution to the design problem of ledgers: don't build it yourself. All of the fantastic new fintech products will need a ledger, but building internal ledgers is no longer a worthwhile investment. --- # ACH Processor Understanding how the Twisp ACH processor works The Twisp ACH processor handles the complete lifecycle of ACH (Automated Clearing House) transactions, from origination through settlement or return. This explanation clarifies how the processor works, why it's designed this way, and how the components interact. ## What is the ACH Processor? The ACH processor is a financial infrastructure component that enables you to send and receive electronic payments through the ACH network. Unlike building custom ACH handling from scratch, the Twisp ACH processor provides production-ready workflows, file generation, webhook decisioning, and ledger integration out of the box. The processor handles two fundamental roles: - **ODFI (Originating Depository Financial Institution)**: Originate ACH transactions - **RDFI (Receiving Depository Financial Institution)**: Receive ACH transactions ## Why ACH Processing is Complex ACH might seem straightforward - just move money between accounts - but production ACH processing involves substantial complexity: **File Format Compliance:** The NACHA file format requires precise 94-character fixed-width records with specific header, batch, entry detail, and control structures. Format errors cause entire files to be rejected. **Asynchronous Settlement:** Unlike real-time payments, ACH transactions take 1-3 business days to settle. Your system must track pending, encumbered, and settled states across this timeline. **Return Handling:** Transactions can return days or even weeks after origination (up to 60 days for unauthorized returns). Returns must match back to original transactions and reverse accounting entries correctly. **Multi-Layer Balance Management:** Customer available balances must reflect encumbered funds (reserved but not sent), pending funds (sent but not confirmed), and settled funds (finalized). This prevents overdrafts and double-spending. **Webhook Decisioning:** For RDFI operations, incoming transactions require real-time business logic decisions: accept, reject, or hold for review. These decisions must execute quickly within processing timeframes. **Regulatory Compliance:** NACHA rules govern transaction types, authorization requirements, return timeframes, and file transmission security. Non-compliance risks ODFI relationship termination. The Twisp ACH processor abstracts this complexity into workflows, file operations, and ledger integration. ## Processor Architecture ### Configuration Layer ACH configuration connects all operational components: - Settlement account for fund transit - Suspense account for unidentifiable transactions - Exception account for business rule violations - Fee account for processing fees - Webhook endpoint for transaction decisioning - ODFI header information for file generation - Timezone for date/time interpretation Configuration versioning ensures immutability - files reference specific configuration versions, allowing historical analysis even after configuration updates. ### File Operations Layer File operations manage NACHA file lifecycle: **Upload:** Presigned URLs provide secure, time-limited file upload capability without storing long-lived credentials. Files upload directly to S3-compatible storage. **Processing:** File parsing validates NACHA format, extracts batch and entry details, and partitions entries for parallel processing. Each entry triggers webhook decisioning. **Generation:** File generation queries submitted transactions, groups by batch parameters, and produces NACHA-compliant files with proper control totals and trace numbers. **Download:** Presigned download URLs provide time-limited file access for transmission to financial institutions. Processing status tracking (NEW → VALIDATING → PROCESSING → COMPLETED) provides visibility into file lifecycle with statistics on entry counts and monetary totals. ### Workflow Layer Workflows orchestrate transaction state transitions and ledger accounting: **PUSH Workflow (Credits):** Sends money to receivers. CREATE encumbers customer funds, SUBMIT settles and includes in file generation. Returns reverse settled entries. **PULL Workflow (Debits):** Collects money from receivers. CREATE encumbers settlement funds, SUBMIT moves to pending, SETTLE finalizes after confirmation. The pending layer prevents premature fund availability before debit confirmation. **State Transitions:** Each workflow state transition creates ledger transactions across appropriate balance layers (encumbrance, pending, settled). This ensures double-entry accounting consistency throughout transaction lifecycle. **Execution Tracking:** Every workflow execution receives a unique ID. The execution record contains input parameters, output state, all created ledger transactions, and ACH workflow traces linking to file entries via trace numbers. ### Webhook Decisioning Layer Webhooks enable custom business logic during file processing: **Request Format:** Webhooks receive POST requests with transaction details: amount, account identifiers, entry metadata, trace number, effective date. **Response Format:** Webhook responses specify settlement instructions: which account to credit/debit, or route to suspense/exception accounts. **Timeout Handling:** Webhook timeframes are strict - typically seconds. Timeouts result in transaction failures requiring manual intervention. **Decision Routing:** - Settlement account: Normal processing, funds go to identified customer account - Suspense account: Unknown account, manual review required - Exception account: Business rule violation, transaction returned Webhook decisioning decouples payment acceptance logic from processor internals, enabling custom risk management, account validation, and compliance checking. ### Balance Layer Management Balance layers track transaction lifecycle states: **Encumbrance Layer:** Holds funds during CREATE state before transmission. Encumbered funds don't affect available balance but prevent overdrafts when transactions later settle. **Pending Layer:** PULL workflows use pending between SUBMIT and SETTLE. Pending represents funds transmitted but awaiting confirmation. Crucial for preventing premature fund availability. **Settled Layer:** Final layer for completed transactions. All accounting eventually posts to settled layer. Fees post directly to settled layer immediately. **Available Balance:** Calculated as: Settled - Pending - Encumbrance. This formula ensures customers can't spend funds that are encumbered or pending confirmation. Layer transitions via workflow states maintain double-entry balance throughout transaction lifecycle. ## Why This Design? **Separation of Concerns:** File operations, workflows, ledger accounting, and decisioning are independent components. This enables testing, scaling, and modifying each component separately. **Immutability:** Configuration versions, workflow executions, and file processing records are immutable. This provides complete audit trails required for financial operations and regulatory compliance. **Idempotency:** Workflow executions use correlation IDs. File processing matches returns via trace numbers. These identifiers enable safe retry logic and prevent duplicate processing. **Parallel Processing:** File processing partitions entries across workers. Concurrent posting to accounts (enableConcurrentPosting) allows high-throughput processing while maintaining consistency. **Webhook Flexibility:** Business logic lives in your webhook, not in the processor. This allows rapid iteration on risk rules, account validation, and compliance checks without processor changes. **Multi-Tenancy:** Configurations are tenant-scoped. Multiple organizations can operate independently on shared infrastructure with complete isolation. ## Workflow State Machines Understanding state machines clarifies transaction lifecycle: ### PUSH State Machine ``` CREATE → SUBMIT → [completed] ↓ CANCEL → REIMBURSE_FEE → [completed] SUBMIT → RETURN → REIMBURSE_FEE → [completed] ``` Credits are final upon transmission, so SUBMIT immediately settles. Returns reverse after the fact. ### PULL State Machine ``` CREATE → SUBMIT → SETTLE → [completed] ↓ CANCEL → REIMBURSE_FEE → [completed] SUBMIT → RETURN → REIMBURSE_FEE → [completed] ``` Debits require confirmation, so SETTLE finalizes after SUBMIT. Returns can occur before or after SETTLE. State machines ensure transactions follow valid progressions. Attempting invalid transitions (e.g., SUBMIT after CANCEL) fails with clear error messages. ## Return Processing Flow Returns demonstrate processor coordination: 1. **Receipt:** Return file uploads via presigned URL 2. **Parsing:** File processing extracts return entries with return codes and trace numbers 3. **Matching:** System queries original transactions via trace number indexes 4. **Execution:** RETURN workflow state executes for each matched transaction 5. **Reversal:** Ledger entries reverse from appropriate balance layer 6. **Audit:** Complete return processing history records in file and workflow execution history Automatic return processing eliminates manual ledger adjustments and ensures accounting consistency. ## Integration Points The processor integrates with other Twisp components: **Ledger Integration:** Workflows create ledger transactions via tran codes. Balance queries span encumbrance, pending, and settled layers. Concurrent posting enables high-throughput ACH processing. **Event System:** Webhook endpoints route to events system. File processing status changes emit events. Workflow state transitions trigger notifications. **Files Service:** Presigned URLs delegate upload/download to files service. Storage layer handles retention and access policies. **Workflow Engine:** Workflow executions use general workflow engine. ACH workflows are workflow templates with specific parameters and state machines. ## Design Trade-Offs **Complexity vs Flexibility:** Workflows and webhook decisioning add complexity but enable custom business logic without processor modifications. **Consistency vs Performance:** Balance layer management requires multiple ledger transactions per workflow state but ensures consistent accounting throughout transaction lifecycle. **Immutability vs Storage:** Versioning configurations and maintaining execution history increases storage but provides complete audit trails required for financial operations. **Asynchronous Processing vs Latency:** File processing partitions entries for parallel processing, adding complexity but enabling high-throughput processing of large files. These trade-offs prioritize correctness, auditability, and flexibility over simplicity. ## Production Considerations **ODFI Relationships:** You must establish banking relationships for ACH origination. ODFIs require application processes, risk assessment, and reserve accounts. Some ODFIs have minimum volume requirements or restrict certain transaction types. **File Transmission:** You're responsible for secure file transmission (typically SFTP) to/from financial institutions. The processor generates and parses files but doesn't transmit them. **Return Timeframes:** NACHA rules specify return timeframes (typically 2 business days, some codes allow 60 days). Automated return processing helps maintain compliance. **Balance Management:** Properly managing encumbrance, pending, and settled layers prevents overdrafts. Available balance calculations must account for all three layers. **Webhook Performance:** Webhook decisioning must respond within strict timeframes (typically seconds). Slow webhooks cause transaction failures. Consider caching, rate limiting, and fallback strategies. **Reconciliation:** Daily reconciliation validates file processing, ledger balances, and transaction counts. Automated reconciliation with alerting identifies issues quickly. ## Further Reading To learn ACH processing hands-on: - [Setting Up ACH Processing](https://www.twisp.com/docs/tutorials/ach/setting-up-ach.md) - Configuration from scratch - [Your First ACH Payment](https://www.twisp.com/docs/tutorials/ach/first-ach-payment.md) - Send a payment end-to-end For production operations: - [Processing ACH Payments](https://www.twisp.com/docs/guides/processing-ach-payments.md) - Daily workflows - [Handling ACH Returns](https://www.twisp.com/docs/guides/handling-ach-returns.md) - Return management - [Reconciling ACH Files](https://www.twisp.com/docs/guides/reconciling-ach-files.md) - Daily reconciliation For technical details: - [Configuration Reference](https://www.twisp.com/docs/reference/ach/configuration.md) - Configuration components - [File Operations Reference](https://www.twisp.com/docs/reference/ach/file-operations.md) - File lifecycle - [ODFI Reference](https://www.twisp.com/docs/reference/ach/odfi.md) - Originating transactions - [RDFI Reference](https://www.twisp.com/docs/reference/ach/rdfi.md) - Receiving transactions --- # Processors Processing financial protocols and payment networks Processors are specialized components that handle communication with external payment networks and financial protocols. They bridge the Twisp accounting core with the outside world of financial transactions, enabling seamless integration with payment rails like ACH, wire transfers, and card networks. ## What are Processors? Processors in Twisp handle the complexities of external payment protocols, including: - **Data Format Management**: Converting between Twisp's internal formats and protocol-specific formats (e.g., NACHA for ACH) - **Automated Accounting**: Ledgering, monitoring, and reconciling the lifecycle of payments through various stages (pending, settled, returned) - **Error Handling**: Managing exceptions, returns, and corrections according to protocol rules ## Available Processors Twisp currently supports the following processors: - [**ACH**](https://www.twisp.com/docs/processors/ach.md)\ Process ACH (Automated Clearing House) transactions including debits, credits, returns, and notifications of change for both ODFI and RDFI operators. ## Architecture Processors work in conjunction with the Twisp accounting core to provide end-to-end payment processing: 1. **Origination**: Transactions are initiated through the accounting core 2. **Processing**: The processor formats and transmits files to the appropriate network 3. **Reconciliation**: Incoming files are parsed and reconciled with ledger transactions 4. **Completion**: Final status updates are reflected in the accounting core Continue reading to learn more about specific processors and their capabilities.