Banking
Deliver account transactions into the ERP and send payments out.
A banking app delivers account transactions into the ERP and optionally sends payments out. PayPal and file-based banking (CAMT/PAIN) are the reference implementations.
Registering the bank backend
const bankBackend = await methods.createBankBackend('PayPal', 'APP');
await methods.changeBankBackend(bankBackend.id, {
...bankBackend,
description: 'Manages PayPal transactions, refunds and open-item matching.',
});Two calls, because createBankBackend has no description parameter. changeBankBackend is a full PUT — spread the created object so label and version survive.
You register a backend — the customer creates the bank
This is the key decision when you start:
Registering the backend makes your app selectable in the ERP's "create bank" dialog. The customer then creates their own bank — with the correct name, IBAN and BIC — and picks your backend.
Do not create banks on install. The customer owns that master data. The one exception is a provider where the "bank" is the provider itself and there is nothing for the customer to name — the PayPal app creates a single PayPal bank, one account per connected merchant.
Letting the ERP call you
The bank backend record carries three URL fields. Set them if the ERP should drive the interaction:
appTransactionUrl
Fetch transactions
appPaymentUrl
Send a payment or direct debit
appStatusUrl
Poll the status of a payment
If your app pulls on a schedule instead, you don't need them — use a cron webhook.
Delivering transactions
One POST per transaction. There is no bulk endpoint:
The fields that matter:
accountId
The ERP bank account this belongs to
bookingDate, valueDate
Date-time
amount
Always positive — the sign lives in direction
direction
INCOMING or OUTGOING
currency
ISO code
endToEndId
Your stable identifier — this is what deduplication keys on
purpose, name, iban, accountNumber
Counterparty and reference data
matchingState and isPotentialDuplicate are set by the ERP — don't try to write them.
Balances are updated separately, and the value goes in the query string:
Matching against open items
You do not implement matching. Hand it to the ERP after importing:
Call it once per account after a batch, not per transaction. Only call it when something was actually imported.
Sending payments
Outbound is the mirror image: read payments the ERP has prepared, send them, then report back.
Only payments with status
CREATEDorPREPAREDare pending.Credit transfers are
MONEY_TRANSFER/REALTIME_MONEY_TRANSFER; direct debits areSEPA_CORE_DIRECT_DEBIT/SEPA_B2B_DIRECT_DEBIT.Confirm with
POST /erp/bank/payments/{id}/update_status/SENT— and only after the bank or provider has actually accepted it.
Archive before you mark as sent. If you write the file to the DMS after transitioning payments to SENT, a failed upload leaves payments marked as sent with no record of what the bank received. Upload first, then transition.
What to watch out for
Deduplication is your responsibility. The ERP flags potential duplicates but does not reject them. Key on endToEndId. Two traps:
If you only check for existing ids within the date window you just queried, a transaction whose booking date falls outside that window will be imported again. Overlapping sync windows make this likely.
Transactions without an
endToEndIdcan never be deduplicated. Derive a stable identifier for them or accept duplicates.
For file-based imports, layer the checks: hash of the whole file, then statement identity, then per-entry id. A file that failed to import must still be retryable — treat an import as "already done" only if it actually created rows.
Provider data lags. Reporting APIs often do not have the last few hours. Ending a sync window at "now" returns empty results or an error. End it deliberately in the past and track how far you have synced.
Windows have maximum lengths. Many providers cap a query at around a month. Split a long back-fill into chunks rather than requesting the whole range.
Watch the sync cursor. If a manual sync with an explicit date range writes the same "synced to" value the scheduled job reads, a user investigating an old transaction can move the cursor backwards or forwards and skip days. Keep the cursor separate from ad-hoc queries.
Dates as strings. If you compare dates lexicographically, never mix date-only (2026-01-15) and date-time (2026-01-15T10:30:00Z) values in the same field — the comparison silently breaks.
Amounts. Send the absolute value and set direction. Losing the direction flag silently inverts a booking. Keep two decimals and be careful with floating-point sums over large batches.
SEPA is EUR-only and its character set is restricted — transliterate umlauts and accents, and strip anything outside the permitted set before generating a file, or the bank rejects it. The SEPA creditor identifier (Gläubiger-ID) lives on the ERP bank record, not in your app configuration.
Credentials per connected account. A tenant may connect several merchants or accounts to one backend. Key credentials by account so they don't overwrite each other, and never log them.
Last updated
Was this helpful?