You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 14通过Ngrok接收SVIX Webhook遇308重定向问题求助

Next.js 14 接收SVIX Webhook返回308永久重定向问题

我在Next.js 14应用中无法正常接收来自SVIX的Webhook。目前使用Ngrok将请求转发至本地运行的Next.js应用,但所有HTTP请求均返回308 Permanent Redirect状态码。希望解决该问题,当前仅需实现Webhook响应日志功能。

我的Next.js API路由(app/api/recallWebhook/route.js)

import crypto from "crypto";

export async function POST(request) {
  try {
    const body = await request.json();

    console.log("Webhook received:", body);

    const signingSecret = process.env.DEV_RECALL_WEBHOOK_SIGNING_SECRET;
    console.log("Signing secret:", signingSecret);

    const svixId = request.headers.get("svix-id");
    const svixTimestamp = request.headers.get("svix-timestamp");
    const svixSignature = request.headers.get("svix-signature");

    const signedContent = `${svixId}.${svixTimestamp}.${JSON.stringify(body)}`;
    const secretBytes = Buffer.from(signingSecret.split("_")[1], "base64");

    const computedSignature = crypto
      .createHmac("sha256", secretBytes)
      .update(signedContent)
      .digest("base64");

    const isSignatureValid = svixSignature.split(" ").some((sig) => {
      const [version, signature] = sig.split(",");
      return signature === computedSignature;
    });

    if (!isSignatureValid) {
      return new Response("Invalid signature", { status: 401 });
    }

    console.log("Verified webhook:", body);

    return new Response(
      JSON.stringify({
        message: "Webhook received and verified successfully",
      }),
      {
        status: 200,
        headers: {
          "Content-Type": "application/json",
        },
      }
    );
  } catch (error) {
    console.error("Error processing webhook", error);
    return new Response(
      JSON.stringify({ message: "Error processing webhook" }),
      {
        status: 500,
        headers: {
          "Content-Type": "application/json",
        },
      }
    );
  }
}

export function GET() {
  return new Response("GET method not supported", { status: 405 });
}

Ngrok服务器快照

Session Status                online
Account                       ******* (Plan: Free)
Version                       3.6.0
Region                        Europe (eu)
Latency                       38ms
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://****-***-248-85-9.ngrok-free.app -> http://localhost:3000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              103     4       0.00    0.01    6.05    149.09

HTTP Requests
-------------

POST /api/recallWebhook/       308 Permanent Redirect
POST /api/recallWebhook/       308 Permanent Redirect
POST /api/recallWebhook/       308 Permanent Redirect
POST /api/recallWebhook/       308 Permanent Redirect
POST /api/recallWebhook/       308 Permanent Redirect
POST /api/recallWebhook/       308 Permanent Redirect
POST /api/recallWebhook/       308 Permanent Redirect
POST /api/recallWebhook/       308 Permanent Redirect
POST /api/recallWebhook/       308 Permanent Redirect
POST /api/recallWebhook/       308 Permanent Redirect

Webhook请求端点URL

https://****-***-248-85-9.ngrok-free.app/api/recallWebhook/

解决方案

1. 核心原因

Next.js App Router的API路由默认会对末尾带斜杠的URL返回308重定向,指向不带斜杠的版本。你当前的请求URL末尾带/,触发了这个默认行为。

2. 快速修复:修改Webhook URL

直接在SVIX的Webhook配置中,把请求URL改为不带末尾斜杠的版本:
https://****-***-248-85-9.ngrok-free.app/api/recallWebhook

3. 备选方案:配置Next.js重定向规则

如果无法修改SVIX配置,可在next.config.js中添加重定向规则,将带斜杠的请求转发到目标路由:

/** @type {import('next').NextConfig} */
const nextConfig = {
  async redirects() {
    return [
      {
        source: '/api/recallWebhook/',
        destination: '/api/recallWebhook',
        permanent: false, // 用临时重定向避免浏览器缓存
      },
    ]
  },
}

module.exports = nextConfig

4. 先实现日志功能(跳过签名验证)

如果优先需要确认日志功能正常,可以暂时注释签名验证逻辑,确保请求能进入POST函数并打印日志:

import crypto from "crypto";

export async function POST(request) {
  try {
    const body = await request.json();

    console.log("Webhook received:", body);

    // 暂时注释签名验证,先测试日志功能
    /*
    const signingSecret = process.env.DEV_RECALL_WEBHOOK_SIGNING_SECRET;
    console.log("Signing secret:", signingSecret);

    const svixId = request.headers.get("svix-id");
    const svixTimestamp = request.headers.get("svix-timestamp");
    const svixSignature = request.headers.get("svix-signature");

    const signedContent = `${svixId}.${svixTimestamp}.${JSON.stringify(body)}`;
    const secretBytes = Buffer.from(signingSecret.split("_")[1], "base64");

    const computedSignature = crypto
      .createHmac("sha256", secretBytes)
      .update(signedContent)
      .digest("base64");

    const isSignatureValid = svixSignature.split(" ").some((sig) => {
      const [version, signature] = sig.split(",");
      return signature === computedSignature;
    });

    if (!isSignatureValid) {
      return new Response("Invalid signature", { status: 401 });
    }
    */

    console.log("Webhook logged successfully:", body);

    return new Response(
      JSON.stringify({
        message: "Webhook received and logged successfully",
      }),
      {
        status: 200,
        headers: {
          "Content-Type": "application/json",
        },
      }
    );
  } catch (error) {
    console.error("Error processing webhook", error);
    return new Response(
      JSON.stringify({ message: "Error processing webhook" }),
      {
        status: 500,
        headers: {
          "Content-Type": "application/json",
        },
      }
    );
  }
}

export function GET() {
  return new Response("GET method not supported", { status: 405 });
}

内容的提问来源于stack exchange,提问作者maxwtd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 03:21:09