Turn a Webhook Attachment into a Public CDN URL
A webhook can deliver a file as multipart data, a temporary download URL, or a base64 string. None of those formats is automatically a durable public link. The reliable pattern is to resolve the bytes once, upload them to file storage, and pass the returned URL to every downstream step.
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://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://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://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