Turn a Webhook Attachment into a Public CDN URL
A webhook often contains a file URL, but that URL may be private, short-lived, or accessible only to the sender's account. If another service needs a public link, validate the incoming request and copy the bytes to a host that is meant to serve them.
FilePost reduces that storage step to one authenticated request. The response includes a public url, a stable file_id, and the file metadata you need for a database row, CRM field, Slack message, or follow-up webhook.
Choose the recipe that matches the webhook payload
| Incoming value | What your handler does | FilePost endpoint |
|---|---|---|
| Multipart attachment | Forward the file stream | POST /v1/upload |
| Temporary file URL | Download, validate, then forward the bytes | POST /v1/upload |
| Base64 string | Forward filename, content type, and base64 data as JSON | POST /v1/upload/base64 |
Multipart attachment example
This FastAPI handler receives a file and immediately turns it into a FilePost URL:
import os
import httpx
from fastapi import FastAPI, UploadFile
app = FastAPI()
@app.post("/webhooks/file")
async def receive_file(file: UploadFile):
contents = await file.read()
async with httpx.AsyncClient(timeout=30) as client:
response = await client.post(
"https://upload.filepost.dev/v1/upload",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
files={"file": (file.filename, contents, file.content_type)},
)
response.raise_for_status()
uploaded = response.json()
return {"public_url": uploaded["url"], "file_id": uploaded["file_id"]}
Temporary source URL example
Do not copy an untrusted URL directly into a server-side request without controls. Allow only expected hosts, reject private and loopback addresses, cap redirects and response size, and validate the content type before forwarding the bytes.
source = await client.get(approved_url, follow_redirects=False)
source.raise_for_status()
uploaded = await client.post(
"https://upload.filepost.dev/v1/upload",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
files={"file": ("attachment.pdf", source.content, "application/pdf")},
)
uploaded.raise_for_status()
public_url = uploaded.json()["url"]
Base64 JSON example
If the webhook already carries base64 data, keep the handoff as JSON:
curl -X POST https://upload.filepost.dev/v1/upload/base64 \
-H "X-API-Key: $FILEPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filename": "report.csv",
"content_type": "text/csv",
"data_base64": "bmFtZSx0b3RhbApGaWxlUG9zdCw0Mg=="
}'
Production checks
- Acknowledge safely. If the sender retries slow webhooks, queue the upload and return the sender's expected success response promptly.
- Make retries idempotent. Save the sender's event ID so one webhook retry does not create several hosted copies.
- Keep the API key server-side. Put it in the workflow platform's credential store or a server environment variable.
- Handle quota warnings. Upload responses include a
quotaobject after 60% usage, so an automated workflow can alert before uploads are rejected. - Verify the outcome. Fetch the returned URL and compare the bytes when the upload is part of a critical pipeline.
Build the storage step once
Start with the live upload above, then copy the matching recipe into your webhook handler.
Get Your Free API Key