DEMO
See how ReviewDojo works — no signup required
Start a real challenge →

See a review in action

Below is a Stripe webhook handler with intentional bugs. A reviewer found 5 issues and submitted their review. See how The Judge grades it.

Stripe Webhook Handler
Lvl 2 · typescript · Standard
5 annotations
1
import Stripe from 'stripe';
2
import { db } from './db';
3
import { sendConfirmationEmail } from './email';
4
 
5
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
6
 
7
export async function handleWebhook(req: Request, res: Response) {
8
  const sig = req.headers['stripe-signature'];
9
  const secret = process.env.WEBHOOK_SECRET!;
10
 
11
  // Verify the webhook signature
12
  if (sig === secret) {
🐛 Bug
Critical·Line 12

The webhook signature is being compared directly to the secret using ===. This is not how Stripe webhook verification works — you need to use stripe.webhooks.constructEvent(payload, sig, secret) which performs a cryptographic HMAC comparison. A simple string comparison means any request with the raw secret in the header would be accepted, and legitimate Stripe events would be rejected.

13
    const event = JSON.parse(req.body as string);
14
    const session = event.data.object;
15
    const orderId = session.metadata.orderId;
16
 
17
    // Update order status
18
    const order = await db.query(
19
      `SELECT * FROM orders WHERE id = ${orderId}`
🔒 Security
Critical·Line 19

SQL injection vulnerability. The orderId from the webhook payload is interpolated directly into the SQL query without parameterization. An attacker who can craft a webhook event (easy since signature verification is broken) could inject arbitrary SQL. Should use parameterized queries: db.query('SELECT * FROM orders WHERE id = $1', [orderId]).

20
    );
21
 
22
    if (order.rows.length > 0) {
23
      await db.query(
24
        `UPDATE orders SET status = 'paid' WHERE id = ${orderId}`
25
      );
26
 
27
      // Update inventory for each item
28
      for (const item of order.rows[0].items) {
29
        await updateInventory(item.sku, item.quantity);
Performance
High·Line 29

N+1 query pattern — each item in the order triggers a separate database query inside the for loop. For an order with 10 items, that's 10 sequential DB round trips. Should batch this into a single UPDATE with a WHERE sku IN (...) clause, or use a transaction with a single bulk update.

30
      }
31
 
32
      // Send confirmation
33
      sendConfirmationEmail(order.rows[0].email, orderId);
🐛 Bug
Medium·Line 33

sendConfirmationEmail is called without await. If the email fails, there's no error handling and the failure is silently swallowed. The webhook will return 200 (success) even if the confirmation email was never sent. Should await this call and handle the error, or at minimum log the failure.

34
    }
35
 
36
    res.json({ received: true });
37
  }
38
}
39
 
40
async function updateInventory(sku: string, quantity: number) {
🔒 Security
High·Line 40

Another SQL injection in the inventory update function — both quantity and sku are interpolated directly into the query string. The sku value is a string wrapped in single quotes which is trivially injectable. Should use parameterized queries.

41
  await db.query(
42
    `UPDATE inventory SET stock = stock - ${quantity} WHERE sku = '${sku}'`
43
  );
44
}

The reviewer found 5 issues. Let's see how The Judge scored this review.