SFTPFTPHTTPIntegration

How to send files to SFTP or FTP with an HTTP request

SFTP and FTP servers do not speak HTTP, so a browser, serverless function, webhook or IoT device cannot POST a file to one. Why not, what a relay has to get right, and how to get an HTTPS upload URL that delivers to any SFTP or FTP(S) server, with curl, Python and PowerShell examples.
DocEvent Engineering·Oct 30, 2026·8 minute read

You cannot POST a file straight to an SFTP or FTP server - they do not speak HTTP. Something has to accept your HTTP request, then open the SFTP or FTP session and write the file. You can write that relay yourself, or use a hosted HTTP-to-SFTP bridge: an HTTPS upload URL on one side, the SFTP or FTP server on the other. This article covers both, with working curl, Python and PowerShell examples.

Why an HTTP request cannot reach an SFTP server

Plenty of software can only make HTTP requests: a web page, a serverless function, an IoT device, a SaaS webhook, the HTTP action in a low-code workflow tool, a reporting system that can “export to URL”. Plenty of partners, banks and legacy systems only accept files over SFTP or FTP. The two do not meet:

  • SFTP is not HTTP. It runs as a subsystem of an SSH connection, port 22 by default, with its own binary packet format (draft-ietf-secsh-filexfer-02) inside SSH’s encrypted transport (RFC 4253). An HTTP client has no way to open that session.
  • FTP is not HTTP either, and it is awkward even for clients that try: RFC 959 uses one connection for commands and a second one for every file, which is why FTP and firewalls argue about passive mode.
  • Browsers gave up on FTP. Chrome removed ftp:// support in version 88 and Firefox in version 90, both in 2021, and no browser ever spoke SFTP. JavaScript in a page gets HTTP and WebSockets, nothing else.
  • curl can do SFTP - from a shell. curl -T file sftp://host/path/ works when curl is built with an SSH backend, but that does not help the serverless function, the device or the SaaS tool that can only send an HTTP request.

Your options for HTTP to SFTP or FTP

  1. Write a relay. A small web service accepts the upload and writes it to the server with an SSH or FTP library. Full control, and everything below is yours to run.
  2. Use a workflow tool. Many automation platforms have a webhook trigger and an SFTP step. Fine for small files and low volume; check their file size limits, retries and what they log before a partner depends on it.
  3. Use a hosted HTTP-to-SFTP gateway. You get an HTTPS upload URL and an API key; the service delivers each file to the SFTP or FTP server you configured, and keeps a log of every attempt. That is what DocEvent Channels does, set up below.

If you write your own relay

The happy path is twenty lines in any language. The production version has to answer these, and most incidents come from the ones skipped:

  • Stream, do not buffer. Reading the whole upload into memory works until the first large file. Pipe the request body to the remote file.
  • Verify the host key or certificate. Most SFTP library examples turn host-key checking off to make the sample run. Pin the server’s key, and for FTPS check the certificate, or a man in the middle gets the file and the password.
  • Decide what the HTTP response means. Answer 200 only after the remote close succeeded, and the caller waits for the slowest SFTP server. Answer 200 on receipt, and you need a queue, retries and somewhere for failed files to wait.
  • Check the write, not just the login. Signing in and being allowed to create a file in the folder are different permissions, and some servers report a missing write right as “no such file”.
  • Keep the credentials out of the code, put the relay behind authentication of its own, and limit request size and rate - an open upload URL that writes to a partner’s server is an easy thing to abuse.
  • Log every file: name, size, when it arrived, when it was delivered and what the server said. The day a partner says a file never arrived, that log is the only answer you have.

Set up an HTTP-to-SFTP bridge with DocEvent Channels

A channel connects incoming endpoints to outgoing ones; every file that arrives on one side is delivered to every destination on the other. For this job you need one incoming HTTP Upload endpoint and one outgoing SFTP or FTP endpoint. As of October 2026:

  1. Create a channel in the console and give it a name.
  2. Add an incoming HTTP endpoint. DocEvent generates an upload URL and an API key for it. The URL looks like https://channel-us-east-1.docevent.io/plugin-recv-http-v1/<endpoint-id>/form-data, with the region you work in. The key goes in an x-api-key header.
  3. Add an outgoing SFTP or FTP endpoint. Host, port, username, password and the folder to deliver into.
    • SFTP: the server’s host key is trusted on first use and checked on every connection after that, or you paste the SHA-256 fingerprints you were given.
    • FTP: plain FTP, explicit FTPS (AUTH TLS) or implicit FTPS on port 990. For FTPS the certificate is trusted on first use, checked against fingerprints or validated against public certificate authorities. Data connections are passive, and an option reuses the server’s address when a server behind NAT advertises an internal one.
  4. Press Test connection on the outgoing endpoint. It does not stop at the login: it creates a small probe file, writes it, lists it back and deletes it, so a missing write permission shows up now instead of on the first real file.

If the server’s owner allowlists IP addresses, give them DocEvent’s static outbound IPs for your region. The pre-built pages for this pairing are HTTP to SFTP and HTTP to FTP.

Send a file to SFTP with an HTTP POST

