AI & Agents

How to Verify External Webhook Signatures for Fastio Workflows

Fastio coordinates file operations via a realtime activity feed and WebSocket events feed rather than outbound webhooks. When integrating external webhooks with your agentic workflows, verifying each incoming signature before updating Fastio workspaces prevents unauthorized modifications and injection attacks. This guide shows how to verify webhook signatures using Express.js and FastAPI before executing Fastio API actions.

Fastio Editorial Team 8 min read
Track file events and incoming API deliveries in your workspace audit log

Event Handling in Fastio Workflows

Fastio does not provide native outbound webhooks. Instead, Fastio provides a realtime activity feed you can poll and a WebSocket events feed for workspace events like file uploads, modifications, and access.

When connecting external webhook producers (such as payment gateways, form builders, or third-party webhooks) to trigger actions in Fastio, verifying those external signatures is essential before executing Fastio REST API calls.

Events include details: file ID, version, user or agent who triggered it, and timestamps.

Set up your external webhook listener to receive provider events, verify signatures, and interact with Fastio workspaces via the REST API or remote MCP server.

Why Signature Verification Matters

Attackers forge webhooks to trigger unauthorized actions: delete files or 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. The sending service signs the timestamped raw payload before your server calls Fastio.

Secure data room with event notifications
Fastio features

Secure Your Agent Workflows Now

Coordinate file operations across Fastio workspaces with realtime activity feeds and remote MCP tools. Start a 14-day Business Trial.

How Signatures Work

External webhook providers add signature headers (such as Stripe-Signature or provider HMAC headers):

Webhook-Signature: t=1694206100,v1=abc123def456...

Signature format: t=<unix_timestamp>,v1=<base64_hmac>

Compute expected signature:

  1. Concat timestamp + "." + raw_request_body
  2. HMAC-SHA256 with your webhook secret (hex or base64)
  3. Base64-encode result
  4. Constant-time compare with v1 value

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 your external webhook provider's developer dashboard.

Click your endpoint and copy the signing secret.

Store the secret securely in environment variables and rotate periodically.

Use this secret in your middleware before forwarding verified file actions to Fastio.

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 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('Webhook-Signature');
  if (!signature) return res.status(400).send('No signature');

const secret = process.env.WEBHOOK_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);

// Now safely call Fastio REST API to upload or modify files
  res.status(200).send('OK');
});

app.listen(3000);

Middleware captures raw body before JSON parse.

Python FastAPI Middleware

FastAPI needs raw bytes. Use Request.body().

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("Webhook-Signature")
    if not signature:
        raise HTTPException(400, "No signature")

secret = b"your_webhook_secret"
    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'])

# Safe to interact with Fastio REST API
    return {"status": "ok"}

await request.body() gets raw bytes.

Handle Webhook Events

Verified payload is JSON:

{
  "event_type": "file.ready",
  "data": {
    "file_id": "f3jm5-zqzfx...",
    "workspace_id": "1234567890123456789",
    "timestamp": 1694206100
  },
  "id": "evt_abc123"
}

Idempotency: check id against DB to skip duplicates.

Common actions after verification:

Trigger Event Fastio REST Action
file.ready Upload new file to workspace
asset.updated Update file version
user.enrolled Create branded portal share
audit.alert Query workspace activity feed

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-minute timestamp window.

Fastio monitoring: Use Fastio's realtime activity feed or WebSocket events feed to track internal file state directly.

Frequently Asked Questions

Does Fastio send outbound webhooks?

Fastio does not send outbound webhooks. Fastio provides a realtime activity feed you can poll and a WebSocket events feed for workspace events. External webhook pipelines should verify incoming payloads before calling the Fastio API.

Why is an incoming webhook signature invalid?

Common causes include middleware modifying the raw request body before HMAC calculation, incorrect secret formatting, timestamp drift beyond five minutes, or mismatched payload encoding.

How do agentic workflows monitor Fastio file events?

Agents monitor file changes using Fastio's realtime activity feed or the WebSocket events feed, eliminating the need for unverified external webhook relays.

Does Fastio support webhook retries?

Fastio does not send webhooks directly. For external services sending webhooks to your ingestion worker, configure your receiver to return 2xx status codes and handle downstream Fastio API retries in a background queue.

Can I test external webhook payloads before sending to Fastio?

Yes, you can simulate signed webhook payloads locally using tools like curl or provider CLI test tools before forwarding verified file operations to the Fastio REST API.

Related Resources

Fastio features

Secure Your Agent Workflows Now

Coordinate file operations across Fastio workspaces with realtime activity feeds and remote MCP tools. Start a 14-day Business Trial.