DEVELOPER GUIDE

Download inbound WhatsApp media

The inbound webhook gives you a download_url, not the bytes. This is how you turn that reference into a file — safely, through ChatRail, never straight from the transport.

Resolve the URL, then fetch it

download_url is a path such as /v1/messages/{id}/media. Resolve it against your API base URL and request it with an API key that carries messages:read. The provider's own file reference is never exposed; this endpoint is the only way in, and it is authenticated and scoped to your workspace.

Terminal
curl -L https://www.chatrail.dev/v1/messages/019bf4c3-0000-7000-8000-000000000001/media \
  -H "Authorization: Bearer $CHATRAIL_API_KEY" \
  -o reply.jpg
Your app requests the download URL from ChatRail with an API key and ChatRail streams the file back; the provider's own URL is never exposed
ChatRail proxies the file; your app never sees the provider's URL.

What comes back

The response streams the file. Its Content-Type is the type ChatRail stored, never one echoed from the transport, and it always carries Content-Disposition: attachment and X-Content-Type-Options: nosniff so customer-supplied media can never render inline on a ChatRail origin. A Content-Length is set when the size is known.

Node.js — stream to disk
import { createWriteStream } from "node:fs";
import { Readable } from "node:stream";

const res = await fetch(new URL(media.download_url, "https://www.chatrail.dev"), {
  headers: { Authorization: `Bearer ${process.env.CHATRAIL_API_KEY}` },
});

if (res.status === 200) {
  await new Promise((resolve, reject) => {
    Readable.fromWeb(res.body).pipe(createWriteStream("reply.bin"))
      .on("finish", resolve).on("error", reject);
  });
}

When there is nothing to fetch

A download_url of null on the webhook means the transport reported a file it will not serve. The message still arrived; there is simply nothing to download. An outbound attachment never carries a download_url either — that URL is the one you supplied when you sent it, and you already have it.

FieldTypeNotes
200—The file, streamed as an attachment.
404—No attachment on the message, or the transport reported a file it does not serve, or it no longer holds it.
503—This deployment cannot retrieve media from the transport.

Need a whole conversation rather than one file? Export the chat from WhatsApp and open it in the free WhatsApp chat viewer, which reads it in your browser and saves it as CSV or PDF.

Why it is proxied, not redirected

ChatRail streams the bytes rather than handing you the transport's URL. That URL names an internal host, is usually unreachable from your network, and carries no authentication of its own. Proxying keeps the boundary the product sets everywhere else: ChatRail stores the reference, never the file, and only the transport's own origin is ever fetched.

BUILD IT

Wire it against the real schemas.

Every shape on this page is the one the endpoint validates. Verify it in your own stack before production rollout.

Start free