The Gateway Script Handler

Updated Jul 26, 2026
Hosted File Gateway

The Gateway Script Handler

The script handler runs one of your scripts with a delivered file in its input context. It is the general-purpose handler: anything the built-in ones do not cover.

Configure it on a route by choosing the Script handler and naming the script.

What the script receives

function main(context) {
  context.file_name      // "purchase_order.edi" — the name the partner used
  context.file_path      // "/inbound/edi/purchase_order.edi"
  context.file_size      // bytes
  context.file_sha256    // content hash
  context.file_content   // the file as TEXT   — see the warning below
  context.file_base64    // the file as BASE64 — the intact bytes
  context.file_id        // the transfer id, as a string
  context.s3_key         // where it was stored
  context.principal_id   // which login delivered it, as a string
  context.protocol       // "sftp" or "ftps"
  context.company_id
  context.user_id
}

The run is recorded in the script's own execution history with triggered_by = "file_gateway", so its output and console logs are where you would expect.

Use file_base64 for anything that is not plain text

file_content destroys binary files. It is a text view of the bytes. Encoding it as JSON replaces every byte sequence that is not valid UTF-8 with the Unicode replacement character — and those bytes do not come back. A scanned PDF or an image handed to a script this way is corrupted before the script sees it, which rules out exactly the document work these scripts exist to do.

This is measurable rather than theoretical. Uploading a 256-byte file containing every possible byte value and reporting what the script could see:

{
  "bytes_received_by_gateway": 256,
  "chars_visible_to_script": 256,
  "replacement_chars": 128,          <-- half the file, gone
  "file_base64_present": true,
  "verdict": "base64 available — binary is recoverable"
}

128 of 256 bytes were replaced in file_content. The same file read from file_base64 is byte-for-byte intact.

A transfer detail panel showing the script's returned JSON with file_base64_present true and replacement_chars 128
The same result on a real transfer. This is a live record, not an illustration.
File typeUse
EDI, CSV, JSON, XML, plain textfile_content — convenient and correct
PDF, TIFF, PNG, JPEG, Excel, ZIP, anything compressed or signedfile_base64 — always

file_base64 is also what the OCR API wants, so it passes straight through:

const result = ocr.extractProfile({
  profileId: "1195805445820416012",
  base64: context.file_base64,
  mediaType: "application/pdf"
});
Do not copy the FTP-connector example. The connector-triggered script documentation uses content_base64, connector_name and file_extension. None of those exist on a gateway run. Reading them gives undefined, and it looks like a routing problem.

The script's return value decides the transfer

The gateway marks a transfer processed once it has started the right script with the right bytes — that is all it can know at that moment. When the script finishes, its verdict is carried back to the transfer.

The script…Transfer becomesReason shown
Throws an errorfailedThe error message
Returns { success: false, script_message: "…" }failedYour message
Returns { success: false } with no messagefailed"the script reported the file could not be accepted"
Returns { success: true }unchanged (processed)
Returns anything with no success key, or nothingunchanged (processed)
Why success: false matters. A script that runs cleanly and returns success: false has made a judgement — this invoice has no PO number, this order references a part we do not make. The runtime calls that run a success, because "success" there only means nothing threw. Reading the returned value is what lets a script reject the document that arrived instead of merely logging that it disagreed with it — and it is what makes "show me today's failures" return the failed invoice instead of a clean sheet.

If script_message is absent, message and then error are used.

A rejecting script

function main(context) {
  const text = context.file_content;
  const po = (text.match(/PO[- ]?(\d{4,})/) || [])[1];

  if (!po) {
    return { success: false,
             script_message: "no PO number in " + context.file_name };
  }

  const rows = odbc.executeQuery("open_purchase_orders");
  if (!rows.some(r => r.po_number === po)) {
    return { success: false,
             script_message: "PO " + po + " is not open" };
  }

  return { success: true, script_message: "matched PO " + po };
}

Both rejections turn the transfer red in the transfers console with the reason on the row.

A transfer already marked failed is never repainted green by a later update, so a fast-failing script cannot be overwritten by the ingest path finishing behind it. Replay resets the transfer first, so a genuine retry does clear the old failure.

There is no gateway.* script API

A run already receives everything about its own transfer. Reading other transfers is reporting work, and it belongs in the console rather than in a sandbox.

Was this page helpful?