Webhook examples
NestJS webhook example
Configure raw-body access and signature verification in a NestJS controller.
What this guide covers
- Enable raw request-body access for the webhook route.
- Verify the signature before passing the event to application services.
- Deduplicate event processing before producing business side effects.
Enable NestJS raw body support
For the Express adapter, pass rawBody: true when creating the Nest application. Nest then exposes the captured Buffer through RawBodyRequest<Request>.rawBody.
import { NestFactory } from "@nestjs/core"
import type {
NestExpressApplication,
} from "@nestjs/platform-express"
import { AppModule } from "./app.module"
async function bootstrap() {
const app =
await NestFactory.create<NestExpressApplication>(
AppModule,
{ rawBody: true }
)
await app.listen(process.env.PORT ?? 3000)
}
void bootstrap()Verify in the controller
import {
BadRequestException,
Controller,
Post,
type RawBodyRequest,
Req,
} from "@nestjs/common"
import type { Request } from "express"
import {
verifyYanolaWebhookSignature,
} from "@yanola/whatsapp"
@Controller("webhooks/yanola")
export class YanolaWebhookController {
@Post()
async receive(
@Req() request: RawBodyRequest<Request>
): Promise<void> {
const signingSecret =
process.env.YANOLA_WEBHOOK_SECRET
if (!signingSecret || !request.rawBody) {
throw new BadRequestException(
"Webhook verification is not configured"
)
}
const valid = verifyYanolaWebhookSignature({
secret: signingSecret,
signature: request.get("yanola-signature"),
rawBody: request.rawBody,
})
if (!valid) {
throw new BadRequestException(
"Invalid Yanola signature"
)
}
const event = JSON.parse(
request.rawBody.toString("utf8")
)
await acceptEventOnce(event)
}
}Use request.rawBody directly in the verifier. request.body has already been parsed by the adapter and must not be re-serialized for HMAC verification.
Register the controller
import { Module } from "@nestjs/common"
import {
YanolaWebhookController,
} from "./yanola-webhook.controller"
@Module({
controllers: [YanolaWebhookController],
})
export class AppModule {}Return after durable acceptance
A controller that resolves without an explicit response normally returns a 2xx Nest response. Ensure acceptEventOnce writes to a durable, uniquely keyed store before the method resolves.
- Put a unique constraint on the evt_ event ID.
- Treat duplicate inserts as already accepted.
- Queue slow handlers after the durable write.
- Use an exception only when Yanola should treat the attempt as failed.
Guards, pipes, and interceptors
Keep signature verification at the webhook boundary before business pipes or interceptors trust the parsed payload. Authentication intended for dashboard users should not block Yanola's signed server-to-server request.
Safe request logging can include yanola-event-id and yanola-delivery-id, but must exclude the secret, full signature, signed raw payload, and sensitive customer content.
Verify the integration
- Create an enabled Test Mode endpoint for /webhooks/yanola.
- Store its copy-once secret as YANOLA_WEBHOOK_SECRET.
- Send yanola.webhook.test from the dashboard.
- Confirm the controller stores one event and returns 2xx.
- Repeat or replay the event and confirm no duplicate side effect occurs.