Build on Xferio
Let people send files from your product without your servers ever touching the bytes. You create the transfer; the sender’s browser does the sending; webhooks tell you how it went.
How an integration works
- Your backend creates a transfer with an API key.
- You send the person who is sending to the transfer’s
hostUrl, a sending page hosted by Xferio. They choose files there. - Recipients open the share link. Files stream from the sender’s browser to theirs while the sender’s tab stays open.
- Your webhook endpoint receives events such as a recipient connecting or a download finishing.
The TypeScript SDK (@xferio/sdk) wraps every step, including webhook verification. Any HTTP client works too.
API keys
Create keys under API & webhooks in your workspace. API access is part of paid plans. A key is shown once; store it as a secret. Send it as a bearer token:
Authorization: Bearer xf_live_…Each key works only in its workspace and only for the scopes you tick. A key sees and stops the transfers it created; give it transfers:read_all to see the whole workspace. Rotating a key keeps the old one working for 24 hours.
Create a transfer
curl -X POST https://api.example.com/v1/tenants/$WORKSPACE_ID/transfers \
-H "Authorization: Bearer $XFERIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title": "Signed contract", "expiresInHours": 24, "password": "optional"}'The response includes:
transfer.shareUrl: the link recipients open.hostUrl: the sending page. Redirect the sender there and never log it; whoever has it can send as this transfer.hostandsignaling: for advanced integrations that run the sender inside their own page with the SDK.
import { XferioApi } from '@xferio/sdk';
const xferio = new XferioApi({ baseUrl: 'https://api.example.com', apiKey: process.env.XFERIO_API_KEY });
const created = await xferio.createTransfer(workspaceId, { title: 'Signed contract' });
return Response.redirect(created.hostUrl, 303);Webhooks
Add an HTTPS endpoint under API & webhooks and pick the events you want. Each delivery is a JSON event signed with the endpoint’s secret:
X-Xferio-Signature: t=1791547200,v1=5f2b…
# v1 = hex HMAC-SHA256(secret, "<t>.<raw request body>")import { verifyWebhook } from '@xferio/sdk';
const event = await verifyWebhook({
payload: rawBody, // raw bytes, before JSON parsing
signature: req.headers['x-xferio-signature'],
secret: process.env.XFERIO_WEBHOOK_SECRET,
}); // throws if forged, altered or older than 5 minutes
if (await alreadyHandled(event.id)) return; // retries reuse the same idReply with any 2xx within 10 seconds. Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours. Events marked source: "client", such as transfer.completed, were reported by a browser.
Errors and limits
Errors share one shape, and their codes never change meaning:
{ "error": { "code": "QUOTA_EXCEEDED", "message": "…", "requestId": "…" } }| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_FAILED | The request body or query is invalid; details list each field. |
| 401 | UNAUTHENTICATED | Missing, revoked or expired key or token. |
| 402 | ENTITLEMENT_MISSING | Your plan does not include this feature. |
| 402 | QUOTA_EXCEEDED | A monthly limit or the seat limit is reached. |
| 403 | FORBIDDEN | The key lacks the scope, or the transfer belongs to someone else. |
| 409 | TRANSFER_NOT_JOINABLE | The transfer has ended or expired. |
| 409 | TRANSFER_FULL | Every recipient place on the link is taken. |
| 429 | RATE_LIMITED | Too many requests; wait for the time in the message. |
Each API key may make 300 requests a minute; creating transfers is limited to 60 a minute.
Reference
- OpenAPI 3.1 document:
https://api.example.com/v1/openapi.json - Example service with webhook verification:
examples/backendin the Xferio repository. - Files are encrypted between the browsers by WebRTC (DTLS), also when they pass through the relay. There is no additional application-level encryption, and Xferio cannot scan files for malware because it never receives them.