Add shared printful webhook secret for authentication

This commit is contained in:
Dominic Ferrando
2026-07-18 00:44:54 -04:00
parent 169034a63c
commit 2c356e6707
3 changed files with 10 additions and 3 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ import { env } from "../util";
const DOMAIN = "dev.api.bladeandbrawn.com"; const DOMAIN = "dev.api.bladeandbrawn.com";
const PRINTFUL_WEBHOOK_URL = `http://${DOMAIN}/webhooks/printful`; const PRINTFUL_WEBHOOK_URL = `http://${DOMAIN}/webhooks/printful?secret=${env.PRINTFUL_WEBHOOK_SECRET}`;
const WEBFLOW_WEBHOOK_URL = `https://${DOMAIN}/webhooks/webflow`; const WEBFLOW_WEBHOOK_URL = `https://${DOMAIN}/webhooks/webflow`;
function printError(err: unknown): never { function printError(err: unknown): never {
+8 -2
View File
@@ -322,8 +322,11 @@ export const app = new Elysia()
) )
// WEBHOOKS // WEBHOOKS
.post("/webhooks/printful", async ({ body }) => { .post("/webhooks/printful", async ({ body, query }) => {
// https://webflow.com/integrations/printful // https://webflow.com/integrations/printful
const verified = crypto.timingSafeEqual(Buffer.from(query.secret, "hex"), Buffer.from(env.PRINTFUL_WEBHOOK_SECRET, "hex"));
if (!verified) throw status(400, "Invalid secret");
const payload = body as Printful.Webhook.EventPayload; const payload = body as Printful.Webhook.EventPayload;
switch (payload.type) { switch (payload.type) {
@@ -360,6 +363,9 @@ export const app = new Elysia()
const shipment = payload.data.shipment; const shipment = payload.data.shipment;
log.info({ wOrderId, carrier: shipment.carrier, tracking: shipment.tracking_number }, "printful webhook: package shipped"); log.info({ wOrderId, carrier: shipment.carrier, tracking: shipment.tracking_number }, "printful webhook: package shipped");
// TODO: possible to have multiple packages shipped
// order should NOT be immediately fulfulled in that case
await s.Commerce.Apparel.Orders.Queue.enqueue({ await s.Commerce.Apparel.Orders.Queue.enqueue({
type: "apparel_order_fulfill", type: "apparel_order_fulfill",
source: "printful", source: "printful",
@@ -371,7 +377,7 @@ export const app = new Elysia()
default: default:
log.warn({ type: (payload as any).type }, "printful webhook: unhandled event type"); log.warn({ type: (payload as any).type }, "printful webhook: unhandled event type");
} }
}) }, { query: t.Object({ secret: t.String() }) })
.post("/webhooks/webflow", async ({ request, body }) => { .post("/webhooks/webflow", async ({ request, body }) => {
if (!s.Commerce.Webflow.Util.verifyWebflowSignature(request, body)) if (!s.Commerce.Webflow.Util.verifyWebflowSignature(request, body))
throw status(400, "Invalid signature"); throw status(400, "Invalid signature");
+1
View File
@@ -11,6 +11,7 @@ export const log = pino({
export const env = { export const env = {
PRINTFUL_AUTH: requireEnv("PRINTFUL_AUTH"), PRINTFUL_AUTH: requireEnv("PRINTFUL_AUTH"),
PRINTFUL_STORE_ID: requireEnv("PRINTFUL_STORE_ID"), PRINTFUL_STORE_ID: requireEnv("PRINTFUL_STORE_ID"),
PRINTFUL_WEBHOOK_SECRET: requireEnv("PRINTFUL_WEBHOOK_SECRET"),
WEBFLOW_SITE_ID: requireEnv("WEBFLOW_SITE_ID"), WEBFLOW_SITE_ID: requireEnv("WEBFLOW_SITE_ID"),
WEBFLOW_COLLECTION_SKUS_ID: requireEnv("WEBFLOW_COLLECTION_SKUS_ID"), WEBFLOW_COLLECTION_SKUS_ID: requireEnv("WEBFLOW_COLLECTION_SKUS_ID"),
WEBFLOW_COLLECTION_PRODUCTS_ID: requireEnv("WEBFLOW_COLLECTION_PRODUCTS_ID"), WEBFLOW_COLLECTION_PRODUCTS_ID: requireEnv("WEBFLOW_COLLECTION_PRODUCTS_ID"),