From 62b651c47656e26e6115a4b85512d13489a46a1a Mon Sep 17 00:00:00 2001 From: Dominic Ferrando Date: Fri, 26 Jun 2026 13:44:19 -0400 Subject: [PATCH] Setup dev printful/webflow webhooks --- apps/api/src/env.ts | 2 +- apps/api/src/scripts/printful.ts | 16 +++++++++++++ apps/api/src/server.ts | 1 + packages/commerce/src/printful.ts | 35 +++++++++++++++++++++++++++++ packages/commerce/src/util/types.ts | 5 +++++ 5 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 apps/api/src/scripts/printful.ts diff --git a/apps/api/src/env.ts b/apps/api/src/env.ts index 03f3c0d..40283c1 100644 --- a/apps/api/src/env.ts +++ b/apps/api/src/env.ts @@ -16,7 +16,7 @@ export const env = { WEBFLOW_SITE_ID: requireEnv("WEBFLOW_SITE_ID"), WEBFLOW_COLLECTION_ID: requireEnv("WEBFLOW_COLLECTION_ID"), WEBFLOW_AUTH: requireEnv("WEBFLOW_AUTH"), - WEBFLOW_WEBHOOK_SECRET: optionEnv("WEBFLOW_WEBHOOK_SECRET"), + WEBFLOW_WEBHOOK_SECRET: requireEnv("WEBFLOW_WEBHOOK_SECRET"), NODE_ENV: optionEnv("NODE_ENV", "development"), LOG_LEVEL: optionEnv("LOG_LEVEL", "info") }; diff --git a/apps/api/src/scripts/printful.ts b/apps/api/src/scripts/printful.ts new file mode 100644 index 0000000..2871b42 --- /dev/null +++ b/apps/api/src/scripts/printful.ts @@ -0,0 +1,16 @@ +import { Printful, PrintfulClient } from "@blade-and-brawn/commerce"; +import { env } from "../env"; + +const printful = new PrintfulClient({ + token: env.PRINTFUL_AUTH, + storeId: env.PRINTFUL_STORE_ID, +}); + +await printful.Webhooks.register("https://dev.bladeandbrawn.com/webhook/printful", [ + Printful.Webhook.Event.ProductUpdated, + Printful.Webhook.Event.ProductDeleted, + Printful.Webhook.Event.PackageShipped, +]); + +const config = await printful.Webhooks.getConfig(); +console.log(config); diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index a1f2169..7060acf 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -162,6 +162,7 @@ export const app = new Elysia() // WEBHOOKS .post("/webhook/printful", async ({ body }) => { + // https://webflow.com/integrations/printful const payload = body as Printful.Webhook.EventPayload; switch (payload.type) { diff --git a/packages/commerce/src/printful.ts b/packages/commerce/src/printful.ts index 4dc1c6f..f0e47d6 100644 --- a/packages/commerce/src/printful.ts +++ b/packages/commerce/src/printful.ts @@ -121,9 +121,43 @@ class OrdersClient { } } +class WebhooksClient { + constructor(private creds: Credentials) { } + + async getConfig(): Promise { + const res = await fetch(`${this.creds.apiUrl}/webhooks`, { + method: "GET", + headers: { + "Content-Type": "application/json", + ...this.creds.authHeaders, + } + }); + if (res.status === 404 || res.status === 400) return; + if (!res.ok) throw new PrintfulError("Failed to get webhook configuration", res.status, await parsePayload(res)); + const payload = (await res.json()) as Printful.Webhook.Config; + return payload; + } + + async register(url: string, events: Printful.Webhook.Event[]) { + const res = await fetch(`${this.creds.apiUrl}/webhooks`, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...this.creds.authHeaders, + }, + body: JSON.stringify({ + "url": url, + "types": events + }), + }); + if (!res.ok) throw new PrintfulError("Failed to register webhook URL", res.status, await parsePayload(res)); + } +} + export class PrintfulClient { readonly Products: ProductsClient; readonly Orders: OrdersClient; + readonly Webhooks: WebhooksClient; constructor(options: ClientOptions) { const creds: Credentials = { @@ -135,6 +169,7 @@ export class PrintfulClient { }; this.Products = new ProductsClient(creds); this.Orders = new OrdersClient(creds); + this.Webhooks = new WebhooksClient(creds); } static Util = { diff --git a/packages/commerce/src/util/types.ts b/packages/commerce/src/util/types.ts index c693cb2..f9679fe 100644 --- a/packages/commerce/src/util/types.ts +++ b/packages/commerce/src/util/types.ts @@ -133,6 +133,11 @@ export namespace Printful { PackageShipped = "package_shipped", } + export interface Config { + url: string, + types: Event[] + } + export interface ProductUpdated extends MetaData< Event.ProductUpdated, {