You'll need to recnrd the settlement of payments in your bank account, or external financial system. This section covers this last step of a payment, see [Making Payments] for how to handle the overall worklow of a payment.
Once a payment settles, a transaction is created in the external system.
To record this settlement in your Ledger:
Links represent an external financial instituion like your bank, Stripe or brokerage clearing house. Each Link mirrors every transaction in every account you hold at a financial institution. They help guarantee your ledger has a complete picture of all your financial activity.
There are two types of Links:
For each account in your external financial system, there's a corresponding External Account. External Accounts have a Tx for each transaction in that account.
After you set up a Link, create a Ledger Account in your Ledger linked to each External Account.
To create a Native Link with Stripe:
Every Balance Transaction at Stripe has two amount fields:
To handle this, Stripe Links create two Txs for each Balance Transaction at Stripe: one for the gross amount and the other for the fee. This allows you to account for these amounts independently in your Ledger. The external IDs for these Txs are:
{{stripe_tx_id}}_gross for the gross amount Tx{{stripe_tx_id}}_fee for the fee amount TxFor example, a Stripe Balance Transaction with ID txn_123 will result in two Txs in FRAGMENT with external IDs txn_123_gross and txn_123_fee. You can reconcile both of these in a single reconcileTx call.
To enable Stripe Connect, create a Restricted Access Key (RAK) with the following permissions:
Read for both Permissions and Connect PermissionsRead for both Permissions and Connect PermissionsRead for both Permissions and Connect PermissionsReadWrite. This is used to setup webhooks to FRAGMENT.Once you have the RAK:
Details pageCustom Links allow you to build your own integration between FRAGMENT and any external financial system. Instead of syncing automatically like a Native Link, you sync External Accounts and Txs by calling the API.
Your sync process can periodically enumerate transactions and accounts in your external system or be triggered by webhook.
You can either create a Link in the dashboard or using createCustomLink:
mutation NewCustomLink($name: String!, $ik: SafeString!) {
createCustomLink(name: $name, ik: $ik) {
__typename
... on CreateCustomLinkResult {
link {
id
name
}
isIkReplay
}
... on Error {
code
message
}
}
}{
"name": "JPM Chase",
"ik": "dev-chase"
}If you only have a few accounts, sync them manually as part of bootstrapping your Ledger.
Otherwise, sync accounts as you create them at the external system. Trigger the sync process either periodically, by webhook, or both.
Once you have a set of accounts to sync, call syncCustomAccounts:
mutation CreateBankAccounts(
$link: LinkMatchInput!
$accounts: [CustomAccountInput!]!
) {
syncCustomAccounts(link: $link, accounts: $accounts) {
__typename
... on SyncCustomAccountsResult {
accounts {
id
externalId
name
}
}
... on Error {
code
message
}
}
}{
"link": {
"id": "some-link-id"
},
"accounts": [
{
"externalId": "bank-account-1",
"name": "Operational Account"
},
{
"externalId": "bank-account-2",
"name": "Reserve Account"
}
]
}You should ensure that externalId is a stable and unique identifier for each account, within the scope of its Link. This ensures that syncing is idempotent. externalId is typically set to the ID of the account at the external system.
Calling syncCustomAccounts with a different name for an existing externalId updates the name of the External Account.
Settled payments at external financial systems will create transactions that need to be synced to FRAGMENT.
You may also want to sync and reconcile when your product makes a payment. You should only sync transactions that are settled, not pending or declined.
Once you have a set of transactions to sync, call syncCustomTxs:
mutation SyncTransactions(
$link: LinkMatchInput!
$txs: [CustomTxInput!]!
) {
syncCustomTxs(link: $link, txs: $txs) {
__typename
... on SyncCustomTxsResult {
txs {
id
externalId
amount
date
description
}
}
... on Error {
code
message
}
}
}{
"link": { "id": "some-link-id" },
"txs": [
{
"account": {
"externalId": "bank-account-1"
},
"externalId": "tx-1",
"description": "Processed ACH batch",
"amount": "-100",
"posted": "1968-01-01"
},
{
"account": {
"externalId": "bank-account-2"
},
"externalId": "tx-2",
"description": "Received RTP payment",
"amount": "100",
"posted": "1968-01-01T16:45:00Z"
}
]
}You should ensure that externalId is a stable and unique identifier for each transaction, within the scope of its account. This identifier enforces idempotentency.
This identifier is typically the ID of the transaction at the external system. Use the lowest level transaction ID available, not the ID of a higher level construct, like a payment that may be linked to multiple transactions. You can sync transactions from different accounts in the same API call, but they must all belong to the same Custom Link.
Calling syncCustomTxs with a different description for an existing externalId updates the description on the existing Tx. amount and posted timestamp are immutable and cannot be updated. Instead, you can delete the existing Tx and resync it with the updated fields.
To delete Txs on a Custom Link, call deleteCustomTxs with the Fragment IDs of the Txs you want to delete:
mutation DeleteCustomTxs($txs: [ID!]!) {
deleteCustomTxs(txs: $txs) {
__typename
... on DeleteCustomTxsResult {
txs {
tx {
id
externalId
posted
amount
sequence
isDeleted
deletedAt
}
}
}
... on Error {
code
message
}
}
}You must pass the FRAGMENT ID, not the transaction's External ID used at the external system. The FRAGMENT ID, serves as the idempotency key for this operation. Subsequent calls to deleteCustomTxs with the same FRAGMENT ID, will return a success response.
Once a Tx is deleted, you can call syncCustomTxs with the same externalId and different values. By deleting and resyncing, you can update immutable fields like amount and posted. The newly synced Tx will have a different FRAGMENT ID, to the original Tx. Calling deleteCustomTxs again with the original FRAGMENT ID, will return a success response, but will not delete the newly synced Tx.
Notes:
To represent external accounts in your Ledger, create a Ledger Account and link it to an External Account.
Any Ledger Line that posts to a linked Ledger Account must match a synced transaction in its corresponding External Account.
To link a Ledger Account to an External Account, set linkedAccount on a Ledger Account in your Schema:
{
"chartOfAccounts": {
"accounts": [
{
"key": "assets-root",
"name": "Assets",
"type": "asset",
"children": [{
"key": "operating",
"name": "Operating Bank",
"linkedAccount": {
"linkId": "some-link-id",
"externalId": "bank-account-1"
}
}]
}
]
}
}The linkId comes from the Link you create in the dashboard. The externalId is the ID of the account at your external financial system.
The CLI automatically replaces variables with ${ENV_VAR} syntax when running fragment store-schema. This lets you use different External Accounts per environment:
{
"chartOfAccounts": {
"accounts": [
{
"key": "assets-root",
"name": "Assets",
"type": "asset",
"children": [{
"key": "operating",
"name": "Operating Bank",
"linkedAccount": {
"linkId": "${BANK_LINK_ID}",
"externalId": "${BANK_ACCOUNT_ID}"
}
}]
}
]
}
}To link a Ledger Account template to an External Account, parameterize the linkedAccount field:
{
"chartOfAccounts": {
"accounts": [
{
"key": "assets-root",
"name": "Assets",
"type": "asset",
"children": [{
"key": "operating",
"name": "Operating Bank",
"template": true,
"linkedAccount": {
"linkId": "{{BANK_LINK_ID}}",
"externalId": "{{BANK_ACCOUNT_ID}}"
}
}]
}
]
}
}These parameters will be required when posting a Ledger Entry to a linked Ledger Account. This can be useful if you're creating bank accounts per customer, for example.
To reconcile a transaction from an external system, follow the same two-step process as when posting Ledger Entries:
Ledger Entries posting to a linked Ledger Account must specify the Tx from the External Account tied to the Ledger Account. This lets FRAGMENT know which transaction to reconcile:
{
"key": "schema-key",
"chartOfAccounts": {...},
"ledgerEntries": {
"types": [
{
"type": "user_funds_account_via_link",
"description": "Funding {{user_id}} for {{funding_amount}}",
"lines": [
{
"account": { "path": "assets/operating" },
"key": "funds_arrive_in_bank",
"tx": {
"externalId": "{{bank_transaction_id}}"
}
},
{
"account": { "path": "liabilities/users:{{user_id}}/available" },
"key": "increase_user_balance"
}
]
}
]
}
}Notes:
bank_transaction_id represents the ID of the transaction at the external system.Instead of calling addLedgerEntry, use the reconcileTx mutation:
mutation ReconcileTx(
$entry: LedgerEntryInput!
) {
reconcileTx(
entry: $entry
) {
... on ReconcileTxResult {
entry {
type
created
posted
}
lines {
amount
key
description
account {
path
}
}
}
... on Error {
code
message
}
}
}The parameters look similar to addLedgerEntry. Specify the Ledger Entry type that you are using and provide the parameters defined in the Schema:
{
"entry": {
"type": "user_funding",
"ledger": {
"ik": "quickstart-ledger"
},
"parameters": {
"txId": "tx_12345",
"customerId": "customer-1"
}
}
}ik and posted are optional when posting Ledger Entries with reconcileTx:
ik: the Tx.externalId is used to ensure that reconciling a transaction is idempotentposted: the timestamp of the Ledger Entry is taken from the Tx to ensure the Ledger mirrors the external systemBook transfers are a common type of money movement which produce two Txs at your bank as part of one payment.
To reconcile multiple Txs using reconcileTx:
{
"key": "schema-key",
"chartOfAccounts": {...},
"ledgerEntries": {
"types": [
{
"type": "user_funds_account_via_link",
"description": "Funding {{user_id}} for {{funding_amount}}",
"lines": [
{
"key": "funds_arrive_in_operating_bank",
"account": { "path": "assets/operating-bank-account" },
"tx": {
"externalId": "{{bank_transaction_id}}"
}
},
{
"key": "funds_leave_holding_bank",
"account": { "path": "assets/holding-bank-account" },
"tx": {
"externalId": "{{bank_transaction_id}}"
}
}
]
}
]
}
}Notes:
posted timestamp.Transactions synced to FRAGMENT but not reconciled to a Ledger are considered unreconciled.
You can query unreconciled transactions in a linked Ledger Account using the unreconciledTxs field on theLedgerAccount.
query GetUnreconciledTxs (
$ledgerAccount: LedgerAccountMatchInput!
) {
ledgerAccount(ledgerAccount: $ledgerAccount) {
id
unreconciledTxs {
nodes {
id
description
amount
externalId
externalAccountId
}
pageInfo {
endCursor
hasNextPage
}
}
}
}The variables specify the path of the linked Ledger Account you are querying and the Ledger it belongs to.
{
"ledgerAccount": {
"path": "assets/operating",
"ledger": {...}
}
}unreconciledTxs is eventually consistent, so if you've recently transacted at a Native Link or just synced manually for a Custom Link, you may not see the transaction in the query results immediately.