Dịch vụ bên ngoài không thể gọi trực tiếp localhost. Tunnel cung cấp URL HTTPS công khai và chuyển tiếp headers thật cùng body nguyên bản tới server cục bộ. Giữ raw body trước JSON parser. Xác minh chữ ký và dữ liệu, lưu lần gửi bền vững rồi mới chạy business logic.
Cách HubSpot webhook đi tới ứng dụng cục bộ
Dịch vụ bên ngoài không thể gọi trực tiếp localhost. Tunnel cung cấp URL HTTPS công khai và chuyển tiếp headers thật cùng body nguyên bản tới server cục bộ. >tài liệu chính thức
1. Tạo endpoint giữ nguyên raw body
Giữ raw body trước JSON parser. Xác minh chữ ký và dữ liệu, lưu lần gửi bền vững rồi mới chạy business logic.
import express from "express";
import crypto from "node:crypto";
const app = express();
const secret = process.env.HUBSPOT_CLIENT_SECRET;
const publicBase = process.env.WEBHOOK_PUBLIC_BASE_URL;
app.post(
"/webhooks/hubspot",
express.raw({ type: "application/json", limit: "1mb" }),
async (req, res) => {
const signature = req.get("x-hubspot-signature-v3");
const timestamp = req.get("x-hubspot-request-timestamp");
const rawBody = req.body.toString("utf8");
if (!verifyHubSpotV3({
signature,
timestamp,
method: req.method,
publicUri: `${publicBase}${req.originalUrl}`,
rawBody,
secret,
})) {
return res.sendStatus(401);
}
let events;
try {
events = JSON.parse(rawBody);
if (!Array.isArray(events)) throw new Error("Expected a batch");
await enqueueBatchIdempotently(events);
} catch (error) {
console.error("HubSpot webhook rejected", error);
return res.sendStatus(500);
}
return res.sendStatus(200);
}
);
app.use(express.json());
app.listen(3000);
Đăng ký URL HTTPS đầy đủ, chỉ chọn event cần thiết và kích hoạt trường hợp thật trong môi trường thử nghiệm. Lưu secret ngoài repository.
2. Công khai cổng cục bộ qua HTTPS
Khởi động server và giữ tunnel chạy trong terminal khác. Phản hồi 401 cho request không ký xác nhận routing hoạt động và xác thực từ chối đúng.
npx portpreview 3000
https://example.portpreview.dev/webhooks/hubspot
Khởi động server và giữ tunnel chạy trong terminal khác. Phản hồi 401 cho request không ký xác nhận routing hoạt động và xác thực từ chối đúng.
3. Cấu hình webhook trong HubSpot
Đăng ký URL HTTPS đầy đủ, chỉ chọn event cần thiết và kích hoạt trường hợp thật trong môi trường thử nghiệm. Lưu secret ngoài repository. >tài liệu chính thức
Chỉ xử lý type đã biết, chấp nhận field bổ sung và khử trùng lặp bằng ID lần gửi ổn định trong storage bền vững.
Xác minh chữ ký trên đúng chuỗi byte
Tính HMAC bằng webhook secret và dữ liệu chính xác theo đặc tả. So sánh constant-time và không ghi secret vào log. >tài liệu chính thức
function decodeHubSpotQuery(uri) {
const [base, query] = uri.split("?", 2);
if (query === undefined) return base;
const map = {
"%3A": ":", "%2F": "/", "%3F": "?", "%40": "@",
"%21": "!", "%24": "$", "%27": "'", "%28": "(",
"%29": ")", "%2A": "*", "%2C": ",", "%3B": ";",
};
const decoded = query.replace(
/%3A|%2F|%3F|%40|%21|%24|%27|%28|%29|%2A|%2C|%3B/g,
value => map[value]
);
return `${base}?${decoded}`;
}
function verifyHubSpotV3(input) {
if (!input.secret || !input.signature || !input.timestamp) return false;
const sentAt = Number(input.timestamp);
if (!Number.isFinite(sentAt) || Math.abs(Date.now() - sentAt) > 300_000) {
return false;
}
const uri = decodeHubSpotQuery(input.publicUri.split("#")[0]);
const source = `${input.method}${uri}${input.rawBody}${input.timestamp}`;
const expected = crypto
.createHmac("sha256", input.secret)
.update(source, "utf8")
.digest("base64");
const actualBuffer = Buffer.from(input.signature, "utf8");
const expectedBuffer = Buffer.from(expected, "utf8");
return actualBuffer.length === expectedBuffer.length &&
crypto.timingSafeEqual(actualBuffer, expectedBuffer);
}
Kiểm tra độ mới của timestamp sau khi chữ ký hợp lệ và đồng bộ đồng hồ host.
Tính HMAC bằng webhook secret và dữ liệu chính xác theo đặc tả. So sánh constant-time và không ghi secret vào log. >hướng dẫn thực hành
Phản hồi nhanh và xử lý idempotent
Chỉ xử lý type đã biết, chấp nhận field bổ sung và khử trùng lặp bằng ID lần gửi ổn định trong storage bền vững.
Giữ ID lần gửi và tạo job trong cùng transaction trước khi trả 200. Nếu không thể lưu bền vững, trả lỗi để nhà cung cấp retry. >hướng dẫn thực hành
Khắc phục lỗi HubSpot webhook
- Khởi động server và giữ tunnel chạy trong terminal khác. Phản hồi 401 cho request không ký xác nhận routing hoạt động và xác thực từ chối đúng.
- Tính HMAC bằng webhook secret và dữ liệu chính xác theo đặc tả. So sánh constant-time và không ghi secret vào log.
- Kiểm tra routing, headers, chữ ký, thời gian phản hồi và idempotency. Request tổng hợp phù hợp nhất để thử nhánh từ chối.
- Kiểm tra POST path, port của tunnel, raw body, secret, đồng hồ hệ thống và độ trễ cơ sở dữ liệu.
>hướng dẫn thực hành · >hướng dẫn thực hành
Danh sách bảo mật cho môi trường local và production
- Bắt buộc HTTPS, giới hạn kích thước và method, bảo vệ secrets, che dữ liệu log và xóa URL thử nghiệm cũ.
- Giữ raw body trước JSON parser. Xác minh chữ ký và dữ liệu, lưu lần gửi bền vững rồi mới chạy business logic.
- Giữ ID lần gửi và tạo job trong cùng transaction trước khi trả 200. Nếu không thể lưu bền vững, trả lỗi để nhà cung cấp retry.
Bắt buộc HTTPS, giới hạn kích thước và method, bảo vệ secrets, che dữ liệu log và xóa URL thử nghiệm cũ.
