Fix Insecure Webhooks in AdonisJS
Webhooks are high-value targets for spoofing and replay attacks. In AdonisJS, if you process incoming webhooks without cryptographic verification, you're effectively exposing internal logic to the public internet. Attackers can forge events, manipulate data, or trigger unintended side effects by simply POSTing a JSON payload to your endpoint. To secure this, you must implement HMAC signature verification using the raw request body.
The Vulnerable Pattern
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'export default class WebhooksController { public async handle({ request, response }: HttpContextContract) { // DANGER: Blindly trusting the parsed body const payload = request.all()
if (payload.type === 'payment.succeeded') { // This logic can be triggered by anyone with the URL await fulfillOrder(payload.data.orderId) } return response.ok({ status: 'success' })
} }
The Secure Implementation
The secure implementation relies on three critical security principles. First, we use `request.raw()` because standard body parsers can modify the spacing or key order of JSON, which invalidates the HMAC. Second, we generate a local hash using a shared secret and the SHA-256 algorithm. Third, we use `crypto.timingSafeEqual` to compare the signatures; standard string comparison (`===`) is vulnerable to timing attacks where an attacker can determine the correct signature one character at a time by measuring the response latency.
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' import crypto from 'crypto' import Env from '@ioc:Adonis/Core/Env'export default class WebhooksController { public async handle({ request, response }: HttpContextContract) { const signature = request.header(‘X-Webhook-Signature’) const secret = Env.get(‘WEBHOOK_SECRET’) const rawBody = request.raw() // Essential: use the unparsed body
if (!signature || !rawBody) { return response.unauthorized('Missing signature') } const hmac = crypto.createHmac('sha256', secret) const expectedSignature = hmac.update(rawBody).digest('hex') // Prevent timing attacks with timingSafeEqual const isValid = crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ) if (!isValid) { return response.badRequest('Invalid signature') } const payload = JSON.parse(rawBody) // Process verified payload... return response.ok({ status: 'verified' })
} }
Protect your AdonisJS API
Don't rely on manual checks. GuardAPI's Gemini 3 Pro engine detects Insecure Webhooks and logic flaws in seconds.
Run Automated AuditVerified by Ghost Labs Security Team
This content is continuously validated by our automated security engine and reviewed by our research team. Ghost Labs analyzes over 500+ vulnerability patterns across 40+ frameworks to provide up-to-date remediation strategies.