← Files Upstash RedisARCHIVED FILE

skills/upstash-qstash-js/verification/platform-specific/nextjs.md

4.21 KB · Oct 3, 2026 · 06:22 UTC

↓ Download file

# Next.js Endpoint Verification

## Overview

Next.js applications can use QStash in both App Router (route handlers) and Pages Router (API routes). The SDK provides dedicated verification utilities for each.

## App Router Verification

### Using verifySignatureAppRouter

The SDK provides `verifySignatureAppRouter` for App Router route handlers:

```typescript
import { verifySignatureAppRouter } from "@upstash/qstash/nextjs";

// app/api/webhook/route.ts
export const POST = verifySignatureAppRouter(async (req) => {
  const body = await req.json();

  // Request is verified - process it
  console.log("Received verified message:", body);

  return new Response("OK", { status: 200 });
});
```

### With Custom Configuration

```typescript
import { verifySignatureAppRouter } from "@upstash/qstash/nextjs";

export const POST = verifySignatureAppRouter(
  async (req) => {
    const body = await req.json();
    return Response.json({ received: true });
  },
  {
    currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY,
    nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY,
    clockTolerance: 5, // Allow 5 seconds clock difference
  }
);
```

### Multi-Region Support

```typescript
import { verifySignatureAppRouter } from "@upstash/qstash/nextjs";

export const POST = verifySignatureAppRouter(async (req) => {
  const upstashRegion = req.headers.get("upstash-region");
  console.log("Request from region:", upstashRegion);

  const body = await req.json();
  return Response.json({ region: upstashRegion, data: body });
});
```

## Pages Router Verification

### Using verifySignature

For Pages Router API routes, use the `verifySignature` wrapper:

```typescript
import type { NextApiRequest, NextApiResponse } from "next";
import { verifySignature } from "@upstash/qstash/nextjs";

// pages/api/webhook.ts
async function handler(req: NextApiRequest, res: NextApiResponse) {
  const body = req.body;

  // Request is verified
  console.log("Received:", body);

  res.status(200).json({ success: true });
}

export default verifySignature(handler);
```

### With Configuration

```typescript
import type { NextApiRequest, NextApiResponse } from "next";
import { verifySignature } from "@upstash/qstash/nextjs";

async function handler(req: NextApiRequest, res: NextApiResponse) {
  res.status(200).json({ message: "Verified" });
}

export default verifySignature(handler, {
  currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY,
  nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY,
  clockTolerance: 5,
});
```

## Manual Verification with Receiver

For more control, use the `Receiver` class directly:

### Manual Verification

```typescript
import { Receiver } from "@upstash/qstash";
import { NextRequest, NextResponse } from "next/server";

const receiver = new Receiver({
  currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY!,
  nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY!,
});

export async function POST(req: NextRequest) {
  const signature = req.headers.get("upstash-signature");

  if (!signature) {
    return NextResponse.json({ error: "Missing signature" }, { status: 401 });
  }

  const body = await req.text();

  try {
    await receiver.verify({
      signature,
      body,
      url: req.url,
    });

    // Verified - parse and process
    const data = JSON.parse(body);
    return NextResponse.json({ success: true });
  } catch (error) {
    return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
  }
}
```

## Best Practices

### Handle Errors Gracefully

```typescript
export const POST = verifySignatureAppRouter(async (req) => {
  try {
    const body = await req.json();
    await processWebhook(body);
    return Response.json({ success: true });
  } catch (error) {
    console.error("Processing error:", error);
    return Response.json({ error: "Processing failed" }, { status: 500 });
  }
});
```

## Common Issues

### Missing Environment Variables

```typescript
if (!process.env.QSTASH_CURRENT_SIGNING_KEY) {
  throw new Error("Missing QSTASH_CURRENT_SIGNING_KEY");
}
```

## Related Resources

- [General Receiver Documentation](../receiver.md)
- [Multi-Region Setup](../../advanced/multi-region/summary.md)
- [Next.js Documentation](https://nextjs.org/docs)
- [Vercel Deployment](https://vercel.com/docs)

SHA-256: 8a422e865248f16f9ef79bfc48f3c3a7ee904814701178d0ed9c1ab5e840c4a7