57 lines
1.6 KiB
JavaScript
57 lines
1.6 KiB
JavaScript
require("dotenv").config();
|
|
const express = require("express");
|
|
const crypto = require("crypto");
|
|
|
|
const router = express.Router();
|
|
|
|
router.use(express.raw({ type: "*/*" }));
|
|
|
|
const SHOPIFY_API_SECRET = process.env.SHOPIFY_API_SECRET;
|
|
|
|
function verifyHmac(rawBody, hmacHeader) {
|
|
if (!SHOPIFY_API_SECRET || !hmacHeader || !rawBody) {
|
|
return false;
|
|
}
|
|
|
|
const digest = crypto
|
|
.createHmac("sha256", SHOPIFY_API_SECRET)
|
|
.update(rawBody)
|
|
.digest("base64");
|
|
|
|
const generated = Buffer.from(digest, "utf8");
|
|
const received = Buffer.from(hmacHeader, "utf8");
|
|
if (generated.length !== received.length) {
|
|
return false;
|
|
}
|
|
|
|
return crypto.timingSafeEqual(generated, received);
|
|
}
|
|
|
|
function parseJsonSafe(buf) {
|
|
try {
|
|
return JSON.parse(buf.toString("utf8"));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function handleWebhook(req, res, topicName) {
|
|
const hmacHeader = req.header("x-shopify-hmac-sha256");
|
|
const shop = req.header("x-shopify-shop-domain");
|
|
const topic = req.header("x-shopify-topic") || topicName;
|
|
|
|
if (!verifyHmac(req.body, hmacHeader)) {
|
|
return res.status(401).send("Invalid HMAC");
|
|
}
|
|
|
|
const payload = parseJsonSafe(req.body) || {};
|
|
console.log(`[WEBHOOK:${topic}] shop=${shop}`, payload);
|
|
return res.status(200).json({ status: "ok", topic, shop, received: payload });
|
|
}
|
|
|
|
router.post("/customers/data_request", (req, res) => handleWebhook(req, res, "customers/data_request"));
|
|
router.post("/customers/redact", (req, res) => handleWebhook(req, res, "customers/redact"));
|
|
router.post("/shop/redact", (req, res) => handleWebhook(req, res, "shop/redact"));
|
|
|
|
module.exports = router;
|