The files your customers actually send
A finance lead at a lending company is moving 18 months of history onto your platform. She has a bank export where every amount is positive and a separate "DR/CR" column says which way the money moved. She has a ledger export from her accounting software that shows negatives in parentheses and repeats a subtotal every few hundred rows. And she has a customer list where every account number lost its leading zeros the moment someone opened the file in Excel. One file writes dates month first and the other day first, so 3/4/2025 is a different day depending on where it came from.
Then there are the columns that cannot be wrong. A routing number with two digits swapped still looks like nine digits. A transaction that appears in two overlapping exports gets counted twice. The currency column says "USD" on most rows, "$" on some, and nothing on the rest. If any of it reaches your ledger, someone spends the next week reconciling by hand, and the customer's first impression of your product is a balance that does not match their bank.
Fields and rules that matter in financial data
| Field | What goes wrong in real files | How Dromo catches it |
|---|---|---|
| Transaction ID | Duplicates when two exports overlap | unique validator, or unique_with across date, amount, and account when the source has no ID |
| Account number | Leading zeros stripped, stray spaces | String field with regex_match on the expected length; a column hook trims and pads |
| Routing number | Swapped digits still look valid | regex_match for nine digits, plus a column hook that runs the ABA checksum |
| Amount | Currency symbols, thousands separators, parentheses for negatives | Number field with the usd preset; a column hook converts parentheses to negatives |
| Debit or credit | The sign lives in a separate column | A row hook combines the indicator and the amount into one signed value |
| Transaction date | Day-first and month-first formats from different sources | Date parsing with a fixed output format, and mapping memory per source layout |
| Currency | "USD", "$", "us dollars", or blank | Select field of ISO codes; bulk fix maps the variants in one step |
| Tax ID | SSNs and EINs mixed, with and without dashes | ssn field type for individuals; regex_match for nine-digit EINs |
| Category | Free text that differs for every customer | Select field with allowed values, remembered for the next upload |
A working transactions schema
{
"fields": [
{
"label": "Transaction ID",
"key": "transaction_id",
"type": "string",
"validators": [{ "validate": "required" }, { "validate": "unique" }]
},
{
"label": "Account number",
"key": "account_number",
"type": "string",
"validators": [
{ "validate": "required" },
{ "validate": "regex_match", "regex": "^\\d{4,17}$", "errorMessage": "Account numbers are 4 to 17 digits" }
]
},
{
"label": "Routing number",
"key": "routing_number",
"type": "string",
"validators": [
{ "validate": "regex_match", "regex": "^\\d{9}$", "errorMessage": "Routing numbers are 9 digits" }
]
},
{
"label": "Transaction date",
"key": "transaction_date",
"type": ["date", { "outputFormat": "YYYY-MM-DD" }],
"validators": [{ "validate": "required" }]
},
{
"label": "Amount",
"key": "amount",
"type": ["number", { "preset": "usd" }],
"validators": [{ "validate": "required" }]
},
{
"label": "Currency",
"key": "currency",
"type": "select",
"selectOptions": [
{ "label": "USD", "value": "USD" },
{ "label": "EUR", "value": "EUR" },
{ "label": "GBP", "value": "GBP" },
{ "label": "CAD", "value": "CAD" }
]
}
]
}
The routing checksum and the debit and credit merge are hooks rather than static validators, because they need logic beyond a single pattern. Column hooks can also be async, so the same import can confirm that each account number already exists in your system before the user submits. Schema Studio lets a product manager maintain the field list, the allowed categories, and the error messages from a form, without a deploy.
Security for financial data
Financial files carry account numbers, tax IDs, and balances, and your customers' security teams review every vendor that touches them. Dromo has completed a SOC 2 Type II audit, and the report is available through the Trust Center.
The stronger answer is that Dromo does not need to touch the data at all. In Private Mode, the default, the file is parsed and validated in the user's browser and the results go straight to your application. AI column matching runs in a headers-only mode: Dromo sees column names and the distinct values of select fields, such as currency codes, never account numbers or amounts. On the Enterprise plan, Bring Your Own Storage writes import data directly to your own S3, Google Cloud Storage, or Azure bucket, and the full stack can be self-hosted in your cloud for data residency. See how Private Mode works.
Recurring files from banks and partners
Not every file comes from a person. Banks, processors, and lending partners send statements and settlement files on a schedule, often over SFTP, in a layout that drifts a little each quarter. On the Enterprise plan, the headless data importer runs those files through the same schema automatically, over the API or SFTP.
A file that passes comes back as clean JSON. A file with a renamed column or failing rows stops in NEEDS_REVIEW with a review link, which you send to whoever owns that partner relationship, instead of posting bad entries to the ledger and finding out at month end.
Connect it to your ledger
When a user finishes an embedded import, the onResults callback receives JSON in which every date is ISO, every amount is a signed number, and every currency is a known code. Your backend can write it straight to the ledger tables. For imports that must be recorded server-side regardless of what happens in the browser, configure a webhook in the Dromo dashboard and fetch the results from a presigned URL.
For more on what fintech buyers expect from onboarding, see fintech data onboarding: compliance, speed, and accuracy.

