Careeroza
Real World Example
Testing a Payment Webhook
Payment gateways (Razorpay, Cashfree, Stripe) call your API with a signed webhook when a payment succeeds. A bug here means paid orders stay "pending" and customers call support. Teams cover it with an e2e test that signs a fake event exactly like the gateway does.
test/razorpay-webhook.e2e-spec.ts
import { createHmac } from 'node:crypto';
import request from 'supertest';
const SECRET = 'whsec_test';
const sign = (body: string) => createHmac('sha256', SECRET).update(body).digest('hex');
describe('POST /webhooks/razorpay (e2e)', () => {
// app setup as in the previous topic, with RAZORPAY_WEBHOOK_SECRET=whsec_test
it('marks the order as paid for a valid signature', async () => {
const body = JSON.stringify({ event: 'payment.captured', payload: { payment: { entity: { id: 'pay_N8kq2', notes: { orderId: 'ORD-7781' }, amount: 129900 } } } });
await request(app.getHttpServer())
.post('/webhooks/razorpay')
.set('Content-Type', 'application/json')
.set('X-Razorpay-Signature', sign(body))
.send(body)
.expect(200);
const order = await prisma.order.findUniqueOrThrow({ where: { id: 'ORD-7781' } });
expect(order.status).toBe('PAID');
});
it('rejects a forged signature', async () => {
const body = JSON.stringify({ event: 'payment.captured' });
await request(app.getHttpServer())
.post('/webhooks/razorpay')
.set('Content-Type', 'application/json')
.set('X-Razorpay-Signature', 'deadbeef')
.send(body)
.expect(401);
});
});Note: To verify webhook signatures you need the raw request body: create the app with NestFactory.create(AppModule, { rawBody: true }) and read req.rawBody (type RawBodyRequest<Request>).