Fix Discord interactions endpoint with proper raw body handling

cgen-3347352060874ae89711ff38495e64ab
This commit is contained in:
Builder.io 2025-11-06 07:34:45 +00:00
parent d103af162d
commit aaa9e3cd2c

View file

@ -1,10 +1,31 @@
import { VercelRequest, VercelResponse } from "@vercel/node"; import { VercelRequest, VercelResponse } from "@vercel/node";
import { createVerify } from "crypto"; import { createVerify } from "crypto";
export const config = {
api: {
bodyParser: {
raw: {
type: "application/json",
},
},
},
};
export default function handler(req: VercelRequest, res: VercelResponse) { export default function handler(req: VercelRequest, res: VercelResponse) {
// Only accept POST requests // Set CORS headers for Discord
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
res.setHeader(
"Access-Control-Allow-Headers",
"Content-Type, x-signature-ed25519, x-signature-timestamp"
);
if (req.method === "OPTIONS") {
return res.status(200).end();
}
if (req.method !== "POST") { if (req.method !== "POST") {
res.setHeader("Allow", "POST"); res.setHeader("Allow", "POST, OPTIONS");
return res.status(405).json({ error: "Method not allowed" }); return res.status(405).json({ error: "Method not allowed" });
} }
@ -13,29 +34,33 @@ export default function handler(req: VercelRequest, res: VercelResponse) {
const timestamp = req.headers["x-signature-timestamp"] as string; const timestamp = req.headers["x-signature-timestamp"] as string;
const publicKey = process.env.DISCORD_PUBLIC_KEY; const publicKey = process.env.DISCORD_PUBLIC_KEY;
console.log("[Discord] Interaction received at", new Date().toISOString()); console.log("[Discord] Interaction received");
console.log("[Discord] Has signature:", !!signature);
console.log("[Discord] Has timestamp:", !!timestamp);
console.log("[Discord] Has public key:", !!publicKey);
if (!publicKey) { if (!publicKey) {
console.error("[Discord] DISCORD_PUBLIC_KEY not set"); console.error("[Discord] DISCORD_PUBLIC_KEY not set");
return res.status(401).json({ error: "Server not configured" }); return res.status(500).json({ error: "Server not configured" });
} }
if (!signature || !timestamp) { if (!signature || !timestamp) {
console.error( console.error("[Discord] Missing required headers");
"[Discord] Missing headers - signature:",
!!signature,
"timestamp:",
!!timestamp,
);
return res.status(401).json({ error: "Invalid request" }); return res.status(401).json({ error: "Invalid request" });
} }
// Get raw body // Get raw body - Vercel sends it as Buffer with raw: true config
const rawBody = const rawBody =
typeof req.body === "string" ? req.body : JSON.stringify(req.body); req.body instanceof Buffer
? req.body.toString("utf8")
: typeof req.body === "string"
? req.body
: JSON.stringify(req.body);
// Verify signature // Verify signature
const message = `${timestamp}${rawBody}`; const message = `${timestamp}${rawBody}`;
console.log("[Discord] Verifying signature for message length:", message.length);
const signatureBuffer = Buffer.from(signature, "hex"); const signatureBuffer = Buffer.from(signature, "hex");
const verifier = createVerify("ed25519"); const verifier = createVerify("ed25519");
verifier.update(message); verifier.update(message);
@ -46,23 +71,31 @@ export default function handler(req: VercelRequest, res: VercelResponse) {
return res.status(401).json({ error: "Invalid signature" }); return res.status(401).json({ error: "Invalid signature" });
} }
const interaction = console.log("[Discord] Signature verified successfully");
typeof req.body === "string" ? JSON.parse(req.body) : req.body;
console.log("[Discord] Valid interaction type:", interaction.type); let interaction;
try {
interaction = typeof rawBody === "string" ? JSON.parse(rawBody) : rawBody;
} catch {
console.error("[Discord] Failed to parse JSON body");
return res.status(400).json({ error: "Invalid JSON" });
}
console.log("[Discord] Interaction type:", interaction?.type);
// Discord sends a PING to verify the endpoint // Discord sends a PING to verify the endpoint
if (interaction.type === 1) { if (interaction?.type === 1) {
console.log("[Discord] ✓ PING verified"); console.log("[Discord] ✓ PING verified");
return res.json({ type: 1 }); return res.status(200).json({ type: 1 });
} }
// For all other interactions, acknowledge them // For all other interactions, acknowledge them
return res.json({ return res.status(200).json({
type: 4, type: 4,
data: { content: "Interaction received" }, data: { content: "Interaction received" },
}); });
} catch (err: any) { } catch (err: any) {
console.error("[Discord] Error:", err?.message); console.error("[Discord] Error:", err?.message, err?.stack);
return res.status(500).json({ error: "Server error" }); return res.status(500).json({ error: "Server error" });
} }
} }