The upload is an ordinary multipart/form-data POST (RFC 7578), the same encoding an HTML file input uses, so every HTTP client and language can send it. The form field name is up to you; each part with a filename is one file, and one request can carry several.

curl

curl -X POST "https://channel-us-east-1.docevent.io/plugin-recv-http-v1/<endpoint-id>/form-data" \
  --header "x-api-key: $DOCEVENT_API_KEY" \
  --form "file=@invoice-20261030.csv"

Add more --form "file2=@other.csv" lines to send several files in one request.

Python

import os
import requests

url = "https://channel-us-east-1.docevent.io/plugin-recv-http-v1/<endpoint-id>/form-data"

with open("invoice-20261030.csv", "rb") as f:
    r = requests.post(
        url,
        headers={"x-api-key": os.environ["DOCEVENT_API_KEY"]},
        files={"file": ("invoice-20261030.csv", f)},
        timeout=300,
    )

r.raise_for_status()
body = r.json()
if body["failed"]:
    raise RuntimeError(f"some files were not accepted: {body['failed']}")

PowerShell 7

$url = "https://channel-us-east-1.docevent.io/plugin-recv-http-v1/<endpoint-id>/form-data"
Invoke-RestMethod -Method Post -Uri $url `
  -Headers @{ "x-api-key" = $env:DOCEVENT_API_KEY } `
  -Form @{ file = Get-Item ".\invoice-20261030.csv" }

-Form needs PowerShell 7 (6.1 or later); Windows PowerShell 5.1 can call curl.exe, which ships with Windows 10 and later, using the curl example above.

Reading the response

The endpoint answers with JSON:

{
  "success": 1,
  "error": "",
  "channel": "<endpoint-id>",
  "failed": [],
  "received": [
    { "key": "file", "filename": "invoice-20261030.csv", "guid": "..." }
  ]
}
  • 200 - at least one file was accepted. Check failed anyway: a request carrying three files can succeed for two.
  • 400 - nothing was accepted, for example no part with a filename.
  • 413 - the upload is over the size limit.
  • 503 - the endpoint is busy; wait for the Retry-After seconds and send again.
  • A missing or wrong API key gets one generic refusal, by design, so the endpoint never tells a stranger which part was wrong.

A 200 means the file is safely in the channel, not that it is on the SFTP server yet. Delivery happens straight after, independently of your request, so a slow or unreachable server never holds your HTTP call open. The guid is the file’s id in the channel’s routing log, which shows each delivery attempt and the server’s answer. A failed delivery is attempted up to three times and then parks in Stuck files, where you can send it again once the server is fixed.

Details that matter in production

The filename is the one you upload

The file is written to the destination folder under the filename in the multipart part, replacing a file of the same name. Put the date, a sequence number or your own id in the name if the receiving system needs every file kept. The file is written directly under its final name, so if something on the far side polls the folder, give it a minimum file age or a filename pattern - truncated files on SFTP and FTP explains why.

Retries can deliver a file twice

If your client times out and sends the same file again, the first request may already have been accepted, and both copies are delivered. Retry when you got no response at all, not after a 200, and make the receiving side tolerate a repeated file - a unique filename usually does it.

Keep the API key off web pages

The endpoint accepts cross-origin requests, so a browser page can upload to it directly. But a key in page source is public, and anyone holding it can put files on your partner’s server. For a public upload form, send the file to your own backend first and post it from there.

Large files

Uploads are streamed into the channel rather than buffered, so multi-gigabyte files are fine, and the delivery to the SFTP or FTP server gets a time budget sized to the file. Give your HTTP client a long enough timeout for the upload itself.

One POST, several destinations

Every destination on the channel gets every file. Add an Amazon S3 destination next to the SFTP one and each upload is archived in your own bucket as it is delivered. The reverse direction exists too: a partner uploads over SFTP and each file is POSTed to your webhook, see SFTP server to HTTP.

An HTTPS upload URL for your SFTP or FTP server
Create a channel, add an HTTP endpoint and an SFTP or FTP destination, and send your first file with one curl command. No server to run, and every delivery in the log.

Common questions

Is there a REST API for SFTP?

Not in the protocol. SFTP has no HTTP interface; a “REST API for SFTP” is always a service in front of the server that accepts HTTP and speaks SFTP behind it - your own relay, a vendor product, or a gateway like the one above.

Can I upload to an FTP server from JavaScript in the browser?

No. Browsers no longer support FTP at all and never supported SFTP. The browser has to POST the file over HTTPS to something that then writes it to the FTP server.

Can curl upload to SFTP?

Yes, with curl -T file sftp://user@host/path/, if your curl was built with SSH support (curl -V lists sftp under Protocols). That needs a shell and the SFTP credentials on the machine. Posting to an HTTP endpoint instead keeps the SFTP password in one place and works from anything that can send a request.

Can a webhook send a file to SFTP?

If the webhook can POST the file as multipart/form-data with a custom header, point it at the channel’s upload URL and set x-api-key. A webhook that only sends JSON needs a step in between that fetches or builds the file.

Does it work with FTPS?

Yes. The FTP destination supports explicit and implicit FTPS with certificate verification, as well as plain FTP for servers that offer nothing else.

If the real question is how to prove a file reached the server, see File transfer audit trails.