Build and submit the batch file

The platform hands your submission hook a batch of due payments, and your hook sends the partner a file and reports back what it did.

The submission hook

The platform calls the function you named in batching.submitPaymentsFunction, once per batch. Your hook builds the file, sends it to the partner itself, and returns a list of actions describing what happened.

export const submitPayments = async ({ payment_batch_id, payments, organization, environment }) => {
  const file = buildFile(payments);
  const references = Object.fromEntries(payments.map((p) => [p.payment_id, referenceFor(p)]));

  await sendToPartner(file);

  return [
    {
      name: 'move_to_outbox',
      content: file,
      file_name: `ACMEPAY-20260928-${payment_batch_id.slice(0, 8).toUpperCase()}.txt`,
      references,
    },
  ];
};

What you are given

FieldTypeDescription
payment_batch_idstringThe batch these payments were grouped into. Use it for your log lines and for building an idempotency key on your own request.
paymentsarrayEvery payment in the batch. One file covers all of them.
organizationobjectThe organization the batch belongs to.
environmentstringThe environment the run is in.

Each entry in payments carries the fields below.

FieldTypeDescription
payment_idstringThe platform's id for the payment. You need this to build your references map.
policy_idstringThe policy being collected on.
amountnumberThe amount to collect.
currencystringThe currency of the amount.
premium_typestringWhat the payment is for.
billing_period_startstringThe first day of the period the payment covers.
billing_period_endstringThe last day of that period.
policyholderobjectThe policyholder the partner will debit.
policyobjectThe full policy.
payment_methodobjectThe method the payment was created against.
ℹ️

payment_method is the method the payment was created against, which is not always the one on the
policy today. Moving a policy to a new method does not move payments that already exist, so read
the banking details off this field rather than off policy.

Build the file

The platform never opens your file. Fixed width, CSV, JSON, XML, whatever your partner asked for, you build it and you send it. Nothing on the platform validates its layout, so your partner's spec is the only spec.

Delivery is yours too. Your hook makes its own HTTPS request to the partner, and the platform does not deliver anything on your behalf.

Write a reference for every payment

Your file needs an identifier per line that the partner will quote back to you in its reply. The references map tells the platform which identifier you used for which payment.

references: {
  '8a4c3f1e-...': 'ACME-000481',
  '2b9d7e5c-...': 'ACME-000482',
}

The platform stores each reference on its payment, then hands the whole map back to your response hook when the reply arrives. That is the only thing tying a line in the partner's reply to a payment on the platform.

⚠️

Give every payment in the batch a reference. A batch missing even one cannot be settled from the
reply, and because the partner already holds the file, the platform will not record those payments
as failed either. It raises an alarm and waits for a person.

Report what you sent

Your hook returns an array of actions. Five are accepted from this hook.

ActionReturn it whenWhat the platform does
move_to_outboxThe partner has the fileStores the file, stamps each payment's reference, records the batch as delivered, and reports every payment as submitted
update_payment_batchYou want to move the batch, or record that it failedSets the batch to submitted, accepted, failed or archived
move_to_archiveAfter the file is deliveredCopies the stored file into a dated archive folder
move_to_inboxThe partner replied inside your own delivery responseStores the reply and queues it for your response hook
release_batch_for_retryYour request never left your moduleReturns the batch to pending, so it is submitted again

Inside move_to_outbox

FieldTypeDescription
contentstringThe file exactly as you sent it.
file_namestringThe name the file is stored under.
referencesobjectA map of payment id to the reference you wrote for that payment.

Although file_name is optional in the contract, the first move_to_outbox on a batch has to carry one, because nothing else can supply it. Once a batch has a name, that name is pinned and later actions cannot change it. A retry that sends a different name is stored under the pinned one, and the platform logs the difference.

Choose the right action for what happened

This is the part to get right. The platform cannot see your request, so your actions are the only account of it, and the wrong one debits people twice.

What happenedReturnResult
The partner accepted the filemove_to_outboxThe payments are reported submitted and wait for the reply
The partner rejected the fileupdate_payment_batch with failed, and no move_to_outboxThe payments settle under the reason you gave
Your request never left your modulerelease_batch_for_retry, on its ownThe batch returns to pending and is submitted again
You cannot tellNeitherThe batch stays where it is and a person establishes what the partner holds
⚠️

Return release_batch_for_retry only when you can show your request never reached the partner: a
refused connection, a DNS failure, or a TLS failure before the request was written. A timeout, a
connection dropped mid-request, or any status code at all means the partner may hold your file.
Releasing a batch the partner already has sends the file a second time and debits every payment in
it twice.

A partner that rejects the file is a failure, not a retry. Report it with update_payment_batch and
a failed status, leave move_to_outbox out, and the payments settle under the reason you gave.

What the platform does with your actions

move_to_outbox runs first, because everything else is about a file that already exists. The rest run afterwards, in a fixed order: the archive copy, then the inbox reply, then the batch status. The order is the platform's, not the order you returned them in.

Once your file is stored, the platform stamps the batch as delivered, pins the file name, writes each reference onto its payment, and reports all of the payments as submitted. Your batch status is yours to move, so the platform leaves it alone until your update_payment_batch runs.

ℹ️

move_to_archive and move_to_inbox both need a stored file, so they are ignored on a run that
staged none. A failure while filing the archive copy never fails the payments. The file is with
the partner either way, and the archive is the platform's own record rather than the partner's.

When the platform stops and calls a person

Three returns leave the platform holding two facts it cannot reconcile. In each case it raises an
alarm and settles nothing, because guessing would be a false statement about money.

  • You returned move_to_outbox together with a failed status. One says the partner has the file, the other says the batch did not take. Report a rejection without move_to_outbox instead.
  • You returned move_to_outbox without a reference for every payment. The partner will debit them and no reply can be matched back.
  • A run picked up a batch already sitting in submitting. A previous run may have had the file on the wire, and nothing left in the system can tell an unsent request from an unacknowledged one.

Returning nothing at all ends the batch instead. Your hook ran and answered, so the platform takes that as an answer, and records the batch as failed rather than calling your hook again.

Next step

Handle response files covers the reply: how it reaches the platform, and how your response hook
settles the payments.


Did this page help you?