File batching
File batching
Some payment partners do not accept one API call per payment. They accept one file that holds every payment for the day, and they answer later with a file of their own. Debit order files, EFT collection files and bank submission files all work this way.
File batching is the collection module path for those partners. Root groups the due payments into a batch and hands the whole batch to your module. Your module builds the file in whatever format the partner needs, sends it to the partner, and tells Root what it sent. Root stores a copy of the file, records the outcome against each payment, and routes the partner's reply back to your module so it can settle those payments.
File batching builds on scheduled paymentsThe batch itself comes from the scheduled payments path. Read that first. File batching is
what you add when your partner takes a file instead of an API call.
Is file batching right for your module?
Your module has one submission function either way. What changes is what that function returns.
| API submission | File submission | |
|---|---|---|
| The partner accepts | One request per payment | One file per batch |
| Your function returns | A result per payment | A list of actions |
| Who sends to the partner | Your module | Your module |
| Root stores a copy of what was sent | No | Yes |
| The partner's reply | Arrives per payment, usually by webhook | Arrives as a file, later |
| Turned on by | batching.enabled | batching.enabled plus fileConfig |
If your partner takes an API call per payment, stop here and use the scheduled payments path. Add
fileConfig only when a file is what the partner actually wants.
How it works
flowchart TD
A[Due payments become pending payments] --> B[Root groups them into a batch]
B --> C[Root calls your submission function]
C --> D[Your module builds the file and sends it to the partner]
D --> E[Your module returns move_to_outbox with the file and references]
E --> F[Root stores the file and records each payment as submitted]
F --> G[Root files an archive copy of what was sent]
G --> H[The partner replies with a file]
H --> I[Root stores the reply and calls your response function]
I --> J[Your module returns update_payment for each payment in the reply]
-
Root collects the payments that are due for your module on an hourly tick, and only inside the
submission window you configured. What it collects is split into batches ofsubmitBatchSize. -
Your submission function is called once per batch. It receives every payment in that batch, with
the policy, policyholder and payment method attached to each one. -
Your module builds the file and sends it to the partner over HTTPS. The format is yours. Root
never reads inside the file, so fixed width, CSV, XML and JSON all work the same way. -
Your module returns a list of actions describing what it sent. The one that matters is
move_to_outbox, which carries the file content and the reference your module wrote for each
payment. Root stores the file, stamps those references onto the payments, and records the batch
as delivered. -
When your module also returns
move_to_archive, Root copies the file into an archive folder
dated by the day it went out. That copy is your record of what the partner received. -
The partner replies. Your module either captures the reply in the response to its own delivery
request, or the partner posts the file to a Root endpoint. You choose which withresponseVia. -
Root calls your response function with the reply, the payments on the batch, and the references
your module wrote for them. Your module parses the file and returns oneupdate_paymentaction
per payment it can resolve.
What you build, and what Root does
You write two functions and one file format:
- The submission function that builds the file and sends it to the partner
- The file layout the partner expects
- The response function that parses the partner's reply
- The reference on each payment that ties it to a line in the file
Root handles everything around them:
- Grouping payments into batches, and calling your function on schedule
- Storing the file, the archive copy, and every reply that arrives
- Stamping the references, the delivery time and the batch status
- Stopping a batch from being sent twice
- Matching a reply back to the batch it belongs to
- Writing the final outcome to each payment
Where your files are stored
Root keeps three folders for your module, inside a Root bucket:
| Folder | Holds |
|---|---|
| Outbox | The file your module reported sending. Kept for the life of the batch. |
| Archive | A copy of the delivered file, in a folder dated by the day it went out. |
| Inbox | Every raw reply from the partner, exactly as it arrived, stamped with the time it was received. |
The folders are not configurable, and your partner never sees themRoot derives them from your organisation and your module key, and keeps each environment separate.
Delivery to the partner is your module's own HTTPS request carrying the file, not a read from a
bucket. Nothing in your configuration can point at another module's files.
Batch statuses
| Status | Meaning |
|---|---|
pending | The batch is built, and nothing has been sent |
submitting | A run is in progress and the file may be on its way to the partner |
submitted | Your module reported the file as sent |
accepted | The partner confirmed it received the file |
failed | The batch ended without being collected. Final |
archived | The batch is finished. Final |
Your module moves a batch to submitted, accepted, failed or archived with the
update_payment_batch action. pending and submitting belong to Root, and your module cannot
write them.
A batch stuck insubmittingneeds a person
submittingmeans a run may have had the file on its way to the partner when something broke.
Root cannot tell a file that was never sent from one that was sent but never acknowledged, so it
stops and raises an alarm rather than guessing. Somebody has to confirm with the partner what they
hold before that batch moves again.
The rules that matter most
Getting these wrong means money moves twice, or money is recorded as uncollected when the partner is
about to collect it.
Only report a file as sent when the partner has it
move_to_outboxis your module saying the file reached the partner. Root treats it as proof of
delivery and records every payment in it as submitted. Never return it when your request failed.
Only ask for a retry when the request never left your module
release_batch_for_retrysends the batch again. Use it for a refused connection, a DNS failure or
a TLS failure before the request was written. A timeout, a dropped connection mid-request, or any
status code coming back all mean the partner may hold the file. In those cases return neither
action and let a person decide. Asking for a retry when the partner already has the file debits
every payment in it twice.
Give every payment a referenceThe reference your module writes into the file is the only thing tying a line in the partner's
reply back to a payment in Root. A batch sent without a reference for every payment cannot be
settled from the reply, and has to be fixed by hand.
A partner that rejects the file is a failure, not a retryReport it with
update_payment_batchand afailedstatus, and do not returnmove_to_outbox.
The payments then settle under the reason your module gave.
In this section
- Configure file batching sets up
billingSettings.batchingandfileConfigfield by field,
and lists what Root checks when you publish. - Build and submit the batch file covers what your submission function receives, the actions it
can return, and how to report a request that failed. - Handle response files covers both reply routes, the endpoint your partner posts to, and what
your response function is handed. - Batch states and file storage has the full status flow, the folder layout, and the fields
Root records on a batch. - File batching example is a working module: the configuration, a fixed-width submission
function, and a response function that settles payments from the references. - Instructions for your payment partner is the single page to hand your partner. Endpoint,
headers, file limits.
Updated about 1 hour ago