How to Verify Fast.io Webhook Signatures
Fast.io webhooks deliver real-time notifications for file uploads, modifications, and access in your workspaces. To keep your agentic workflows secure, verify each webhook signature before processing. Webhook signature verification in Fast.io uses HMAC-SHA256 signatures to confirm events come from Fast.io and remain untampered. This blocks SSRF and injection attacks common in unverified webhooks. Copy the middleware below for Express.js or FastAPI. Both capture the raw request body needed for accurate HMAC computation and use constant-time comparison to prevent timing attacks.
What Fast.io Webhooks Do
Fast.io sends HTTP POST requests to your endpoint for events like file.uploaded, file.modified, file.accessed, workspace.member_added.
Events include details: file ID, version, user who triggered it, timestamp.
Set up webhooks in your org dashboard under Settings > Webhooks. Select events, enter URL. Each webhook gets a unique signing secret.
Respond with 2xx status within 30 seconds or Fast.io retries (up to 24 hours, exponential backoff).
Why Signature Verification Matters
Attackers forge webhooks to trigger actions: delete files, exfiltrate data via SSRF.
Unverified webhooks rank as a top vector for SSRF and injection in agentic systems handling file events.
Verification confirms origin and integrity. Fast.io signs the timestamped raw payload.
How Signatures Work
Fast.io adds Fastio-Signature header:
Fastio-Signature: t=1694206100,v1=abc123def456...
Signature format: t=<unix_timestamp>,v1=<base64_hmac>
Compute expected signature:
- Concat
timestamp + "." + raw_request_body - HMAC-SHA256 with your webhook secret (hex or base64)
- Base64-encode result
- Constant-time compare with
v1value
Timestamps expire after 5 minutes to block replays.
Quick Verification Snippet (Node.js)
const crypto = require('crypto');
function verifySignature(rawBody, signature, secret) {
const [timestamp, sig] = signature.split(',').map(s => s.split('=')[1]);
const payload = `${timestamp}.${rawBody}`;
const expected = crypto.createHmac('sha256', secret)
.update(payload)
.digest('base64');
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
Find Your Signing Secret
Log into Fast.io dashboard > your org > Settings > Webhooks.
Click webhook > Reveal secret (hex or base64).
Store securely, rotate periodically via Regenerate.
API alternative: GET /current/org/{org_id}/webhooks/{webhook_id} (requires admin).
Node.js Express Middleware
Express parses JSON by default, corrupting raw body for HMAC. Use raw-body and buffer.
Install: npm i raw-body express
const express = require('express');
const getRawBody = require('raw-body');
const crypto = require('crypto');
const app = express();
app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } }));
app.post('/webhook', async (req, res) => {
const signature = req.get('Fastio-Signature');
if (!signature) return res.status(400).send('No signature');
const secret = process.env.FASTIO_SECRET;
const rawBody = req.rawBody.toString();
const [timePart, sigPart] = signature.split(',');
const timestamp = timePart.split('=')[1];
const expectedSig = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('base64');
if (!crypto.timingSafeEqual(Buffer.from(sigPart.split('=')[1]), Buffer.from(expectedSig))) {
return res.status(401).send('Invalid signature');
}
if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) {
return res.status(401).send('Timestamp expired');
}
const event = req.body;
console.log(`Event: ${event.event_type}`, event.data);
res.status(200).send('OK');
});
app.listen(3000);
Middleware captures raw body before JSON parse.
Python FastAPI Middleware
FastAPI needs raw bytes. Use RawRequest.
Install: pip install fastapi uvicorn cryptography
from fastapi import FastAPI, Request, HTTPException
from hmac import digest
from hashlib import sha256
import secrets
import time
app = FastAPI()
@app.post("/webhook")
async def webhook(request: Request):
signature = request.headers.get("Fastio-Signature")
if not signature:
raise HTTPException(400, "No signature")
secret = "your_webhook_secret".encode()
body = await request.body()
t, v1 = signature.split(",")
timestamp = int(t.split("=")[1])
payload = f"{timestamp}.{body.decode()}".encode()
expected = digest(secret, payload, sha256)
received_sig = v1.split("=")[1].encode()
if not secrets.compare_digest(received_sig, expected):
raise HTTPException(401, "Invalid signature")
if abs(time.time() - timestamp) > 300:
raise HTTPException(401, "Timestamp expired")
event = await request.json()
print(f"Event: {event['event_type']}", event['data'])
return {'status': 'ok'}
await request.body() gets raw bytes.
Handle Webhook Events
Verified payload is JSON:
{
"event_type": "file.uploaded",
"data": {
"file_id": "f3jm5-zqzfx...",
"workspace_id": "1234567890123456789",
"user_id": "9876543210987654321",
"timestamp": 1694206100
},
"id": "evt_abc123"
}
Idempotency: check id against DB to skip duplicates.
Common events:
| Event | Trigger |
|---|---|
file.uploaded |
New file added |
file.modified |
File updated |
file.accessed |
File viewed/downloaded |
workspace.member_added |
New collaborator |
Respond 200 fast; process async with queue.
Troubleshooting
Invalid signature:
- Body modified by middleware (use raw body).
- Wrong secret (check hex/base64).
- Timestamp mismatch (use exact format).
- Encoding issue (UTF-8 raw).
No events: Verify endpoint public, returns 2xx, not rate limited.
Replay attacks: Enforce 5-min timestamp window.
Test: Dashboard test button sends sample event.
Frequently Asked Questions
How do I verify a Fast.io webhook?
Parse `Fastio-Signature` header, compute HMAC-SHA256 of `t=... + "." + rawBody` using secret, compare constant-time. Use middleware above.
Why is my Fast.io webhook signature invalid?
Common causes: JSON middleware alters body, wrong secret format, timestamp drift, non-UTF8 encoding. Capture raw body first and check logs.
What header holds the Fast.io signature?
`Fastio-Signature: t=<unix_timestamp>,v1=<base64_hmac>`
Does Fast.io support webhook retries?
Yes, exponential backoff up to 24 hours if no 2xx response within 30s.
Can I test webhooks without production events?
Use dashboard test button or simulate events via curl with valid payload/signature.
Related Resources
Secure Your Agent Workflows Now
React to file changes instantly with verified Fast.io webhooks. Free agent plan includes 50GB storage, 5 workspaces, and 5,000 monthly credits.