How to Implement Fast.io Webhooks with NestJS
Implementing Fast.io webhooks with NestJS enables real-time responses to file uploads, modifications, and access events in your workspaces. A NestJS webhook receiver uses custom guards to validate Fast.io signatures before processing file events. This guide covers project setup, guard creation, controller logic, testing, and deployment for production-ready integration.
Why Integrate Fast.io Webhooks with NestJS?
Fast.io webhooks deliver instant notifications for workspace events like file uploads, edits, deletions, and views. Instead of polling the events API, your NestJS app reacts immediately to changes, enabling reactive agent workflows.
NestJS fits perfectly here. Its modular design and built-in dependency injection make it scalable for handling high-volume events. Guards provide a clean way to verify signatures upfront, keeping controllers focused on business logic.
Common use cases include triggering builds on file uploads, syncing external systems on modifications, or alerting teams on access patterns. With Fast.io's free agent tier offering 50GB storage and 5,000 credits monthly, you can start without costs.
Prerequisites
Before starting, ensure you have:
- Node.js 18+ installed
- NestJS CLI:
npm i -g @nestjs/cli - A Fast.io account (sign up at fast.io, no credit card for agent tier)
- Ngrok for local testing (or similar tunneling tool)
- Basic knowledge of TypeScript and Express middleware
Create a Fast.io workspace and note your webhook signing secret from the dashboard settings.
Set Up the NestJS Project
Generate a new NestJS app:
nest new fastio-webhook-app
cd fastio-webhook-app
npm install @nestjs/config crypto-js @types/crypto-js
Enable raw body parsing in main.ts for signature verification:
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
app.enableCors();
await app.listen(3000);
}
bootstrap();
Add RawBodyModule later for the webhook route.
Create the Signature Validation Guard
Guards in NestJS run before controllers, ideal for auth checks. Create fastio-webhook.guard.ts:
// src/guards/fastio-webhook.guard.ts
import { Injectable, CanActivate, ExecutionContext, BadRequestException, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as crypto from 'crypto';
@Injectable()
export class FastioWebhookGuard implements CanActivate {
private readonly logger = new Logger(FastioWebhookGuard.name);
private readonly webhookSecret: string;
constructor(private configService: ConfigService) {
this.webhookSecret = this.configService.get('FASTIO_WEBHOOK_SECRET');
}
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const signature = request.headers['x-fastio-signature'];
const timestamp = request.headers['x-fastio-timestamp'];
const rawBody = request.rawBody;
if (!signature || !timestamp || !rawBody) {
this.logger.warn('Missing webhook headers or body');
throw new BadRequestException('Invalid webhook headers');
}
const signedPayload = `${timestamp}.${rawBody}`;
const expectedSignature = crypto
.createHmac('sha256', this.webhookSecret)
.update(signedPayload)
.digest('hex');
if (signature !== `v1=${expectedSignature}`) {
this.logger.warn('Webhook signature mismatch');
throw new BadRequestException('Signature verification failed');
}
// Check timestamp staleness (5 min tolerance)
const fiveMinutes = 300000;
const timestampMs = parseInt(timestamp) * 1000;
const timeDiff = Date.now() - timestampMs;
if (timeDiff > fiveMinutes) {
throw new BadRequestException('Webhook timestamp too old');
}
return true;
}
}
This guard extracts headers, recomputes HMAC-SHA256, and checks staleness. Adjust header names based on Fast.io docs (assumed x-fastio-*).
Build the Webhook Controller
Create webhook.controller.ts and webhook.module.ts.
First, RawBodyModule for parsing:
// src/raw-body/raw-body.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class RawBodyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
request.rawBody = '';
return next.handle();
}
}
Controller:
// src/webhook/webhook.controller.ts
import { Controller, Post, Body, UseGuards, Req, Logger } from '@nestjs/common';
import { FastioWebhookGuard } from '../guards/fastio-webhook.guard';
@Controller('webhook')
@UseGuards(FastioWebhookGuard)
export class WebhookController {
private readonly logger = new Logger(WebhookController.name);
@Post('fastio')
handleFastioEvent(@Body() event: any, @Req() req: any) {
this.logger.log(`Received Fast.io event: ${event.event}`);
switch (event.event) {
case 'workspace_storage_file_added':
this.handleFileAdded(event);
break;
case 'workspace_storage_file_updated':
this.handleFileUpdated(event);
break;
// Add more cases: file_deleted, member_added, etc.
default:
this.logger.debug(`Unhandled event: ${event.event}`);
}
return { received: true };
}
private handleFileAdded(event: any) {
// Trigger job queue, notify agent, etc.
this.logger.log(`File added: ${event.filename} (${event.file_size} bytes)`);
}
private handleFileUpdated(event: any) {
this.logger.log(`File updated: ${event.filename}`);
}
}
Module:
// src/webhook/webhook.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { WebhookController } from './webhook.controller';
import { FastioWebhookGuard } from '../guards/fastio-webhook.guard';
@Module({
imports: [ConfigModule],
controllers: [WebhookController],
providers: [FastioWebhookGuard],
})
export class WebhookModule {}
Import into app.module.ts.
Configure Environment and Fast.io Dashboard
Add to .env:
FASTIO_WEBHOOK_SECRET=your_secret_from_fastio_dashboard
In Fast.io dashboard: Workspaces > Settings > Webhooks > Add Endpoint. Enter your URL (ngrok for local), select events (storage_file_added etc.), paste secret.
Test connection.
Test Locally with Ngrok
Run ngrok http 3000, get https URL.
Configure in Fast.io.
Trigger: Use curl to upload file via Fast.io API, or dashboard upload. Check logs for event.
Verify signature fails without secret.
Deploy to Production
Deploy to Vercel, Railway, or VPS. Use PM2 or systemd.
Set env vars securely.
Monitor with Sentry or logs.
Scale with Redis queues for heavy handlers.
Error Handling and Best Practices
Always return 200 fast. Idempotent handlers with event_id.
Retry logic if needed.
Rate limit incoming.
Log everything, alert failures.
Frequently Asked Questions
How to receive webhooks in NestJS?
Use @Post() controller with @UseGuards(custom guard). Enable raw body parsing to access unparsed payload for signatures.
How do I validate webhook signatures in TypeScript?
Use crypto.createHmac('sha256', secret).update(`${timestamp}.${body}`).digest('hex') and compare to header value.
What Fast.io events can I subscribe to?
Events like workspace_storage_file_added, file_updated, member_added. Full list in Fast.io docs under Events & Activity.
How to test Fast.io webhooks locally?
Tunnel with ngrok, configure in dashboard, trigger uploads via API or UI.
Does NestJS support raw request body?
Yes, with NestRawBody decorator or global interceptor for webhook routes.
Related Resources
Build Reactive Agent Workflows
Get 50GB free storage, 5,000 monthly credits, no credit card. Start integrating Fast.io webhooks in NestJS today.