To handle Paystack webhooks in NestJS, enable rawBody in NestFactory.create, create a WebhookGuard that verifies the HMAC SHA-512 signature, and build a controller that receives the event, returns 200 immediately, and processes it asynchronously.
NestJS does not capture the raw request body by default. You need it for HMAC verification. Enable it in your bootstrap function:
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
var app = await NestFactory.create(AppModule, {
rawBody: true,
});
await app.listen(3000);
}
bootstrap();
With rawBody: true, every request object gets a rawBody property containing the unparsed bytes. This is what you hash for signature verification.
// src/payment/guards/paystack-webhook.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as crypto from 'crypto';
@Injectable()
export class PaystackWebhookGuard implements CanActivate {
constructor(private configService: ConfigService) {}
canActivate(context: ExecutionContext): boolean {
var request = context.switchToHttp().getRequest();
var signature = request.headers['x-paystack-signature'];
if (!signature) {
return false;
}
var secret = this.configService.get('PAYSTACK_SECRET_KEY');
var hash = crypto
.createHmac('sha512', secret)
.update(request.rawBody)
.digest('hex');
return hash === signature;
}
}
This guard runs before the controller method. If the signature does not match, NestJS returns a 403 automatically. The controller code never executes for invalid webhooks.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
// src/payment/webhook.controller.ts
import { Controller, Post, Body, HttpCode, UseGuards } from '@nestjs/common';
import { PaystackWebhookGuard } from './guards/paystack-webhook.guard';
import { PaymentService } from './payment.service';
@Controller('api/webhooks')
export class WebhookController {
constructor(private paymentService: PaymentService) {}
@Post('paystack')
@UseGuards(PaystackWebhookGuard)
@HttpCode(200)
async handleWebhook(@Body() event: any) {
// Process asynchronously
this.processEvent(event).catch(function(err) {
console.error('Webhook processing error:', err);
});
return { received: true };
}
private async processEvent(event: any) {
var eventType = event.event;
var data = event.data;
if (eventType === 'charge.success') {
await this.paymentService.fulfillOrder(data.reference);
} else if (eventType === 'subscription.create') {
await this.paymentService.createSubscription(data);
} else if (eventType === 'invoice.payment_failed') {
await this.paymentService.handleFailedRenewal(data);
}
}
}
@HttpCode(200) ensures NestJS returns 200 instead of the default 201 for POST requests. Paystack expects a 200 response.
// src/payment/payment.module.ts
import { Module } from '@nestjs/common';
import { PaymentController } from './payment.controller';
import { WebhookController } from './webhook.controller';
import { PaystackService } from './paystack.service';
import { PaymentService } from './payment.service';
import { PaystackWebhookGuard } from './guards/paystack-webhook.guard';
@Module({
controllers: [PaymentController, WebhookController],
providers: [PaystackService, PaymentService, PaystackWebhookGuard],
exports: [PaystackService],
})
export class PaymentModule {}
Use ngrok to expose your local server:
npx ngrok http 3000
Set the webhook URL in your Paystack dashboard to the ngrok URL plus your webhook path: https://abc123.ngrok.io/api/webhooks/paystack.
For unit tests, create the HMAC signature yourself and pass it in the request header:
import * as crypto from 'crypto';
var payload = JSON.stringify({
event: 'charge.success',
data: { reference: 'test_123', amount: 500000, status: 'success' },
});
var hash = crypto
.createHmac('sha512', 'sk_test_xxx')
.update(Buffer.from(payload))
.digest('hex');
// POST to /api/webhooks/paystack with:
// Header: x-paystack-signature:
// Body:
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course