@whatalo/plugin-sdk 1.2.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/adapters/express.cjs +5 -0
  2. package/dist/adapters/express.cjs.map +1 -1
  3. package/dist/adapters/express.d.cts +2 -2
  4. package/dist/adapters/express.d.ts +2 -2
  5. package/dist/adapters/express.mjs +5 -0
  6. package/dist/adapters/express.mjs.map +1 -1
  7. package/dist/adapters/hono.cjs +5 -0
  8. package/dist/adapters/hono.cjs.map +1 -1
  9. package/dist/adapters/hono.d.cts +2 -2
  10. package/dist/adapters/hono.d.ts +2 -2
  11. package/dist/adapters/hono.mjs +5 -0
  12. package/dist/adapters/hono.mjs.map +1 -1
  13. package/dist/adapters/nextjs.cjs +5 -0
  14. package/dist/adapters/nextjs.cjs.map +1 -1
  15. package/dist/adapters/nextjs.d.cts +2 -2
  16. package/dist/adapters/nextjs.d.ts +2 -2
  17. package/dist/adapters/nextjs.mjs +5 -0
  18. package/dist/adapters/nextjs.mjs.map +1 -1
  19. package/dist/bridge/index.cjs +27 -3
  20. package/dist/bridge/index.cjs.map +1 -1
  21. package/dist/bridge/index.mjs +27 -3
  22. package/dist/bridge/index.mjs.map +1 -1
  23. package/dist/index.cjs +33 -4
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +3 -3
  26. package/dist/index.d.ts +3 -3
  27. package/dist/index.mjs +33 -4
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/manifest/index.d.cts +1 -1
  30. package/dist/manifest/index.d.ts +1 -1
  31. package/dist/{types-BhtVJPgF.d.ts → types-Bzee9Hvr.d.ts} +1 -1
  32. package/dist/{types-Bo9GbqrM.d.ts → types-CDKGAQ3o.d.ts} +22 -0
  33. package/dist/{types-rA6kVlpV.d.cts → types-CPnSzOfX.d.cts} +22 -0
  34. package/dist/{types-BU71_xQm.d.cts → types-CqyAhbXM.d.cts} +1 -1
  35. package/dist/webhooks/index.cjs +5 -0
  36. package/dist/webhooks/index.cjs.map +1 -1
  37. package/dist/webhooks/index.d.cts +1 -1
  38. package/dist/webhooks/index.d.ts +1 -1
  39. package/dist/webhooks/index.mjs +5 -0
  40. package/dist/webhooks/index.mjs.map +1 -1
  41. package/package.json +1 -1
@@ -91,6 +91,9 @@ function isRuntimeCustomerWebhookPayload(value) {
91
91
  function isRuntimeCheckoutCompletedWebhookPayload(value) {
92
92
  return hasValidBasePayloadFields(value) && hasStringId(value.order) && hasStringId(value.customer) && isRuntimeWebhookStore(value.store);
93
93
  }
94
+ function isRuntimeCheckoutAbandonedWebhookPayload(value) {
95
+ return hasValidBasePayloadFields(value) && hasStringId(value.checkout) && isRuntimeWebhookStore(value.store);
96
+ }
94
97
  function isRuntimeWebhookPayload(value, event) {
95
98
  if (!isRecord(value)) return false;
96
99
  switch (event) {
@@ -108,6 +111,8 @@ function isRuntimeWebhookPayload(value, event) {
108
111
  return isRuntimeCustomerWebhookPayload(value);
109
112
  case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:
110
113
  return isRuntimeCheckoutCompletedWebhookPayload(value);
114
+ case WEBHOOK_EVENTS.CHECKOUT_ABANDONED:
115
+ return isRuntimeCheckoutAbandonedWebhookPayload(value);
111
116
  default:
112
117
  return false;
113
118
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/express.ts","../../src/webhooks/verify.ts","../../src/webhooks/types.ts"],"sourcesContent":["import { verifyWebhook } from \"../webhooks/verify.js\";\nimport { isRuntimeWebhookPayload, isWebhookEvent } from \"../webhooks/types.js\";\nimport type { WebhookEventHandler, WebhookHandlerOptions } from \"./types.js\";\n\ntype ExpressLikeRequest = {\n body: Buffer | string;\n headers: Record<string, string | string[] | undefined>;\n};\n\ntype ExpressLikeStatusResponse = {\n json: (body: unknown) => void;\n send: (body: string) => void;\n};\n\ntype ExpressLikeResponse = {\n status: (code: number) => ExpressLikeStatusResponse;\n};\n\nfunction getHeader(\n name: string,\n headers: Record<string, string | string[] | undefined>\n): string {\n const value = headers[name];\n\n if (Array.isArray(value)) {\n return value[0] ?? \"\";\n }\n\n return value ?? \"\";\n}\n\nexport function createWebhookHandler(\n options: WebhookHandlerOptions\n): (req: ExpressLikeRequest, res: ExpressLikeResponse) => Promise<void> {\n return async function webhookHandler(\n req: ExpressLikeRequest,\n res: ExpressLikeResponse\n ): Promise<void> {\n try {\n const rawPayload =\n typeof req.body === \"string\" ? req.body : req.body.toString(\"utf8\");\n const signature = getHeader(\"x-webhook-signature\", req.headers);\n const timestamp = getHeader(\"x-webhook-timestamp\", req.headers);\n const event = getHeader(\"x-webhook-event\", req.headers);\n const deliveryId = getHeader(\"x-webhook-id\", req.headers);\n\n const isValid = verifyWebhook({\n payload: rawPayload,\n signature,\n timestamp,\n secret: options.secret,\n });\n\n if (!isValid) {\n res.status(401).json({ error: \"Invalid signature\" });\n return;\n }\n\n if (!event) {\n res.status(400).json({ error: \"Missing event header\" });\n return;\n }\n\n if (!isWebhookEvent(event)) {\n res.status(400).json({ error: \"Invalid event header\" });\n return;\n }\n\n let parsedPayload: unknown;\n try {\n parsedPayload = JSON.parse(rawPayload) as unknown;\n } catch {\n res.status(400).json({ error: \"Invalid JSON\" });\n return;\n }\n\n if (!isRuntimeWebhookPayload(parsedPayload, event)) {\n res.status(400).json({ error: \"Invalid payload\" });\n return;\n }\n\n const context = { deliveryId, event, timestamp };\n const handler = options.handlers[event] as\n | WebhookEventHandler<typeof event>\n | undefined;\n\n if (handler) {\n await handler(parsedPayload, context);\n } else if (options.onUnhandledEvent) {\n await options.onUnhandledEvent(event, parsedPayload, context);\n }\n\n res.status(200).send(\"OK\");\n } catch {\n res.status(500).json({ error: \"Internal error\" });\n }\n };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst DEFAULT_TOLERANCE_SECONDS = 300;\n\nexport interface VerifyWebhookParams {\n /** Raw request body as a string (NOT parsed JSON) */\n payload: string;\n /** Value of the X-Webhook-Signature header */\n signature: string;\n /** Value of the X-Webhook-Timestamp header (Unix epoch in seconds) */\n timestamp: string | number;\n /** App's webhook secret (provided during app installation) */\n secret: string;\n /** Maximum timestamp drift allowed in seconds. Defaults to 300 seconds. */\n toleranceSeconds?: number;\n /** Current Unix epoch time in seconds. Intended for tests. */\n currentTime?: number;\n}\n\n/**\n * Verify webhook signature using HMAC-SHA256 over `${timestamp}.${payload}`.\n * Uses timing-safe comparison to prevent timing attacks.\n */\nexport function verifyWebhook({\n payload,\n signature,\n timestamp,\n secret,\n toleranceSeconds = DEFAULT_TOLERANCE_SECONDS,\n currentTime = Math.floor(Date.now() / 1000),\n}: VerifyWebhookParams): boolean {\n if (!payload || !signature || !secret || timestamp === \"\") return false;\n\n const timestampString = String(timestamp);\n const timestampValue =\n typeof timestamp === \"number\" ? timestamp : Number(timestamp);\n\n if (!Number.isInteger(timestampValue)) return false;\n if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) return false;\n if (!Number.isFinite(currentTime)) return false;\n\n const ageSeconds = Math.abs(currentTime - timestampValue);\n if (ageSeconds > toleranceSeconds) return false;\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestampString}.${payload}`, \"utf8\")\n .digest(\"hex\");\n\n if (expected.length !== signature.length) return false;\n if (!/^[a-f0-9]+$/i.test(signature)) return false;\n\n try {\n return timingSafeEqual(\n Buffer.from(expected, \"hex\"),\n Buffer.from(signature, \"hex\")\n );\n } catch {\n return false;\n }\n}\n","import {\n PUBLIC_WEBHOOK_EVENTS,\n isPublicWebhookEvent,\n type PublicWebhookEvent,\n} from \"@whatalo/protocol/events\";\nimport type { Product, Order, Customer } from \"../client/types.js\";\n\nexport const WEBHOOK_EVENTS = PUBLIC_WEBHOOK_EVENTS;\n\n/** All supported webhook event types */\nexport type WebhookEvent = PublicWebhookEvent;\n\n/** Validate an event header against the public runtime webhook event set. */\nexport function isWebhookEvent(value: string): value is WebhookEvent {\n return isPublicWebhookEvent(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasStringId(value: unknown): value is { id: string } {\n return isRecord(value) && typeof value.id === \"string\";\n}\n\nfunction isOptionalString(\n value: Record<string, unknown>,\n key: string\n): boolean {\n return value[key] === undefined || typeof value[key] === \"string\";\n}\n\nfunction isRuntimeWebhookStore(value: unknown): value is RuntimeWebhookStore {\n return (\n isRecord(value) &&\n typeof value.id === \"string\" &&\n typeof value.timezone === \"string\" &&\n (value.name === undefined || typeof value.name === \"string\")\n );\n}\n\nfunction hasValidBasePayloadFields(value: Record<string, unknown>): boolean {\n if (!isOptionalString(value, \"event_id\")) return false;\n if (!isOptionalString(value, \"occurred_at\")) return false;\n\n return isRuntimeWebhookStore(value.store);\n}\n\nfunction isRuntimeOrderWebhookPayload(value: Record<string, unknown>): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n (value.customer === undefined || hasStringId(value.customer))\n );\n}\n\nfunction isRuntimeProductWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.product);\n}\n\nfunction isRuntimeCustomerWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.customer);\n}\n\nfunction isRuntimeCheckoutCompletedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n hasStringId(value.customer) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nexport interface OrderCreatedWebhookData {\n order: Order;\n}\n\nexport interface OrderUpdatedWebhookData {\n order: Order;\n changes: string[];\n}\n\nexport interface OrderCancelledWebhookData {\n orderId: string;\n reason?: string;\n}\n\nexport interface OrderCompletedWebhookData {\n order: Order;\n}\n\nexport interface ProductCreatedWebhookData {\n product: Product;\n}\n\nexport interface ProductUpdatedWebhookData {\n product: Product;\n changes: string[];\n}\n\nexport interface ProductDeletedWebhookProduct {\n id: string;\n}\n\nexport interface ProductDeletedWebhookData {\n product: ProductDeletedWebhookProduct;\n}\n\nexport interface CustomerCreatedWebhookData {\n customer: Customer;\n}\n\nexport interface CustomerUpdatedWebhookData {\n customer: Customer;\n changes: string[];\n}\n\nexport interface CheckoutCompletedWebhookData {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Map of webhook events to their payload data shapes */\nexport interface WebhookEventData {\n \"order.created\": OrderCreatedWebhookData;\n \"order.updated\": OrderUpdatedWebhookData;\n \"order.cancelled\": OrderCancelledWebhookData;\n \"order.completed\": OrderCompletedWebhookData;\n \"product.created\": ProductCreatedWebhookData;\n \"product.updated\": ProductUpdatedWebhookData;\n \"product.deleted\": ProductDeletedWebhookData;\n \"customer.created\": CustomerCreatedWebhookData;\n \"customer.updated\": CustomerUpdatedWebhookData;\n \"checkout.completed\": CheckoutCompletedWebhookData;\n}\n\n/** Legacy envelope payload. Runtime webhook deliveries do not include the event field in the body. */\nexport interface WebhookPayload<E extends WebhookEvent = WebhookEvent> {\n event: E;\n timestamp: string;\n storeId: string;\n data: WebhookEventData[E];\n}\n\nexport interface RuntimeWebhookStore {\n id: string;\n name?: string;\n timezone: string;\n}\n\nexport interface RuntimeWebhookBasePayload {\n event_id?: string;\n occurred_at?: string;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookOrder {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookProduct {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCustomer {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeOrderWebhookPayload extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer?: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeProductWebhookPayload extends RuntimeWebhookBasePayload {\n product: RuntimeWebhookProduct;\n}\n\nexport interface RuntimeCustomerWebhookPayload extends RuntimeWebhookBasePayload {\n customer: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeCheckoutCompletedWebhookPayload\n extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookEventData {\n \"order.created\": RuntimeOrderWebhookPayload;\n \"order.updated\": RuntimeOrderWebhookPayload;\n \"order.cancelled\": RuntimeOrderWebhookPayload;\n \"order.completed\": RuntimeOrderWebhookPayload;\n \"product.created\": RuntimeProductWebhookPayload;\n \"product.updated\": RuntimeProductWebhookPayload;\n \"product.deleted\": RuntimeProductWebhookPayload;\n \"customer.created\": RuntimeCustomerWebhookPayload;\n \"customer.updated\": RuntimeCustomerWebhookPayload;\n \"checkout.completed\": RuntimeCheckoutCompletedWebhookPayload;\n}\n\n/** Runtime body delivered to subscriber endpoints. The event comes from X-Webhook-Event. */\nexport type RuntimeWebhookPayload<E extends WebhookEvent = WebhookEvent> =\n RuntimeWebhookEventData[E];\n\n/** Validate the minimum runtime payload shape before invoking typed handlers. */\nexport function isRuntimeWebhookPayload<E extends WebhookEvent>(\n value: unknown,\n event: E\n): value is RuntimeWebhookPayload<E> {\n if (!isRecord(value)) return false;\n\n switch (event) {\n case WEBHOOK_EVENTS.ORDER_CREATED:\n case WEBHOOK_EVENTS.ORDER_UPDATED:\n case WEBHOOK_EVENTS.ORDER_CANCELLED:\n case WEBHOOK_EVENTS.ORDER_COMPLETED:\n return isRuntimeOrderWebhookPayload(value);\n case WEBHOOK_EVENTS.PRODUCT_CREATED:\n case WEBHOOK_EVENTS.PRODUCT_UPDATED:\n case WEBHOOK_EVENTS.PRODUCT_DELETED:\n return isRuntimeProductWebhookPayload(value);\n case WEBHOOK_EVENTS.CUSTOMER_CREATED:\n case WEBHOOK_EVENTS.CUSTOMER_UPDATED:\n return isRuntimeCustomerWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:\n return isRuntimeCheckoutCompletedWebhookPayload(value);\n default:\n return false;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/adapters/express.ts","../../src/webhooks/verify.ts","../../src/webhooks/types.ts"],"sourcesContent":["import { verifyWebhook } from \"../webhooks/verify.js\";\nimport { isRuntimeWebhookPayload, isWebhookEvent } from \"../webhooks/types.js\";\nimport type { WebhookEventHandler, WebhookHandlerOptions } from \"./types.js\";\n\ntype ExpressLikeRequest = {\n body: Buffer | string;\n headers: Record<string, string | string[] | undefined>;\n};\n\ntype ExpressLikeStatusResponse = {\n json: (body: unknown) => void;\n send: (body: string) => void;\n};\n\ntype ExpressLikeResponse = {\n status: (code: number) => ExpressLikeStatusResponse;\n};\n\nfunction getHeader(\n name: string,\n headers: Record<string, string | string[] | undefined>\n): string {\n const value = headers[name];\n\n if (Array.isArray(value)) {\n return value[0] ?? \"\";\n }\n\n return value ?? \"\";\n}\n\nexport function createWebhookHandler(\n options: WebhookHandlerOptions\n): (req: ExpressLikeRequest, res: ExpressLikeResponse) => Promise<void> {\n return async function webhookHandler(\n req: ExpressLikeRequest,\n res: ExpressLikeResponse\n ): Promise<void> {\n try {\n const rawPayload =\n typeof req.body === \"string\" ? req.body : req.body.toString(\"utf8\");\n const signature = getHeader(\"x-webhook-signature\", req.headers);\n const timestamp = getHeader(\"x-webhook-timestamp\", req.headers);\n const event = getHeader(\"x-webhook-event\", req.headers);\n const deliveryId = getHeader(\"x-webhook-id\", req.headers);\n\n const isValid = verifyWebhook({\n payload: rawPayload,\n signature,\n timestamp,\n secret: options.secret,\n });\n\n if (!isValid) {\n res.status(401).json({ error: \"Invalid signature\" });\n return;\n }\n\n if (!event) {\n res.status(400).json({ error: \"Missing event header\" });\n return;\n }\n\n if (!isWebhookEvent(event)) {\n res.status(400).json({ error: \"Invalid event header\" });\n return;\n }\n\n let parsedPayload: unknown;\n try {\n parsedPayload = JSON.parse(rawPayload) as unknown;\n } catch {\n res.status(400).json({ error: \"Invalid JSON\" });\n return;\n }\n\n if (!isRuntimeWebhookPayload(parsedPayload, event)) {\n res.status(400).json({ error: \"Invalid payload\" });\n return;\n }\n\n const context = { deliveryId, event, timestamp };\n const handler = options.handlers[event] as\n | WebhookEventHandler<typeof event>\n | undefined;\n\n if (handler) {\n await handler(parsedPayload, context);\n } else if (options.onUnhandledEvent) {\n await options.onUnhandledEvent(event, parsedPayload, context);\n }\n\n res.status(200).send(\"OK\");\n } catch {\n res.status(500).json({ error: \"Internal error\" });\n }\n };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst DEFAULT_TOLERANCE_SECONDS = 300;\n\nexport interface VerifyWebhookParams {\n /** Raw request body as a string (NOT parsed JSON) */\n payload: string;\n /** Value of the X-Webhook-Signature header */\n signature: string;\n /** Value of the X-Webhook-Timestamp header (Unix epoch in seconds) */\n timestamp: string | number;\n /** App's webhook secret (provided during app installation) */\n secret: string;\n /** Maximum timestamp drift allowed in seconds. Defaults to 300 seconds. */\n toleranceSeconds?: number;\n /** Current Unix epoch time in seconds. Intended for tests. */\n currentTime?: number;\n}\n\n/**\n * Verify webhook signature using HMAC-SHA256 over `${timestamp}.${payload}`.\n * Uses timing-safe comparison to prevent timing attacks.\n */\nexport function verifyWebhook({\n payload,\n signature,\n timestamp,\n secret,\n toleranceSeconds = DEFAULT_TOLERANCE_SECONDS,\n currentTime = Math.floor(Date.now() / 1000),\n}: VerifyWebhookParams): boolean {\n if (!payload || !signature || !secret || timestamp === \"\") return false;\n\n const timestampString = String(timestamp);\n const timestampValue =\n typeof timestamp === \"number\" ? timestamp : Number(timestamp);\n\n if (!Number.isInteger(timestampValue)) return false;\n if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) return false;\n if (!Number.isFinite(currentTime)) return false;\n\n const ageSeconds = Math.abs(currentTime - timestampValue);\n if (ageSeconds > toleranceSeconds) return false;\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestampString}.${payload}`, \"utf8\")\n .digest(\"hex\");\n\n if (expected.length !== signature.length) return false;\n if (!/^[a-f0-9]+$/i.test(signature)) return false;\n\n try {\n return timingSafeEqual(\n Buffer.from(expected, \"hex\"),\n Buffer.from(signature, \"hex\")\n );\n } catch {\n return false;\n }\n}\n","import {\n PUBLIC_WEBHOOK_EVENTS,\n isPublicWebhookEvent,\n type PublicWebhookEvent,\n} from \"@whatalo/protocol/events\";\nimport type { Product, Order, Customer } from \"../client/types.js\";\n\nexport const WEBHOOK_EVENTS = PUBLIC_WEBHOOK_EVENTS;\n\n/** All supported webhook event types */\nexport type WebhookEvent = PublicWebhookEvent;\n\n/** Validate an event header against the public runtime webhook event set. */\nexport function isWebhookEvent(value: string): value is WebhookEvent {\n return isPublicWebhookEvent(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasStringId(value: unknown): value is { id: string } {\n return isRecord(value) && typeof value.id === \"string\";\n}\n\nfunction isOptionalString(\n value: Record<string, unknown>,\n key: string\n): boolean {\n return value[key] === undefined || typeof value[key] === \"string\";\n}\n\nfunction isRuntimeWebhookStore(value: unknown): value is RuntimeWebhookStore {\n return (\n isRecord(value) &&\n typeof value.id === \"string\" &&\n typeof value.timezone === \"string\" &&\n (value.name === undefined || typeof value.name === \"string\")\n );\n}\n\nfunction hasValidBasePayloadFields(value: Record<string, unknown>): boolean {\n if (!isOptionalString(value, \"event_id\")) return false;\n if (!isOptionalString(value, \"occurred_at\")) return false;\n\n return isRuntimeWebhookStore(value.store);\n}\n\nfunction isRuntimeOrderWebhookPayload(value: Record<string, unknown>): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n (value.customer === undefined || hasStringId(value.customer))\n );\n}\n\nfunction isRuntimeProductWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.product);\n}\n\nfunction isRuntimeCustomerWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.customer);\n}\n\nfunction isRuntimeCheckoutCompletedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n hasStringId(value.customer) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nfunction isRuntimeCheckoutAbandonedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n // An abandoned draft is pre-customer, so `customer` carries no `id` and is not required.\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.checkout) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nexport interface OrderCreatedWebhookData {\n order: Order;\n}\n\nexport interface OrderUpdatedWebhookData {\n order: Order;\n changes: string[];\n}\n\nexport interface OrderCancelledWebhookData {\n orderId: string;\n reason?: string;\n}\n\nexport interface OrderCompletedWebhookData {\n order: Order;\n}\n\nexport interface ProductCreatedWebhookData {\n product: Product;\n}\n\nexport interface ProductUpdatedWebhookData {\n product: Product;\n changes: string[];\n}\n\nexport interface ProductDeletedWebhookProduct {\n id: string;\n}\n\nexport interface ProductDeletedWebhookData {\n product: ProductDeletedWebhookProduct;\n}\n\nexport interface CustomerCreatedWebhookData {\n customer: Customer;\n}\n\nexport interface CustomerUpdatedWebhookData {\n customer: Customer;\n changes: string[];\n}\n\nexport interface CheckoutCompletedWebhookData {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Contact captured on an abandoned draft. No `id` — the draft is pre-customer. */\nexport interface CheckoutAbandonedWebhookCustomer {\n name: string | null;\n email: string | null;\n phone: string | null;\n}\n\nexport interface CheckoutAbandonedWebhookData {\n checkout: RuntimeWebhookCheckout;\n customer: CheckoutAbandonedWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Map of webhook events to their payload data shapes */\nexport interface WebhookEventData {\n \"order.created\": OrderCreatedWebhookData;\n \"order.updated\": OrderUpdatedWebhookData;\n \"order.cancelled\": OrderCancelledWebhookData;\n \"order.completed\": OrderCompletedWebhookData;\n \"product.created\": ProductCreatedWebhookData;\n \"product.updated\": ProductUpdatedWebhookData;\n \"product.deleted\": ProductDeletedWebhookData;\n \"customer.created\": CustomerCreatedWebhookData;\n \"customer.updated\": CustomerUpdatedWebhookData;\n \"checkout.completed\": CheckoutCompletedWebhookData;\n \"checkout.abandoned\": CheckoutAbandonedWebhookData;\n}\n\n/** Legacy envelope payload. Runtime webhook deliveries do not include the event field in the body. */\nexport interface WebhookPayload<E extends WebhookEvent = WebhookEvent> {\n event: E;\n timestamp: string;\n storeId: string;\n data: WebhookEventData[E];\n}\n\nexport interface RuntimeWebhookStore {\n id: string;\n name?: string;\n timezone: string;\n}\n\nexport interface RuntimeWebhookBasePayload {\n event_id?: string;\n occurred_at?: string;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookOrder {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookProduct {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCustomer {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCheckout {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeOrderWebhookPayload extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer?: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeProductWebhookPayload extends RuntimeWebhookBasePayload {\n product: RuntimeWebhookProduct;\n}\n\nexport interface RuntimeCustomerWebhookPayload extends RuntimeWebhookBasePayload {\n customer: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeCheckoutCompletedWebhookPayload\n extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeCheckoutAbandonedWebhookPayload\n extends RuntimeWebhookBasePayload {\n checkout: RuntimeWebhookCheckout;\n // Pre-customer draft: contact fields only, no customer id.\n customer?: CheckoutAbandonedWebhookCustomer;\n}\n\nexport interface RuntimeWebhookEventData {\n \"order.created\": RuntimeOrderWebhookPayload;\n \"order.updated\": RuntimeOrderWebhookPayload;\n \"order.cancelled\": RuntimeOrderWebhookPayload;\n \"order.completed\": RuntimeOrderWebhookPayload;\n \"product.created\": RuntimeProductWebhookPayload;\n \"product.updated\": RuntimeProductWebhookPayload;\n \"product.deleted\": RuntimeProductWebhookPayload;\n \"customer.created\": RuntimeCustomerWebhookPayload;\n \"customer.updated\": RuntimeCustomerWebhookPayload;\n \"checkout.completed\": RuntimeCheckoutCompletedWebhookPayload;\n \"checkout.abandoned\": RuntimeCheckoutAbandonedWebhookPayload;\n}\n\n/** Runtime body delivered to subscriber endpoints. The event comes from X-Webhook-Event. */\nexport type RuntimeWebhookPayload<E extends WebhookEvent = WebhookEvent> =\n RuntimeWebhookEventData[E];\n\n/** Validate the minimum runtime payload shape before invoking typed handlers. */\nexport function isRuntimeWebhookPayload<E extends WebhookEvent>(\n value: unknown,\n event: E\n): value is RuntimeWebhookPayload<E> {\n if (!isRecord(value)) return false;\n\n switch (event) {\n case WEBHOOK_EVENTS.ORDER_CREATED:\n case WEBHOOK_EVENTS.ORDER_UPDATED:\n case WEBHOOK_EVENTS.ORDER_CANCELLED:\n case WEBHOOK_EVENTS.ORDER_COMPLETED:\n return isRuntimeOrderWebhookPayload(value);\n case WEBHOOK_EVENTS.PRODUCT_CREATED:\n case WEBHOOK_EVENTS.PRODUCT_UPDATED:\n case WEBHOOK_EVENTS.PRODUCT_DELETED:\n return isRuntimeProductWebhookPayload(value);\n case WEBHOOK_EVENTS.CUSTOMER_CREATED:\n case WEBHOOK_EVENTS.CUSTOMER_UPDATED:\n return isRuntimeCustomerWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:\n return isRuntimeCheckoutCompletedWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_ABANDONED:\n return isRuntimeCheckoutAbandonedWebhookPayload(value);\n default:\n return false;\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
- import { W as WebhookHandlerOptions } from '../types-BU71_xQm.cjs';
2
- import '../types-rA6kVlpV.cjs';
1
+ import { W as WebhookHandlerOptions } from '../types-CqyAhbXM.cjs';
2
+ import '../types-CPnSzOfX.cjs';
3
3
  import '@whatalo/protocol/events';
4
4
  import '../types-B8BAV362.cjs';
5
5
 
@@ -1,5 +1,5 @@
1
- import { W as WebhookHandlerOptions } from '../types-BhtVJPgF.js';
2
- import '../types-Bo9GbqrM.js';
1
+ import { W as WebhookHandlerOptions } from '../types-Bzee9Hvr.js';
2
+ import '../types-CDKGAQ3o.js';
3
3
  import '@whatalo/protocol/events';
4
4
  import '../types-B8BAV362.js';
5
5
 
@@ -68,6 +68,9 @@ function isRuntimeCustomerWebhookPayload(value) {
68
68
  function isRuntimeCheckoutCompletedWebhookPayload(value) {
69
69
  return hasValidBasePayloadFields(value) && hasStringId(value.order) && hasStringId(value.customer) && isRuntimeWebhookStore(value.store);
70
70
  }
71
+ function isRuntimeCheckoutAbandonedWebhookPayload(value) {
72
+ return hasValidBasePayloadFields(value) && hasStringId(value.checkout) && isRuntimeWebhookStore(value.store);
73
+ }
71
74
  function isRuntimeWebhookPayload(value, event) {
72
75
  if (!isRecord(value)) return false;
73
76
  switch (event) {
@@ -85,6 +88,8 @@ function isRuntimeWebhookPayload(value, event) {
85
88
  return isRuntimeCustomerWebhookPayload(value);
86
89
  case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:
87
90
  return isRuntimeCheckoutCompletedWebhookPayload(value);
91
+ case WEBHOOK_EVENTS.CHECKOUT_ABANDONED:
92
+ return isRuntimeCheckoutAbandonedWebhookPayload(value);
88
93
  default:
89
94
  return false;
90
95
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/webhooks/verify.ts","../../src/webhooks/types.ts","../../src/adapters/express.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst DEFAULT_TOLERANCE_SECONDS = 300;\n\nexport interface VerifyWebhookParams {\n /** Raw request body as a string (NOT parsed JSON) */\n payload: string;\n /** Value of the X-Webhook-Signature header */\n signature: string;\n /** Value of the X-Webhook-Timestamp header (Unix epoch in seconds) */\n timestamp: string | number;\n /** App's webhook secret (provided during app installation) */\n secret: string;\n /** Maximum timestamp drift allowed in seconds. Defaults to 300 seconds. */\n toleranceSeconds?: number;\n /** Current Unix epoch time in seconds. Intended for tests. */\n currentTime?: number;\n}\n\n/**\n * Verify webhook signature using HMAC-SHA256 over `${timestamp}.${payload}`.\n * Uses timing-safe comparison to prevent timing attacks.\n */\nexport function verifyWebhook({\n payload,\n signature,\n timestamp,\n secret,\n toleranceSeconds = DEFAULT_TOLERANCE_SECONDS,\n currentTime = Math.floor(Date.now() / 1000),\n}: VerifyWebhookParams): boolean {\n if (!payload || !signature || !secret || timestamp === \"\") return false;\n\n const timestampString = String(timestamp);\n const timestampValue =\n typeof timestamp === \"number\" ? timestamp : Number(timestamp);\n\n if (!Number.isInteger(timestampValue)) return false;\n if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) return false;\n if (!Number.isFinite(currentTime)) return false;\n\n const ageSeconds = Math.abs(currentTime - timestampValue);\n if (ageSeconds > toleranceSeconds) return false;\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestampString}.${payload}`, \"utf8\")\n .digest(\"hex\");\n\n if (expected.length !== signature.length) return false;\n if (!/^[a-f0-9]+$/i.test(signature)) return false;\n\n try {\n return timingSafeEqual(\n Buffer.from(expected, \"hex\"),\n Buffer.from(signature, \"hex\")\n );\n } catch {\n return false;\n }\n}\n","import {\n PUBLIC_WEBHOOK_EVENTS,\n isPublicWebhookEvent,\n type PublicWebhookEvent,\n} from \"@whatalo/protocol/events\";\nimport type { Product, Order, Customer } from \"../client/types.js\";\n\nexport const WEBHOOK_EVENTS = PUBLIC_WEBHOOK_EVENTS;\n\n/** All supported webhook event types */\nexport type WebhookEvent = PublicWebhookEvent;\n\n/** Validate an event header against the public runtime webhook event set. */\nexport function isWebhookEvent(value: string): value is WebhookEvent {\n return isPublicWebhookEvent(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasStringId(value: unknown): value is { id: string } {\n return isRecord(value) && typeof value.id === \"string\";\n}\n\nfunction isOptionalString(\n value: Record<string, unknown>,\n key: string\n): boolean {\n return value[key] === undefined || typeof value[key] === \"string\";\n}\n\nfunction isRuntimeWebhookStore(value: unknown): value is RuntimeWebhookStore {\n return (\n isRecord(value) &&\n typeof value.id === \"string\" &&\n typeof value.timezone === \"string\" &&\n (value.name === undefined || typeof value.name === \"string\")\n );\n}\n\nfunction hasValidBasePayloadFields(value: Record<string, unknown>): boolean {\n if (!isOptionalString(value, \"event_id\")) return false;\n if (!isOptionalString(value, \"occurred_at\")) return false;\n\n return isRuntimeWebhookStore(value.store);\n}\n\nfunction isRuntimeOrderWebhookPayload(value: Record<string, unknown>): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n (value.customer === undefined || hasStringId(value.customer))\n );\n}\n\nfunction isRuntimeProductWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.product);\n}\n\nfunction isRuntimeCustomerWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.customer);\n}\n\nfunction isRuntimeCheckoutCompletedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n hasStringId(value.customer) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nexport interface OrderCreatedWebhookData {\n order: Order;\n}\n\nexport interface OrderUpdatedWebhookData {\n order: Order;\n changes: string[];\n}\n\nexport interface OrderCancelledWebhookData {\n orderId: string;\n reason?: string;\n}\n\nexport interface OrderCompletedWebhookData {\n order: Order;\n}\n\nexport interface ProductCreatedWebhookData {\n product: Product;\n}\n\nexport interface ProductUpdatedWebhookData {\n product: Product;\n changes: string[];\n}\n\nexport interface ProductDeletedWebhookProduct {\n id: string;\n}\n\nexport interface ProductDeletedWebhookData {\n product: ProductDeletedWebhookProduct;\n}\n\nexport interface CustomerCreatedWebhookData {\n customer: Customer;\n}\n\nexport interface CustomerUpdatedWebhookData {\n customer: Customer;\n changes: string[];\n}\n\nexport interface CheckoutCompletedWebhookData {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Map of webhook events to their payload data shapes */\nexport interface WebhookEventData {\n \"order.created\": OrderCreatedWebhookData;\n \"order.updated\": OrderUpdatedWebhookData;\n \"order.cancelled\": OrderCancelledWebhookData;\n \"order.completed\": OrderCompletedWebhookData;\n \"product.created\": ProductCreatedWebhookData;\n \"product.updated\": ProductUpdatedWebhookData;\n \"product.deleted\": ProductDeletedWebhookData;\n \"customer.created\": CustomerCreatedWebhookData;\n \"customer.updated\": CustomerUpdatedWebhookData;\n \"checkout.completed\": CheckoutCompletedWebhookData;\n}\n\n/** Legacy envelope payload. Runtime webhook deliveries do not include the event field in the body. */\nexport interface WebhookPayload<E extends WebhookEvent = WebhookEvent> {\n event: E;\n timestamp: string;\n storeId: string;\n data: WebhookEventData[E];\n}\n\nexport interface RuntimeWebhookStore {\n id: string;\n name?: string;\n timezone: string;\n}\n\nexport interface RuntimeWebhookBasePayload {\n event_id?: string;\n occurred_at?: string;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookOrder {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookProduct {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCustomer {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeOrderWebhookPayload extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer?: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeProductWebhookPayload extends RuntimeWebhookBasePayload {\n product: RuntimeWebhookProduct;\n}\n\nexport interface RuntimeCustomerWebhookPayload extends RuntimeWebhookBasePayload {\n customer: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeCheckoutCompletedWebhookPayload\n extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookEventData {\n \"order.created\": RuntimeOrderWebhookPayload;\n \"order.updated\": RuntimeOrderWebhookPayload;\n \"order.cancelled\": RuntimeOrderWebhookPayload;\n \"order.completed\": RuntimeOrderWebhookPayload;\n \"product.created\": RuntimeProductWebhookPayload;\n \"product.updated\": RuntimeProductWebhookPayload;\n \"product.deleted\": RuntimeProductWebhookPayload;\n \"customer.created\": RuntimeCustomerWebhookPayload;\n \"customer.updated\": RuntimeCustomerWebhookPayload;\n \"checkout.completed\": RuntimeCheckoutCompletedWebhookPayload;\n}\n\n/** Runtime body delivered to subscriber endpoints. The event comes from X-Webhook-Event. */\nexport type RuntimeWebhookPayload<E extends WebhookEvent = WebhookEvent> =\n RuntimeWebhookEventData[E];\n\n/** Validate the minimum runtime payload shape before invoking typed handlers. */\nexport function isRuntimeWebhookPayload<E extends WebhookEvent>(\n value: unknown,\n event: E\n): value is RuntimeWebhookPayload<E> {\n if (!isRecord(value)) return false;\n\n switch (event) {\n case WEBHOOK_EVENTS.ORDER_CREATED:\n case WEBHOOK_EVENTS.ORDER_UPDATED:\n case WEBHOOK_EVENTS.ORDER_CANCELLED:\n case WEBHOOK_EVENTS.ORDER_COMPLETED:\n return isRuntimeOrderWebhookPayload(value);\n case WEBHOOK_EVENTS.PRODUCT_CREATED:\n case WEBHOOK_EVENTS.PRODUCT_UPDATED:\n case WEBHOOK_EVENTS.PRODUCT_DELETED:\n return isRuntimeProductWebhookPayload(value);\n case WEBHOOK_EVENTS.CUSTOMER_CREATED:\n case WEBHOOK_EVENTS.CUSTOMER_UPDATED:\n return isRuntimeCustomerWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:\n return isRuntimeCheckoutCompletedWebhookPayload(value);\n default:\n return false;\n }\n}\n","import { verifyWebhook } from \"../webhooks/verify.js\";\nimport { isRuntimeWebhookPayload, isWebhookEvent } from \"../webhooks/types.js\";\nimport type { WebhookEventHandler, WebhookHandlerOptions } from \"./types.js\";\n\ntype ExpressLikeRequest = {\n body: Buffer | string;\n headers: Record<string, string | string[] | undefined>;\n};\n\ntype ExpressLikeStatusResponse = {\n json: (body: unknown) => void;\n send: (body: string) => void;\n};\n\ntype ExpressLikeResponse = {\n status: (code: number) => ExpressLikeStatusResponse;\n};\n\nfunction getHeader(\n name: string,\n headers: Record<string, string | string[] | undefined>\n): string {\n const value = headers[name];\n\n if (Array.isArray(value)) {\n return value[0] ?? \"\";\n }\n\n return value ?? \"\";\n}\n\nexport function createWebhookHandler(\n options: WebhookHandlerOptions\n): (req: ExpressLikeRequest, res: ExpressLikeResponse) => Promise<void> {\n return async function webhookHandler(\n req: ExpressLikeRequest,\n res: ExpressLikeResponse\n ): Promise<void> {\n try {\n const rawPayload =\n typeof req.body === \"string\" ? req.body : req.body.toString(\"utf8\");\n const signature = getHeader(\"x-webhook-signature\", req.headers);\n const timestamp = getHeader(\"x-webhook-timestamp\", req.headers);\n const event = getHeader(\"x-webhook-event\", req.headers);\n const deliveryId = getHeader(\"x-webhook-id\", req.headers);\n\n const isValid = verifyWebhook({\n payload: rawPayload,\n signature,\n timestamp,\n secret: options.secret,\n });\n\n if (!isValid) {\n res.status(401).json({ error: \"Invalid signature\" });\n return;\n }\n\n if (!event) {\n res.status(400).json({ error: \"Missing event header\" });\n return;\n }\n\n if (!isWebhookEvent(event)) {\n res.status(400).json({ error: \"Invalid event header\" });\n return;\n }\n\n let parsedPayload: unknown;\n try {\n parsedPayload = JSON.parse(rawPayload) as unknown;\n } catch {\n res.status(400).json({ error: \"Invalid JSON\" });\n return;\n }\n\n if (!isRuntimeWebhookPayload(parsedPayload, event)) {\n res.status(400).json({ error: \"Invalid payload\" });\n return;\n }\n\n const context = { deliveryId, event, timestamp };\n const handler = options.handlers[event] as\n | WebhookEventHandler<typeof event>\n | undefined;\n\n if (handler) {\n await handler(parsedPayload, context);\n } else if (options.onUnhandledEvent) {\n await options.onUnhandledEvent(event, parsedPayload, context);\n }\n\n res.status(200).send(\"OK\");\n } catch {\n res.status(500).json({ error: \"Internal error\" });\n }\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/webhooks/verify.ts","../../src/webhooks/types.ts","../../src/adapters/express.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst DEFAULT_TOLERANCE_SECONDS = 300;\n\nexport interface VerifyWebhookParams {\n /** Raw request body as a string (NOT parsed JSON) */\n payload: string;\n /** Value of the X-Webhook-Signature header */\n signature: string;\n /** Value of the X-Webhook-Timestamp header (Unix epoch in seconds) */\n timestamp: string | number;\n /** App's webhook secret (provided during app installation) */\n secret: string;\n /** Maximum timestamp drift allowed in seconds. Defaults to 300 seconds. */\n toleranceSeconds?: number;\n /** Current Unix epoch time in seconds. Intended for tests. */\n currentTime?: number;\n}\n\n/**\n * Verify webhook signature using HMAC-SHA256 over `${timestamp}.${payload}`.\n * Uses timing-safe comparison to prevent timing attacks.\n */\nexport function verifyWebhook({\n payload,\n signature,\n timestamp,\n secret,\n toleranceSeconds = DEFAULT_TOLERANCE_SECONDS,\n currentTime = Math.floor(Date.now() / 1000),\n}: VerifyWebhookParams): boolean {\n if (!payload || !signature || !secret || timestamp === \"\") return false;\n\n const timestampString = String(timestamp);\n const timestampValue =\n typeof timestamp === \"number\" ? timestamp : Number(timestamp);\n\n if (!Number.isInteger(timestampValue)) return false;\n if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) return false;\n if (!Number.isFinite(currentTime)) return false;\n\n const ageSeconds = Math.abs(currentTime - timestampValue);\n if (ageSeconds > toleranceSeconds) return false;\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestampString}.${payload}`, \"utf8\")\n .digest(\"hex\");\n\n if (expected.length !== signature.length) return false;\n if (!/^[a-f0-9]+$/i.test(signature)) return false;\n\n try {\n return timingSafeEqual(\n Buffer.from(expected, \"hex\"),\n Buffer.from(signature, \"hex\")\n );\n } catch {\n return false;\n }\n}\n","import {\n PUBLIC_WEBHOOK_EVENTS,\n isPublicWebhookEvent,\n type PublicWebhookEvent,\n} from \"@whatalo/protocol/events\";\nimport type { Product, Order, Customer } from \"../client/types.js\";\n\nexport const WEBHOOK_EVENTS = PUBLIC_WEBHOOK_EVENTS;\n\n/** All supported webhook event types */\nexport type WebhookEvent = PublicWebhookEvent;\n\n/** Validate an event header against the public runtime webhook event set. */\nexport function isWebhookEvent(value: string): value is WebhookEvent {\n return isPublicWebhookEvent(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasStringId(value: unknown): value is { id: string } {\n return isRecord(value) && typeof value.id === \"string\";\n}\n\nfunction isOptionalString(\n value: Record<string, unknown>,\n key: string\n): boolean {\n return value[key] === undefined || typeof value[key] === \"string\";\n}\n\nfunction isRuntimeWebhookStore(value: unknown): value is RuntimeWebhookStore {\n return (\n isRecord(value) &&\n typeof value.id === \"string\" &&\n typeof value.timezone === \"string\" &&\n (value.name === undefined || typeof value.name === \"string\")\n );\n}\n\nfunction hasValidBasePayloadFields(value: Record<string, unknown>): boolean {\n if (!isOptionalString(value, \"event_id\")) return false;\n if (!isOptionalString(value, \"occurred_at\")) return false;\n\n return isRuntimeWebhookStore(value.store);\n}\n\nfunction isRuntimeOrderWebhookPayload(value: Record<string, unknown>): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n (value.customer === undefined || hasStringId(value.customer))\n );\n}\n\nfunction isRuntimeProductWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.product);\n}\n\nfunction isRuntimeCustomerWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.customer);\n}\n\nfunction isRuntimeCheckoutCompletedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n hasStringId(value.customer) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nfunction isRuntimeCheckoutAbandonedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n // An abandoned draft is pre-customer, so `customer` carries no `id` and is not required.\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.checkout) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nexport interface OrderCreatedWebhookData {\n order: Order;\n}\n\nexport interface OrderUpdatedWebhookData {\n order: Order;\n changes: string[];\n}\n\nexport interface OrderCancelledWebhookData {\n orderId: string;\n reason?: string;\n}\n\nexport interface OrderCompletedWebhookData {\n order: Order;\n}\n\nexport interface ProductCreatedWebhookData {\n product: Product;\n}\n\nexport interface ProductUpdatedWebhookData {\n product: Product;\n changes: string[];\n}\n\nexport interface ProductDeletedWebhookProduct {\n id: string;\n}\n\nexport interface ProductDeletedWebhookData {\n product: ProductDeletedWebhookProduct;\n}\n\nexport interface CustomerCreatedWebhookData {\n customer: Customer;\n}\n\nexport interface CustomerUpdatedWebhookData {\n customer: Customer;\n changes: string[];\n}\n\nexport interface CheckoutCompletedWebhookData {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Contact captured on an abandoned draft. No `id` — the draft is pre-customer. */\nexport interface CheckoutAbandonedWebhookCustomer {\n name: string | null;\n email: string | null;\n phone: string | null;\n}\n\nexport interface CheckoutAbandonedWebhookData {\n checkout: RuntimeWebhookCheckout;\n customer: CheckoutAbandonedWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Map of webhook events to their payload data shapes */\nexport interface WebhookEventData {\n \"order.created\": OrderCreatedWebhookData;\n \"order.updated\": OrderUpdatedWebhookData;\n \"order.cancelled\": OrderCancelledWebhookData;\n \"order.completed\": OrderCompletedWebhookData;\n \"product.created\": ProductCreatedWebhookData;\n \"product.updated\": ProductUpdatedWebhookData;\n \"product.deleted\": ProductDeletedWebhookData;\n \"customer.created\": CustomerCreatedWebhookData;\n \"customer.updated\": CustomerUpdatedWebhookData;\n \"checkout.completed\": CheckoutCompletedWebhookData;\n \"checkout.abandoned\": CheckoutAbandonedWebhookData;\n}\n\n/** Legacy envelope payload. Runtime webhook deliveries do not include the event field in the body. */\nexport interface WebhookPayload<E extends WebhookEvent = WebhookEvent> {\n event: E;\n timestamp: string;\n storeId: string;\n data: WebhookEventData[E];\n}\n\nexport interface RuntimeWebhookStore {\n id: string;\n name?: string;\n timezone: string;\n}\n\nexport interface RuntimeWebhookBasePayload {\n event_id?: string;\n occurred_at?: string;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookOrder {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookProduct {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCustomer {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCheckout {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeOrderWebhookPayload extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer?: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeProductWebhookPayload extends RuntimeWebhookBasePayload {\n product: RuntimeWebhookProduct;\n}\n\nexport interface RuntimeCustomerWebhookPayload extends RuntimeWebhookBasePayload {\n customer: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeCheckoutCompletedWebhookPayload\n extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeCheckoutAbandonedWebhookPayload\n extends RuntimeWebhookBasePayload {\n checkout: RuntimeWebhookCheckout;\n // Pre-customer draft: contact fields only, no customer id.\n customer?: CheckoutAbandonedWebhookCustomer;\n}\n\nexport interface RuntimeWebhookEventData {\n \"order.created\": RuntimeOrderWebhookPayload;\n \"order.updated\": RuntimeOrderWebhookPayload;\n \"order.cancelled\": RuntimeOrderWebhookPayload;\n \"order.completed\": RuntimeOrderWebhookPayload;\n \"product.created\": RuntimeProductWebhookPayload;\n \"product.updated\": RuntimeProductWebhookPayload;\n \"product.deleted\": RuntimeProductWebhookPayload;\n \"customer.created\": RuntimeCustomerWebhookPayload;\n \"customer.updated\": RuntimeCustomerWebhookPayload;\n \"checkout.completed\": RuntimeCheckoutCompletedWebhookPayload;\n \"checkout.abandoned\": RuntimeCheckoutAbandonedWebhookPayload;\n}\n\n/** Runtime body delivered to subscriber endpoints. The event comes from X-Webhook-Event. */\nexport type RuntimeWebhookPayload<E extends WebhookEvent = WebhookEvent> =\n RuntimeWebhookEventData[E];\n\n/** Validate the minimum runtime payload shape before invoking typed handlers. */\nexport function isRuntimeWebhookPayload<E extends WebhookEvent>(\n value: unknown,\n event: E\n): value is RuntimeWebhookPayload<E> {\n if (!isRecord(value)) return false;\n\n switch (event) {\n case WEBHOOK_EVENTS.ORDER_CREATED:\n case WEBHOOK_EVENTS.ORDER_UPDATED:\n case WEBHOOK_EVENTS.ORDER_CANCELLED:\n case WEBHOOK_EVENTS.ORDER_COMPLETED:\n return isRuntimeOrderWebhookPayload(value);\n case WEBHOOK_EVENTS.PRODUCT_CREATED:\n case WEBHOOK_EVENTS.PRODUCT_UPDATED:\n case WEBHOOK_EVENTS.PRODUCT_DELETED:\n return isRuntimeProductWebhookPayload(value);\n case WEBHOOK_EVENTS.CUSTOMER_CREATED:\n case WEBHOOK_EVENTS.CUSTOMER_UPDATED:\n return isRuntimeCustomerWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:\n return isRuntimeCheckoutCompletedWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_ABANDONED:\n return isRuntimeCheckoutAbandonedWebhookPayload(value);\n default:\n return false;\n }\n}\n","import { verifyWebhook } from \"../webhooks/verify.js\";\nimport { isRuntimeWebhookPayload, isWebhookEvent } from \"../webhooks/types.js\";\nimport type { WebhookEventHandler, WebhookHandlerOptions } from \"./types.js\";\n\ntype ExpressLikeRequest = {\n body: Buffer | string;\n headers: Record<string, string | string[] | undefined>;\n};\n\ntype ExpressLikeStatusResponse = {\n json: (body: unknown) => void;\n send: (body: string) => void;\n};\n\ntype ExpressLikeResponse = {\n status: (code: number) => ExpressLikeStatusResponse;\n};\n\nfunction getHeader(\n name: string,\n headers: Record<string, string | string[] | undefined>\n): string {\n const value = headers[name];\n\n if (Array.isArray(value)) {\n return value[0] ?? \"\";\n }\n\n return value ?? \"\";\n}\n\nexport function createWebhookHandler(\n options: WebhookHandlerOptions\n): (req: ExpressLikeRequest, res: ExpressLikeResponse) => Promise<void> {\n return async function webhookHandler(\n req: ExpressLikeRequest,\n res: ExpressLikeResponse\n ): Promise<void> {\n try {\n const rawPayload =\n typeof req.body === \"string\" ? req.body : req.body.toString(\"utf8\");\n const signature = getHeader(\"x-webhook-signature\", req.headers);\n const timestamp = getHeader(\"x-webhook-timestamp\", req.headers);\n const event = getHeader(\"x-webhook-event\", req.headers);\n const deliveryId = getHeader(\"x-webhook-id\", req.headers);\n\n const isValid = verifyWebhook({\n payload: rawPayload,\n signature,\n timestamp,\n secret: options.secret,\n });\n\n if (!isValid) {\n res.status(401).json({ error: \"Invalid signature\" });\n return;\n }\n\n if (!event) {\n res.status(400).json({ error: \"Missing event header\" });\n return;\n }\n\n if (!isWebhookEvent(event)) {\n res.status(400).json({ error: \"Invalid event header\" });\n return;\n }\n\n let parsedPayload: unknown;\n try {\n parsedPayload = JSON.parse(rawPayload) as unknown;\n } catch {\n res.status(400).json({ error: \"Invalid JSON\" });\n return;\n }\n\n if (!isRuntimeWebhookPayload(parsedPayload, event)) {\n res.status(400).json({ error: \"Invalid payload\" });\n return;\n }\n\n const context = { deliveryId, event, timestamp };\n const handler = options.handlers[event] as\n | WebhookEventHandler<typeof event>\n | undefined;\n\n if (handler) {\n await handler(parsedPayload, context);\n } else if (options.onUnhandledEvent) {\n await options.onUnhandledEvent(event, parsedPayload, context);\n }\n\n res.status(200).send(\"OK\");\n } catch {\n res.status(500).json({ error: \"Internal error\" });\n }\n };\n}\n"],"mappings":"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","names":[]}
@@ -91,6 +91,9 @@ function isRuntimeCustomerWebhookPayload(value) {
91
91
  function isRuntimeCheckoutCompletedWebhookPayload(value) {
92
92
  return hasValidBasePayloadFields(value) && hasStringId(value.order) && hasStringId(value.customer) && isRuntimeWebhookStore(value.store);
93
93
  }
94
+ function isRuntimeCheckoutAbandonedWebhookPayload(value) {
95
+ return hasValidBasePayloadFields(value) && hasStringId(value.checkout) && isRuntimeWebhookStore(value.store);
96
+ }
94
97
  function isRuntimeWebhookPayload(value, event) {
95
98
  if (!isRecord(value)) return false;
96
99
  switch (event) {
@@ -108,6 +111,8 @@ function isRuntimeWebhookPayload(value, event) {
108
111
  return isRuntimeCustomerWebhookPayload(value);
109
112
  case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:
110
113
  return isRuntimeCheckoutCompletedWebhookPayload(value);
114
+ case WEBHOOK_EVENTS.CHECKOUT_ABANDONED:
115
+ return isRuntimeCheckoutAbandonedWebhookPayload(value);
111
116
  default:
112
117
  return false;
113
118
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/hono.ts","../../src/webhooks/verify.ts","../../src/webhooks/types.ts"],"sourcesContent":["import { verifyWebhook } from \"../webhooks/verify.js\";\nimport { isRuntimeWebhookPayload, isWebhookEvent } from \"../webhooks/types.js\";\nimport type { WebhookEventHandler, WebhookHandlerOptions } from \"./types.js\";\n\nexport type HonoContextLike = {\n req: {\n text: () => Promise<string>;\n header: (name: string) => string | undefined;\n };\n json: (body: unknown, status?: number) => unknown;\n text: (body: string, status?: number) => unknown;\n};\n\nexport function createWebhookHandler(\n options: WebhookHandlerOptions\n): (c: HonoContextLike) => Promise<unknown> {\n return async function webhookHandler(c: HonoContextLike): Promise<unknown> {\n try {\n const rawPayload = await c.req.text();\n const signature = c.req.header(\"X-Webhook-Signature\") ?? \"\";\n const timestamp = c.req.header(\"X-Webhook-Timestamp\") ?? \"\";\n const event = c.req.header(\"X-Webhook-Event\") ?? \"\";\n const deliveryId = c.req.header(\"X-Webhook-Id\") ?? \"\";\n\n const isValid = verifyWebhook({\n payload: rawPayload,\n signature,\n timestamp,\n secret: options.secret,\n });\n\n if (!isValid) {\n return c.json({ error: \"Invalid signature\" }, 401);\n }\n\n if (!event) {\n return c.json({ error: \"Missing event header\" }, 400);\n }\n\n if (!isWebhookEvent(event)) {\n return c.json({ error: \"Invalid event header\" }, 400);\n }\n\n let parsedPayload: unknown;\n try {\n parsedPayload = JSON.parse(rawPayload) as unknown;\n } catch {\n return c.json({ error: \"Invalid JSON\" }, 400);\n }\n\n if (!isRuntimeWebhookPayload(parsedPayload, event)) {\n return c.json({ error: \"Invalid payload\" }, 400);\n }\n\n const context = { deliveryId, event, timestamp };\n const handler = options.handlers[event] as\n | WebhookEventHandler<typeof event>\n | undefined;\n\n if (handler) {\n await handler(parsedPayload, context);\n } else if (options.onUnhandledEvent) {\n await options.onUnhandledEvent(event, parsedPayload, context);\n }\n\n return c.text(\"OK\", 200);\n } catch {\n return c.json({ error: \"Internal error\" }, 500);\n }\n };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst DEFAULT_TOLERANCE_SECONDS = 300;\n\nexport interface VerifyWebhookParams {\n /** Raw request body as a string (NOT parsed JSON) */\n payload: string;\n /** Value of the X-Webhook-Signature header */\n signature: string;\n /** Value of the X-Webhook-Timestamp header (Unix epoch in seconds) */\n timestamp: string | number;\n /** App's webhook secret (provided during app installation) */\n secret: string;\n /** Maximum timestamp drift allowed in seconds. Defaults to 300 seconds. */\n toleranceSeconds?: number;\n /** Current Unix epoch time in seconds. Intended for tests. */\n currentTime?: number;\n}\n\n/**\n * Verify webhook signature using HMAC-SHA256 over `${timestamp}.${payload}`.\n * Uses timing-safe comparison to prevent timing attacks.\n */\nexport function verifyWebhook({\n payload,\n signature,\n timestamp,\n secret,\n toleranceSeconds = DEFAULT_TOLERANCE_SECONDS,\n currentTime = Math.floor(Date.now() / 1000),\n}: VerifyWebhookParams): boolean {\n if (!payload || !signature || !secret || timestamp === \"\") return false;\n\n const timestampString = String(timestamp);\n const timestampValue =\n typeof timestamp === \"number\" ? timestamp : Number(timestamp);\n\n if (!Number.isInteger(timestampValue)) return false;\n if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) return false;\n if (!Number.isFinite(currentTime)) return false;\n\n const ageSeconds = Math.abs(currentTime - timestampValue);\n if (ageSeconds > toleranceSeconds) return false;\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestampString}.${payload}`, \"utf8\")\n .digest(\"hex\");\n\n if (expected.length !== signature.length) return false;\n if (!/^[a-f0-9]+$/i.test(signature)) return false;\n\n try {\n return timingSafeEqual(\n Buffer.from(expected, \"hex\"),\n Buffer.from(signature, \"hex\")\n );\n } catch {\n return false;\n }\n}\n","import {\n PUBLIC_WEBHOOK_EVENTS,\n isPublicWebhookEvent,\n type PublicWebhookEvent,\n} from \"@whatalo/protocol/events\";\nimport type { Product, Order, Customer } from \"../client/types.js\";\n\nexport const WEBHOOK_EVENTS = PUBLIC_WEBHOOK_EVENTS;\n\n/** All supported webhook event types */\nexport type WebhookEvent = PublicWebhookEvent;\n\n/** Validate an event header against the public runtime webhook event set. */\nexport function isWebhookEvent(value: string): value is WebhookEvent {\n return isPublicWebhookEvent(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasStringId(value: unknown): value is { id: string } {\n return isRecord(value) && typeof value.id === \"string\";\n}\n\nfunction isOptionalString(\n value: Record<string, unknown>,\n key: string\n): boolean {\n return value[key] === undefined || typeof value[key] === \"string\";\n}\n\nfunction isRuntimeWebhookStore(value: unknown): value is RuntimeWebhookStore {\n return (\n isRecord(value) &&\n typeof value.id === \"string\" &&\n typeof value.timezone === \"string\" &&\n (value.name === undefined || typeof value.name === \"string\")\n );\n}\n\nfunction hasValidBasePayloadFields(value: Record<string, unknown>): boolean {\n if (!isOptionalString(value, \"event_id\")) return false;\n if (!isOptionalString(value, \"occurred_at\")) return false;\n\n return isRuntimeWebhookStore(value.store);\n}\n\nfunction isRuntimeOrderWebhookPayload(value: Record<string, unknown>): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n (value.customer === undefined || hasStringId(value.customer))\n );\n}\n\nfunction isRuntimeProductWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.product);\n}\n\nfunction isRuntimeCustomerWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.customer);\n}\n\nfunction isRuntimeCheckoutCompletedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n hasStringId(value.customer) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nexport interface OrderCreatedWebhookData {\n order: Order;\n}\n\nexport interface OrderUpdatedWebhookData {\n order: Order;\n changes: string[];\n}\n\nexport interface OrderCancelledWebhookData {\n orderId: string;\n reason?: string;\n}\n\nexport interface OrderCompletedWebhookData {\n order: Order;\n}\n\nexport interface ProductCreatedWebhookData {\n product: Product;\n}\n\nexport interface ProductUpdatedWebhookData {\n product: Product;\n changes: string[];\n}\n\nexport interface ProductDeletedWebhookProduct {\n id: string;\n}\n\nexport interface ProductDeletedWebhookData {\n product: ProductDeletedWebhookProduct;\n}\n\nexport interface CustomerCreatedWebhookData {\n customer: Customer;\n}\n\nexport interface CustomerUpdatedWebhookData {\n customer: Customer;\n changes: string[];\n}\n\nexport interface CheckoutCompletedWebhookData {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Map of webhook events to their payload data shapes */\nexport interface WebhookEventData {\n \"order.created\": OrderCreatedWebhookData;\n \"order.updated\": OrderUpdatedWebhookData;\n \"order.cancelled\": OrderCancelledWebhookData;\n \"order.completed\": OrderCompletedWebhookData;\n \"product.created\": ProductCreatedWebhookData;\n \"product.updated\": ProductUpdatedWebhookData;\n \"product.deleted\": ProductDeletedWebhookData;\n \"customer.created\": CustomerCreatedWebhookData;\n \"customer.updated\": CustomerUpdatedWebhookData;\n \"checkout.completed\": CheckoutCompletedWebhookData;\n}\n\n/** Legacy envelope payload. Runtime webhook deliveries do not include the event field in the body. */\nexport interface WebhookPayload<E extends WebhookEvent = WebhookEvent> {\n event: E;\n timestamp: string;\n storeId: string;\n data: WebhookEventData[E];\n}\n\nexport interface RuntimeWebhookStore {\n id: string;\n name?: string;\n timezone: string;\n}\n\nexport interface RuntimeWebhookBasePayload {\n event_id?: string;\n occurred_at?: string;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookOrder {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookProduct {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCustomer {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeOrderWebhookPayload extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer?: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeProductWebhookPayload extends RuntimeWebhookBasePayload {\n product: RuntimeWebhookProduct;\n}\n\nexport interface RuntimeCustomerWebhookPayload extends RuntimeWebhookBasePayload {\n customer: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeCheckoutCompletedWebhookPayload\n extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookEventData {\n \"order.created\": RuntimeOrderWebhookPayload;\n \"order.updated\": RuntimeOrderWebhookPayload;\n \"order.cancelled\": RuntimeOrderWebhookPayload;\n \"order.completed\": RuntimeOrderWebhookPayload;\n \"product.created\": RuntimeProductWebhookPayload;\n \"product.updated\": RuntimeProductWebhookPayload;\n \"product.deleted\": RuntimeProductWebhookPayload;\n \"customer.created\": RuntimeCustomerWebhookPayload;\n \"customer.updated\": RuntimeCustomerWebhookPayload;\n \"checkout.completed\": RuntimeCheckoutCompletedWebhookPayload;\n}\n\n/** Runtime body delivered to subscriber endpoints. The event comes from X-Webhook-Event. */\nexport type RuntimeWebhookPayload<E extends WebhookEvent = WebhookEvent> =\n RuntimeWebhookEventData[E];\n\n/** Validate the minimum runtime payload shape before invoking typed handlers. */\nexport function isRuntimeWebhookPayload<E extends WebhookEvent>(\n value: unknown,\n event: E\n): value is RuntimeWebhookPayload<E> {\n if (!isRecord(value)) return false;\n\n switch (event) {\n case WEBHOOK_EVENTS.ORDER_CREATED:\n case WEBHOOK_EVENTS.ORDER_UPDATED:\n case WEBHOOK_EVENTS.ORDER_CANCELLED:\n case WEBHOOK_EVENTS.ORDER_COMPLETED:\n return isRuntimeOrderWebhookPayload(value);\n case WEBHOOK_EVENTS.PRODUCT_CREATED:\n case WEBHOOK_EVENTS.PRODUCT_UPDATED:\n case WEBHOOK_EVENTS.PRODUCT_DELETED:\n return isRuntimeProductWebhookPayload(value);\n case WEBHOOK_EVENTS.CUSTOMER_CREATED:\n case WEBHOOK_EVENTS.CUSTOMER_UPDATED:\n return isRuntimeCustomerWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:\n return isRuntimeCheckoutCompletedWebhookPayload(value);\n default:\n return false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,yBAA4C;AAE5C,IAAM,4BAA4B;AAqB3B,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,mBAAmB;AAAA,EACnB,cAAc,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAC5C,GAAiC;AAC/B,MAAI,CAAC,WAAW,CAAC,aAAa,CAAC,UAAU,cAAc,GAAI,QAAO;AAElE,QAAM,kBAAkB,OAAO,SAAS;AACxC,QAAM,iBACJ,OAAO,cAAc,WAAW,YAAY,OAAO,SAAS;AAE9D,MAAI,CAAC,OAAO,UAAU,cAAc,EAAG,QAAO;AAC9C,MAAI,CAAC,OAAO,SAAS,gBAAgB,KAAK,mBAAmB,EAAG,QAAO;AACvE,MAAI,CAAC,OAAO,SAAS,WAAW,EAAG,QAAO;AAE1C,QAAM,aAAa,KAAK,IAAI,cAAc,cAAc;AACxD,MAAI,aAAa,iBAAkB,QAAO;AAE1C,QAAM,eAAW,+BAAW,UAAU,MAAM,EACzC,OAAO,GAAG,eAAe,IAAI,OAAO,IAAI,MAAM,EAC9C,OAAO,KAAK;AAEf,MAAI,SAAS,WAAW,UAAU,OAAQ,QAAO;AACjD,MAAI,CAAC,eAAe,KAAK,SAAS,EAAG,QAAO;AAE5C,MAAI;AACF,eAAO;AAAA,MACL,OAAO,KAAK,UAAU,KAAK;AAAA,MAC3B,OAAO,KAAK,WAAW,KAAK;AAAA,IAC9B;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AC3DA,oBAIO;AAGA,IAAM,iBAAiB;AAMvB,SAAS,eAAe,OAAsC;AACnE,aAAO,oCAAqB,KAAK;AACnC;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,YAAY,OAAyC;AAC5D,SAAO,SAAS,KAAK,KAAK,OAAO,MAAM,OAAO;AAChD;AAEA,SAAS,iBACP,OACA,KACS;AACT,SAAO,MAAM,GAAG,MAAM,UAAa,OAAO,MAAM,GAAG,MAAM;AAC3D;AAEA,SAAS,sBAAsB,OAA8C;AAC3E,SACE,SAAS,KAAK,KACd,OAAO,MAAM,OAAO,YACpB,OAAO,MAAM,aAAa,aACzB,MAAM,SAAS,UAAa,OAAO,MAAM,SAAS;AAEvD;AAEA,SAAS,0BAA0B,OAAyC;AAC1E,MAAI,CAAC,iBAAiB,OAAO,UAAU,EAAG,QAAO;AACjD,MAAI,CAAC,iBAAiB,OAAO,aAAa,EAAG,QAAO;AAEpD,SAAO,sBAAsB,MAAM,KAAK;AAC1C;AAEA,SAAS,6BAA6B,OAAyC;AAC7E,SACE,0BAA0B,KAAK,KAC/B,YAAY,MAAM,KAAK,MACtB,MAAM,aAAa,UAAa,YAAY,MAAM,QAAQ;AAE/D;AAEA,SAAS,+BAA+B,OAAyC;AAC/E,SAAO,0BAA0B,KAAK,KAAK,YAAY,MAAM,OAAO;AACtE;AAEA,SAAS,gCAAgC,OAAyC;AAChF,SAAO,0BAA0B,KAAK,KAAK,YAAY,MAAM,QAAQ;AACvE;AAEA,SAAS,yCACP,OACS;AACT,SACE,0BAA0B,KAAK,KAC/B,YAAY,MAAM,KAAK,KACvB,YAAY,MAAM,QAAQ,KAC1B,sBAAsB,MAAM,KAAK;AAErC;AA2IO,SAAS,wBACd,OACA,OACmC;AACnC,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAE7B,UAAQ,OAAO;AAAA,IACb,KAAK,eAAe;AAAA,IACpB,KAAK,eAAe;AAAA,IACpB,KAAK,eAAe;AAAA,IACpB,KAAK,eAAe;AAClB,aAAO,6BAA6B,KAAK;AAAA,IAC3C,KAAK,eAAe;AAAA,IACpB,KAAK,eAAe;AAAA,IACpB,KAAK,eAAe;AAClB,aAAO,+BAA+B,KAAK;AAAA,IAC7C,KAAK,eAAe;AAAA,IACpB,KAAK,eAAe;AAClB,aAAO,gCAAgC,KAAK;AAAA,IAC9C,KAAK,eAAe;AAClB,aAAO,yCAAyC,KAAK;AAAA,IACvD;AACE,aAAO;AAAA,EACX;AACF;;;AF/NO,SAAS,qBACd,SAC0C;AAC1C,SAAO,eAAe,eAAe,GAAsC;AACzE,QAAI;AACF,YAAM,aAAa,MAAM,EAAE,IAAI,KAAK;AACpC,YAAM,YAAY,EAAE,IAAI,OAAO,qBAAqB,KAAK;AACzD,YAAM,YAAY,EAAE,IAAI,OAAO,qBAAqB,KAAK;AACzD,YAAM,QAAQ,EAAE,IAAI,OAAO,iBAAiB,KAAK;AACjD,YAAM,aAAa,EAAE,IAAI,OAAO,cAAc,KAAK;AAEnD,YAAM,UAAU,cAAc;AAAA,QAC5B,SAAS;AAAA,QACT;AAAA,QACA;AAAA,QACA,QAAQ,QAAQ;AAAA,MAClB,CAAC;AAED,UAAI,CAAC,SAAS;AACZ,eAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;AAAA,MACnD;AAEA,UAAI,CAAC,OAAO;AACV,eAAO,EAAE,KAAK,EAAE,OAAO,uBAAuB,GAAG,GAAG;AAAA,MACtD;AAEA,UAAI,CAAC,eAAe,KAAK,GAAG;AAC1B,eAAO,EAAE,KAAK,EAAE,OAAO,uBAAuB,GAAG,GAAG;AAAA,MACtD;AAEA,UAAI;AACJ,UAAI;AACF,wBAAgB,KAAK,MAAM,UAAU;AAAA,MACvC,QAAQ;AACN,eAAO,EAAE,KAAK,EAAE,OAAO,eAAe,GAAG,GAAG;AAAA,MAC9C;AAEA,UAAI,CAAC,wBAAwB,eAAe,KAAK,GAAG;AAClD,eAAO,EAAE,KAAK,EAAE,OAAO,kBAAkB,GAAG,GAAG;AAAA,MACjD;AAEA,YAAM,UAAU,EAAE,YAAY,OAAO,UAAU;AAC/C,YAAM,UAAU,QAAQ,SAAS,KAAK;AAItC,UAAI,SAAS;AACX,cAAM,QAAQ,eAAe,OAAO;AAAA,MACtC,WAAW,QAAQ,kBAAkB;AACnC,cAAM,QAAQ,iBAAiB,OAAO,eAAe,OAAO;AAAA,MAC9D;AAEA,aAAO,EAAE,KAAK,MAAM,GAAG;AAAA,IACzB,QAAQ;AACN,aAAO,EAAE,KAAK,EAAE,OAAO,iBAAiB,GAAG,GAAG;AAAA,IAChD;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/adapters/hono.ts","../../src/webhooks/verify.ts","../../src/webhooks/types.ts"],"sourcesContent":["import { verifyWebhook } from \"../webhooks/verify.js\";\nimport { isRuntimeWebhookPayload, isWebhookEvent } from \"../webhooks/types.js\";\nimport type { WebhookEventHandler, WebhookHandlerOptions } from \"./types.js\";\n\nexport type HonoContextLike = {\n req: {\n text: () => Promise<string>;\n header: (name: string) => string | undefined;\n };\n json: (body: unknown, status?: number) => unknown;\n text: (body: string, status?: number) => unknown;\n};\n\nexport function createWebhookHandler(\n options: WebhookHandlerOptions\n): (c: HonoContextLike) => Promise<unknown> {\n return async function webhookHandler(c: HonoContextLike): Promise<unknown> {\n try {\n const rawPayload = await c.req.text();\n const signature = c.req.header(\"X-Webhook-Signature\") ?? \"\";\n const timestamp = c.req.header(\"X-Webhook-Timestamp\") ?? \"\";\n const event = c.req.header(\"X-Webhook-Event\") ?? \"\";\n const deliveryId = c.req.header(\"X-Webhook-Id\") ?? \"\";\n\n const isValid = verifyWebhook({\n payload: rawPayload,\n signature,\n timestamp,\n secret: options.secret,\n });\n\n if (!isValid) {\n return c.json({ error: \"Invalid signature\" }, 401);\n }\n\n if (!event) {\n return c.json({ error: \"Missing event header\" }, 400);\n }\n\n if (!isWebhookEvent(event)) {\n return c.json({ error: \"Invalid event header\" }, 400);\n }\n\n let parsedPayload: unknown;\n try {\n parsedPayload = JSON.parse(rawPayload) as unknown;\n } catch {\n return c.json({ error: \"Invalid JSON\" }, 400);\n }\n\n if (!isRuntimeWebhookPayload(parsedPayload, event)) {\n return c.json({ error: \"Invalid payload\" }, 400);\n }\n\n const context = { deliveryId, event, timestamp };\n const handler = options.handlers[event] as\n | WebhookEventHandler<typeof event>\n | undefined;\n\n if (handler) {\n await handler(parsedPayload, context);\n } else if (options.onUnhandledEvent) {\n await options.onUnhandledEvent(event, parsedPayload, context);\n }\n\n return c.text(\"OK\", 200);\n } catch {\n return c.json({ error: \"Internal error\" }, 500);\n }\n };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst DEFAULT_TOLERANCE_SECONDS = 300;\n\nexport interface VerifyWebhookParams {\n /** Raw request body as a string (NOT parsed JSON) */\n payload: string;\n /** Value of the X-Webhook-Signature header */\n signature: string;\n /** Value of the X-Webhook-Timestamp header (Unix epoch in seconds) */\n timestamp: string | number;\n /** App's webhook secret (provided during app installation) */\n secret: string;\n /** Maximum timestamp drift allowed in seconds. Defaults to 300 seconds. */\n toleranceSeconds?: number;\n /** Current Unix epoch time in seconds. Intended for tests. */\n currentTime?: number;\n}\n\n/**\n * Verify webhook signature using HMAC-SHA256 over `${timestamp}.${payload}`.\n * Uses timing-safe comparison to prevent timing attacks.\n */\nexport function verifyWebhook({\n payload,\n signature,\n timestamp,\n secret,\n toleranceSeconds = DEFAULT_TOLERANCE_SECONDS,\n currentTime = Math.floor(Date.now() / 1000),\n}: VerifyWebhookParams): boolean {\n if (!payload || !signature || !secret || timestamp === \"\") return false;\n\n const timestampString = String(timestamp);\n const timestampValue =\n typeof timestamp === \"number\" ? timestamp : Number(timestamp);\n\n if (!Number.isInteger(timestampValue)) return false;\n if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) return false;\n if (!Number.isFinite(currentTime)) return false;\n\n const ageSeconds = Math.abs(currentTime - timestampValue);\n if (ageSeconds > toleranceSeconds) return false;\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestampString}.${payload}`, \"utf8\")\n .digest(\"hex\");\n\n if (expected.length !== signature.length) return false;\n if (!/^[a-f0-9]+$/i.test(signature)) return false;\n\n try {\n return timingSafeEqual(\n Buffer.from(expected, \"hex\"),\n Buffer.from(signature, \"hex\")\n );\n } catch {\n return false;\n }\n}\n","import {\n PUBLIC_WEBHOOK_EVENTS,\n isPublicWebhookEvent,\n type PublicWebhookEvent,\n} from \"@whatalo/protocol/events\";\nimport type { Product, Order, Customer } from \"../client/types.js\";\n\nexport const WEBHOOK_EVENTS = PUBLIC_WEBHOOK_EVENTS;\n\n/** All supported webhook event types */\nexport type WebhookEvent = PublicWebhookEvent;\n\n/** Validate an event header against the public runtime webhook event set. */\nexport function isWebhookEvent(value: string): value is WebhookEvent {\n return isPublicWebhookEvent(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasStringId(value: unknown): value is { id: string } {\n return isRecord(value) && typeof value.id === \"string\";\n}\n\nfunction isOptionalString(\n value: Record<string, unknown>,\n key: string\n): boolean {\n return value[key] === undefined || typeof value[key] === \"string\";\n}\n\nfunction isRuntimeWebhookStore(value: unknown): value is RuntimeWebhookStore {\n return (\n isRecord(value) &&\n typeof value.id === \"string\" &&\n typeof value.timezone === \"string\" &&\n (value.name === undefined || typeof value.name === \"string\")\n );\n}\n\nfunction hasValidBasePayloadFields(value: Record<string, unknown>): boolean {\n if (!isOptionalString(value, \"event_id\")) return false;\n if (!isOptionalString(value, \"occurred_at\")) return false;\n\n return isRuntimeWebhookStore(value.store);\n}\n\nfunction isRuntimeOrderWebhookPayload(value: Record<string, unknown>): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n (value.customer === undefined || hasStringId(value.customer))\n );\n}\n\nfunction isRuntimeProductWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.product);\n}\n\nfunction isRuntimeCustomerWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.customer);\n}\n\nfunction isRuntimeCheckoutCompletedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n hasStringId(value.customer) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nfunction isRuntimeCheckoutAbandonedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n // An abandoned draft is pre-customer, so `customer` carries no `id` and is not required.\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.checkout) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nexport interface OrderCreatedWebhookData {\n order: Order;\n}\n\nexport interface OrderUpdatedWebhookData {\n order: Order;\n changes: string[];\n}\n\nexport interface OrderCancelledWebhookData {\n orderId: string;\n reason?: string;\n}\n\nexport interface OrderCompletedWebhookData {\n order: Order;\n}\n\nexport interface ProductCreatedWebhookData {\n product: Product;\n}\n\nexport interface ProductUpdatedWebhookData {\n product: Product;\n changes: string[];\n}\n\nexport interface ProductDeletedWebhookProduct {\n id: string;\n}\n\nexport interface ProductDeletedWebhookData {\n product: ProductDeletedWebhookProduct;\n}\n\nexport interface CustomerCreatedWebhookData {\n customer: Customer;\n}\n\nexport interface CustomerUpdatedWebhookData {\n customer: Customer;\n changes: string[];\n}\n\nexport interface CheckoutCompletedWebhookData {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Contact captured on an abandoned draft. No `id` — the draft is pre-customer. */\nexport interface CheckoutAbandonedWebhookCustomer {\n name: string | null;\n email: string | null;\n phone: string | null;\n}\n\nexport interface CheckoutAbandonedWebhookData {\n checkout: RuntimeWebhookCheckout;\n customer: CheckoutAbandonedWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Map of webhook events to their payload data shapes */\nexport interface WebhookEventData {\n \"order.created\": OrderCreatedWebhookData;\n \"order.updated\": OrderUpdatedWebhookData;\n \"order.cancelled\": OrderCancelledWebhookData;\n \"order.completed\": OrderCompletedWebhookData;\n \"product.created\": ProductCreatedWebhookData;\n \"product.updated\": ProductUpdatedWebhookData;\n \"product.deleted\": ProductDeletedWebhookData;\n \"customer.created\": CustomerCreatedWebhookData;\n \"customer.updated\": CustomerUpdatedWebhookData;\n \"checkout.completed\": CheckoutCompletedWebhookData;\n \"checkout.abandoned\": CheckoutAbandonedWebhookData;\n}\n\n/** Legacy envelope payload. Runtime webhook deliveries do not include the event field in the body. */\nexport interface WebhookPayload<E extends WebhookEvent = WebhookEvent> {\n event: E;\n timestamp: string;\n storeId: string;\n data: WebhookEventData[E];\n}\n\nexport interface RuntimeWebhookStore {\n id: string;\n name?: string;\n timezone: string;\n}\n\nexport interface RuntimeWebhookBasePayload {\n event_id?: string;\n occurred_at?: string;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookOrder {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookProduct {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCustomer {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCheckout {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeOrderWebhookPayload extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer?: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeProductWebhookPayload extends RuntimeWebhookBasePayload {\n product: RuntimeWebhookProduct;\n}\n\nexport interface RuntimeCustomerWebhookPayload extends RuntimeWebhookBasePayload {\n customer: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeCheckoutCompletedWebhookPayload\n extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeCheckoutAbandonedWebhookPayload\n extends RuntimeWebhookBasePayload {\n checkout: RuntimeWebhookCheckout;\n // Pre-customer draft: contact fields only, no customer id.\n customer?: CheckoutAbandonedWebhookCustomer;\n}\n\nexport interface RuntimeWebhookEventData {\n \"order.created\": RuntimeOrderWebhookPayload;\n \"order.updated\": RuntimeOrderWebhookPayload;\n \"order.cancelled\": RuntimeOrderWebhookPayload;\n \"order.completed\": RuntimeOrderWebhookPayload;\n \"product.created\": RuntimeProductWebhookPayload;\n \"product.updated\": RuntimeProductWebhookPayload;\n \"product.deleted\": RuntimeProductWebhookPayload;\n \"customer.created\": RuntimeCustomerWebhookPayload;\n \"customer.updated\": RuntimeCustomerWebhookPayload;\n \"checkout.completed\": RuntimeCheckoutCompletedWebhookPayload;\n \"checkout.abandoned\": RuntimeCheckoutAbandonedWebhookPayload;\n}\n\n/** Runtime body delivered to subscriber endpoints. The event comes from X-Webhook-Event. */\nexport type RuntimeWebhookPayload<E extends WebhookEvent = WebhookEvent> =\n RuntimeWebhookEventData[E];\n\n/** Validate the minimum runtime payload shape before invoking typed handlers. */\nexport function isRuntimeWebhookPayload<E extends WebhookEvent>(\n value: unknown,\n event: E\n): value is RuntimeWebhookPayload<E> {\n if (!isRecord(value)) return false;\n\n switch (event) {\n case WEBHOOK_EVENTS.ORDER_CREATED:\n case WEBHOOK_EVENTS.ORDER_UPDATED:\n case WEBHOOK_EVENTS.ORDER_CANCELLED:\n case WEBHOOK_EVENTS.ORDER_COMPLETED:\n return isRuntimeOrderWebhookPayload(value);\n case WEBHOOK_EVENTS.PRODUCT_CREATED:\n case WEBHOOK_EVENTS.PRODUCT_UPDATED:\n case WEBHOOK_EVENTS.PRODUCT_DELETED:\n return isRuntimeProductWebhookPayload(value);\n case WEBHOOK_EVENTS.CUSTOMER_CREATED:\n case WEBHOOK_EVENTS.CUSTOMER_UPDATED:\n return isRuntimeCustomerWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:\n return isRuntimeCheckoutCompletedWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_ABANDONED:\n return isRuntimeCheckoutAbandonedWebhookPayload(value);\n default:\n return false;\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
- import { W as WebhookHandlerOptions } from '../types-BU71_xQm.cjs';
2
- import '../types-rA6kVlpV.cjs';
1
+ import { W as WebhookHandlerOptions } from '../types-CqyAhbXM.cjs';
2
+ import '../types-CPnSzOfX.cjs';
3
3
  import '@whatalo/protocol/events';
4
4
  import '../types-B8BAV362.cjs';
5
5
 
@@ -1,5 +1,5 @@
1
- import { W as WebhookHandlerOptions } from '../types-BhtVJPgF.js';
2
- import '../types-Bo9GbqrM.js';
1
+ import { W as WebhookHandlerOptions } from '../types-Bzee9Hvr.js';
2
+ import '../types-CDKGAQ3o.js';
3
3
  import '@whatalo/protocol/events';
4
4
  import '../types-B8BAV362.js';
5
5
 
@@ -68,6 +68,9 @@ function isRuntimeCustomerWebhookPayload(value) {
68
68
  function isRuntimeCheckoutCompletedWebhookPayload(value) {
69
69
  return hasValidBasePayloadFields(value) && hasStringId(value.order) && hasStringId(value.customer) && isRuntimeWebhookStore(value.store);
70
70
  }
71
+ function isRuntimeCheckoutAbandonedWebhookPayload(value) {
72
+ return hasValidBasePayloadFields(value) && hasStringId(value.checkout) && isRuntimeWebhookStore(value.store);
73
+ }
71
74
  function isRuntimeWebhookPayload(value, event) {
72
75
  if (!isRecord(value)) return false;
73
76
  switch (event) {
@@ -85,6 +88,8 @@ function isRuntimeWebhookPayload(value, event) {
85
88
  return isRuntimeCustomerWebhookPayload(value);
86
89
  case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:
87
90
  return isRuntimeCheckoutCompletedWebhookPayload(value);
91
+ case WEBHOOK_EVENTS.CHECKOUT_ABANDONED:
92
+ return isRuntimeCheckoutAbandonedWebhookPayload(value);
88
93
  default:
89
94
  return false;
90
95
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/webhooks/verify.ts","../../src/webhooks/types.ts","../../src/adapters/hono.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst DEFAULT_TOLERANCE_SECONDS = 300;\n\nexport interface VerifyWebhookParams {\n /** Raw request body as a string (NOT parsed JSON) */\n payload: string;\n /** Value of the X-Webhook-Signature header */\n signature: string;\n /** Value of the X-Webhook-Timestamp header (Unix epoch in seconds) */\n timestamp: string | number;\n /** App's webhook secret (provided during app installation) */\n secret: string;\n /** Maximum timestamp drift allowed in seconds. Defaults to 300 seconds. */\n toleranceSeconds?: number;\n /** Current Unix epoch time in seconds. Intended for tests. */\n currentTime?: number;\n}\n\n/**\n * Verify webhook signature using HMAC-SHA256 over `${timestamp}.${payload}`.\n * Uses timing-safe comparison to prevent timing attacks.\n */\nexport function verifyWebhook({\n payload,\n signature,\n timestamp,\n secret,\n toleranceSeconds = DEFAULT_TOLERANCE_SECONDS,\n currentTime = Math.floor(Date.now() / 1000),\n}: VerifyWebhookParams): boolean {\n if (!payload || !signature || !secret || timestamp === \"\") return false;\n\n const timestampString = String(timestamp);\n const timestampValue =\n typeof timestamp === \"number\" ? timestamp : Number(timestamp);\n\n if (!Number.isInteger(timestampValue)) return false;\n if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) return false;\n if (!Number.isFinite(currentTime)) return false;\n\n const ageSeconds = Math.abs(currentTime - timestampValue);\n if (ageSeconds > toleranceSeconds) return false;\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestampString}.${payload}`, \"utf8\")\n .digest(\"hex\");\n\n if (expected.length !== signature.length) return false;\n if (!/^[a-f0-9]+$/i.test(signature)) return false;\n\n try {\n return timingSafeEqual(\n Buffer.from(expected, \"hex\"),\n Buffer.from(signature, \"hex\")\n );\n } catch {\n return false;\n }\n}\n","import {\n PUBLIC_WEBHOOK_EVENTS,\n isPublicWebhookEvent,\n type PublicWebhookEvent,\n} from \"@whatalo/protocol/events\";\nimport type { Product, Order, Customer } from \"../client/types.js\";\n\nexport const WEBHOOK_EVENTS = PUBLIC_WEBHOOK_EVENTS;\n\n/** All supported webhook event types */\nexport type WebhookEvent = PublicWebhookEvent;\n\n/** Validate an event header against the public runtime webhook event set. */\nexport function isWebhookEvent(value: string): value is WebhookEvent {\n return isPublicWebhookEvent(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasStringId(value: unknown): value is { id: string } {\n return isRecord(value) && typeof value.id === \"string\";\n}\n\nfunction isOptionalString(\n value: Record<string, unknown>,\n key: string\n): boolean {\n return value[key] === undefined || typeof value[key] === \"string\";\n}\n\nfunction isRuntimeWebhookStore(value: unknown): value is RuntimeWebhookStore {\n return (\n isRecord(value) &&\n typeof value.id === \"string\" &&\n typeof value.timezone === \"string\" &&\n (value.name === undefined || typeof value.name === \"string\")\n );\n}\n\nfunction hasValidBasePayloadFields(value: Record<string, unknown>): boolean {\n if (!isOptionalString(value, \"event_id\")) return false;\n if (!isOptionalString(value, \"occurred_at\")) return false;\n\n return isRuntimeWebhookStore(value.store);\n}\n\nfunction isRuntimeOrderWebhookPayload(value: Record<string, unknown>): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n (value.customer === undefined || hasStringId(value.customer))\n );\n}\n\nfunction isRuntimeProductWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.product);\n}\n\nfunction isRuntimeCustomerWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.customer);\n}\n\nfunction isRuntimeCheckoutCompletedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n hasStringId(value.customer) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nexport interface OrderCreatedWebhookData {\n order: Order;\n}\n\nexport interface OrderUpdatedWebhookData {\n order: Order;\n changes: string[];\n}\n\nexport interface OrderCancelledWebhookData {\n orderId: string;\n reason?: string;\n}\n\nexport interface OrderCompletedWebhookData {\n order: Order;\n}\n\nexport interface ProductCreatedWebhookData {\n product: Product;\n}\n\nexport interface ProductUpdatedWebhookData {\n product: Product;\n changes: string[];\n}\n\nexport interface ProductDeletedWebhookProduct {\n id: string;\n}\n\nexport interface ProductDeletedWebhookData {\n product: ProductDeletedWebhookProduct;\n}\n\nexport interface CustomerCreatedWebhookData {\n customer: Customer;\n}\n\nexport interface CustomerUpdatedWebhookData {\n customer: Customer;\n changes: string[];\n}\n\nexport interface CheckoutCompletedWebhookData {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Map of webhook events to their payload data shapes */\nexport interface WebhookEventData {\n \"order.created\": OrderCreatedWebhookData;\n \"order.updated\": OrderUpdatedWebhookData;\n \"order.cancelled\": OrderCancelledWebhookData;\n \"order.completed\": OrderCompletedWebhookData;\n \"product.created\": ProductCreatedWebhookData;\n \"product.updated\": ProductUpdatedWebhookData;\n \"product.deleted\": ProductDeletedWebhookData;\n \"customer.created\": CustomerCreatedWebhookData;\n \"customer.updated\": CustomerUpdatedWebhookData;\n \"checkout.completed\": CheckoutCompletedWebhookData;\n}\n\n/** Legacy envelope payload. Runtime webhook deliveries do not include the event field in the body. */\nexport interface WebhookPayload<E extends WebhookEvent = WebhookEvent> {\n event: E;\n timestamp: string;\n storeId: string;\n data: WebhookEventData[E];\n}\n\nexport interface RuntimeWebhookStore {\n id: string;\n name?: string;\n timezone: string;\n}\n\nexport interface RuntimeWebhookBasePayload {\n event_id?: string;\n occurred_at?: string;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookOrder {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookProduct {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCustomer {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeOrderWebhookPayload extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer?: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeProductWebhookPayload extends RuntimeWebhookBasePayload {\n product: RuntimeWebhookProduct;\n}\n\nexport interface RuntimeCustomerWebhookPayload extends RuntimeWebhookBasePayload {\n customer: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeCheckoutCompletedWebhookPayload\n extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookEventData {\n \"order.created\": RuntimeOrderWebhookPayload;\n \"order.updated\": RuntimeOrderWebhookPayload;\n \"order.cancelled\": RuntimeOrderWebhookPayload;\n \"order.completed\": RuntimeOrderWebhookPayload;\n \"product.created\": RuntimeProductWebhookPayload;\n \"product.updated\": RuntimeProductWebhookPayload;\n \"product.deleted\": RuntimeProductWebhookPayload;\n \"customer.created\": RuntimeCustomerWebhookPayload;\n \"customer.updated\": RuntimeCustomerWebhookPayload;\n \"checkout.completed\": RuntimeCheckoutCompletedWebhookPayload;\n}\n\n/** Runtime body delivered to subscriber endpoints. The event comes from X-Webhook-Event. */\nexport type RuntimeWebhookPayload<E extends WebhookEvent = WebhookEvent> =\n RuntimeWebhookEventData[E];\n\n/** Validate the minimum runtime payload shape before invoking typed handlers. */\nexport function isRuntimeWebhookPayload<E extends WebhookEvent>(\n value: unknown,\n event: E\n): value is RuntimeWebhookPayload<E> {\n if (!isRecord(value)) return false;\n\n switch (event) {\n case WEBHOOK_EVENTS.ORDER_CREATED:\n case WEBHOOK_EVENTS.ORDER_UPDATED:\n case WEBHOOK_EVENTS.ORDER_CANCELLED:\n case WEBHOOK_EVENTS.ORDER_COMPLETED:\n return isRuntimeOrderWebhookPayload(value);\n case WEBHOOK_EVENTS.PRODUCT_CREATED:\n case WEBHOOK_EVENTS.PRODUCT_UPDATED:\n case WEBHOOK_EVENTS.PRODUCT_DELETED:\n return isRuntimeProductWebhookPayload(value);\n case WEBHOOK_EVENTS.CUSTOMER_CREATED:\n case WEBHOOK_EVENTS.CUSTOMER_UPDATED:\n return isRuntimeCustomerWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:\n return isRuntimeCheckoutCompletedWebhookPayload(value);\n default:\n return false;\n }\n}\n","import { verifyWebhook } from \"../webhooks/verify.js\";\nimport { isRuntimeWebhookPayload, isWebhookEvent } from \"../webhooks/types.js\";\nimport type { WebhookEventHandler, WebhookHandlerOptions } from \"./types.js\";\n\nexport type HonoContextLike = {\n req: {\n text: () => Promise<string>;\n header: (name: string) => string | undefined;\n };\n json: (body: unknown, status?: number) => unknown;\n text: (body: string, status?: number) => unknown;\n};\n\nexport function createWebhookHandler(\n options: WebhookHandlerOptions\n): (c: HonoContextLike) => Promise<unknown> {\n return async function webhookHandler(c: HonoContextLike): Promise<unknown> {\n try {\n const rawPayload = await c.req.text();\n const signature = c.req.header(\"X-Webhook-Signature\") ?? \"\";\n const timestamp = c.req.header(\"X-Webhook-Timestamp\") ?? \"\";\n const event = c.req.header(\"X-Webhook-Event\") ?? \"\";\n const deliveryId = c.req.header(\"X-Webhook-Id\") ?? \"\";\n\n const isValid = verifyWebhook({\n payload: rawPayload,\n signature,\n timestamp,\n secret: options.secret,\n });\n\n if (!isValid) {\n return c.json({ error: \"Invalid signature\" }, 401);\n }\n\n if (!event) {\n return c.json({ error: \"Missing event header\" }, 400);\n }\n\n if (!isWebhookEvent(event)) {\n return c.json({ error: \"Invalid event header\" }, 400);\n }\n\n let parsedPayload: unknown;\n try {\n parsedPayload = JSON.parse(rawPayload) as unknown;\n } catch {\n return c.json({ error: \"Invalid JSON\" }, 400);\n }\n\n if (!isRuntimeWebhookPayload(parsedPayload, event)) {\n return c.json({ error: \"Invalid payload\" }, 400);\n }\n\n const context = { deliveryId, event, timestamp };\n const handler = options.handlers[event] as\n | WebhookEventHandler<typeof event>\n | undefined;\n\n if (handler) {\n await handler(parsedPayload, context);\n } else if (options.onUnhandledEvent) {\n await options.onUnhandledEvent(event, parsedPayload, context);\n }\n\n return c.text(\"OK\", 200);\n } catch {\n return c.json({ error: \"Internal error\" }, 500);\n }\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/webhooks/verify.ts","../../src/webhooks/types.ts","../../src/adapters/hono.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst DEFAULT_TOLERANCE_SECONDS = 300;\n\nexport interface VerifyWebhookParams {\n /** Raw request body as a string (NOT parsed JSON) */\n payload: string;\n /** Value of the X-Webhook-Signature header */\n signature: string;\n /** Value of the X-Webhook-Timestamp header (Unix epoch in seconds) */\n timestamp: string | number;\n /** App's webhook secret (provided during app installation) */\n secret: string;\n /** Maximum timestamp drift allowed in seconds. Defaults to 300 seconds. */\n toleranceSeconds?: number;\n /** Current Unix epoch time in seconds. Intended for tests. */\n currentTime?: number;\n}\n\n/**\n * Verify webhook signature using HMAC-SHA256 over `${timestamp}.${payload}`.\n * Uses timing-safe comparison to prevent timing attacks.\n */\nexport function verifyWebhook({\n payload,\n signature,\n timestamp,\n secret,\n toleranceSeconds = DEFAULT_TOLERANCE_SECONDS,\n currentTime = Math.floor(Date.now() / 1000),\n}: VerifyWebhookParams): boolean {\n if (!payload || !signature || !secret || timestamp === \"\") return false;\n\n const timestampString = String(timestamp);\n const timestampValue =\n typeof timestamp === \"number\" ? timestamp : Number(timestamp);\n\n if (!Number.isInteger(timestampValue)) return false;\n if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) return false;\n if (!Number.isFinite(currentTime)) return false;\n\n const ageSeconds = Math.abs(currentTime - timestampValue);\n if (ageSeconds > toleranceSeconds) return false;\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestampString}.${payload}`, \"utf8\")\n .digest(\"hex\");\n\n if (expected.length !== signature.length) return false;\n if (!/^[a-f0-9]+$/i.test(signature)) return false;\n\n try {\n return timingSafeEqual(\n Buffer.from(expected, \"hex\"),\n Buffer.from(signature, \"hex\")\n );\n } catch {\n return false;\n }\n}\n","import {\n PUBLIC_WEBHOOK_EVENTS,\n isPublicWebhookEvent,\n type PublicWebhookEvent,\n} from \"@whatalo/protocol/events\";\nimport type { Product, Order, Customer } from \"../client/types.js\";\n\nexport const WEBHOOK_EVENTS = PUBLIC_WEBHOOK_EVENTS;\n\n/** All supported webhook event types */\nexport type WebhookEvent = PublicWebhookEvent;\n\n/** Validate an event header against the public runtime webhook event set. */\nexport function isWebhookEvent(value: string): value is WebhookEvent {\n return isPublicWebhookEvent(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasStringId(value: unknown): value is { id: string } {\n return isRecord(value) && typeof value.id === \"string\";\n}\n\nfunction isOptionalString(\n value: Record<string, unknown>,\n key: string\n): boolean {\n return value[key] === undefined || typeof value[key] === \"string\";\n}\n\nfunction isRuntimeWebhookStore(value: unknown): value is RuntimeWebhookStore {\n return (\n isRecord(value) &&\n typeof value.id === \"string\" &&\n typeof value.timezone === \"string\" &&\n (value.name === undefined || typeof value.name === \"string\")\n );\n}\n\nfunction hasValidBasePayloadFields(value: Record<string, unknown>): boolean {\n if (!isOptionalString(value, \"event_id\")) return false;\n if (!isOptionalString(value, \"occurred_at\")) return false;\n\n return isRuntimeWebhookStore(value.store);\n}\n\nfunction isRuntimeOrderWebhookPayload(value: Record<string, unknown>): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n (value.customer === undefined || hasStringId(value.customer))\n );\n}\n\nfunction isRuntimeProductWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.product);\n}\n\nfunction isRuntimeCustomerWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.customer);\n}\n\nfunction isRuntimeCheckoutCompletedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n hasStringId(value.customer) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nfunction isRuntimeCheckoutAbandonedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n // An abandoned draft is pre-customer, so `customer` carries no `id` and is not required.\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.checkout) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nexport interface OrderCreatedWebhookData {\n order: Order;\n}\n\nexport interface OrderUpdatedWebhookData {\n order: Order;\n changes: string[];\n}\n\nexport interface OrderCancelledWebhookData {\n orderId: string;\n reason?: string;\n}\n\nexport interface OrderCompletedWebhookData {\n order: Order;\n}\n\nexport interface ProductCreatedWebhookData {\n product: Product;\n}\n\nexport interface ProductUpdatedWebhookData {\n product: Product;\n changes: string[];\n}\n\nexport interface ProductDeletedWebhookProduct {\n id: string;\n}\n\nexport interface ProductDeletedWebhookData {\n product: ProductDeletedWebhookProduct;\n}\n\nexport interface CustomerCreatedWebhookData {\n customer: Customer;\n}\n\nexport interface CustomerUpdatedWebhookData {\n customer: Customer;\n changes: string[];\n}\n\nexport interface CheckoutCompletedWebhookData {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Contact captured on an abandoned draft. No `id` — the draft is pre-customer. */\nexport interface CheckoutAbandonedWebhookCustomer {\n name: string | null;\n email: string | null;\n phone: string | null;\n}\n\nexport interface CheckoutAbandonedWebhookData {\n checkout: RuntimeWebhookCheckout;\n customer: CheckoutAbandonedWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Map of webhook events to their payload data shapes */\nexport interface WebhookEventData {\n \"order.created\": OrderCreatedWebhookData;\n \"order.updated\": OrderUpdatedWebhookData;\n \"order.cancelled\": OrderCancelledWebhookData;\n \"order.completed\": OrderCompletedWebhookData;\n \"product.created\": ProductCreatedWebhookData;\n \"product.updated\": ProductUpdatedWebhookData;\n \"product.deleted\": ProductDeletedWebhookData;\n \"customer.created\": CustomerCreatedWebhookData;\n \"customer.updated\": CustomerUpdatedWebhookData;\n \"checkout.completed\": CheckoutCompletedWebhookData;\n \"checkout.abandoned\": CheckoutAbandonedWebhookData;\n}\n\n/** Legacy envelope payload. Runtime webhook deliveries do not include the event field in the body. */\nexport interface WebhookPayload<E extends WebhookEvent = WebhookEvent> {\n event: E;\n timestamp: string;\n storeId: string;\n data: WebhookEventData[E];\n}\n\nexport interface RuntimeWebhookStore {\n id: string;\n name?: string;\n timezone: string;\n}\n\nexport interface RuntimeWebhookBasePayload {\n event_id?: string;\n occurred_at?: string;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookOrder {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookProduct {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCustomer {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCheckout {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeOrderWebhookPayload extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer?: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeProductWebhookPayload extends RuntimeWebhookBasePayload {\n product: RuntimeWebhookProduct;\n}\n\nexport interface RuntimeCustomerWebhookPayload extends RuntimeWebhookBasePayload {\n customer: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeCheckoutCompletedWebhookPayload\n extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeCheckoutAbandonedWebhookPayload\n extends RuntimeWebhookBasePayload {\n checkout: RuntimeWebhookCheckout;\n // Pre-customer draft: contact fields only, no customer id.\n customer?: CheckoutAbandonedWebhookCustomer;\n}\n\nexport interface RuntimeWebhookEventData {\n \"order.created\": RuntimeOrderWebhookPayload;\n \"order.updated\": RuntimeOrderWebhookPayload;\n \"order.cancelled\": RuntimeOrderWebhookPayload;\n \"order.completed\": RuntimeOrderWebhookPayload;\n \"product.created\": RuntimeProductWebhookPayload;\n \"product.updated\": RuntimeProductWebhookPayload;\n \"product.deleted\": RuntimeProductWebhookPayload;\n \"customer.created\": RuntimeCustomerWebhookPayload;\n \"customer.updated\": RuntimeCustomerWebhookPayload;\n \"checkout.completed\": RuntimeCheckoutCompletedWebhookPayload;\n \"checkout.abandoned\": RuntimeCheckoutAbandonedWebhookPayload;\n}\n\n/** Runtime body delivered to subscriber endpoints. The event comes from X-Webhook-Event. */\nexport type RuntimeWebhookPayload<E extends WebhookEvent = WebhookEvent> =\n RuntimeWebhookEventData[E];\n\n/** Validate the minimum runtime payload shape before invoking typed handlers. */\nexport function isRuntimeWebhookPayload<E extends WebhookEvent>(\n value: unknown,\n event: E\n): value is RuntimeWebhookPayload<E> {\n if (!isRecord(value)) return false;\n\n switch (event) {\n case WEBHOOK_EVENTS.ORDER_CREATED:\n case WEBHOOK_EVENTS.ORDER_UPDATED:\n case WEBHOOK_EVENTS.ORDER_CANCELLED:\n case WEBHOOK_EVENTS.ORDER_COMPLETED:\n return isRuntimeOrderWebhookPayload(value);\n case WEBHOOK_EVENTS.PRODUCT_CREATED:\n case WEBHOOK_EVENTS.PRODUCT_UPDATED:\n case WEBHOOK_EVENTS.PRODUCT_DELETED:\n return isRuntimeProductWebhookPayload(value);\n case WEBHOOK_EVENTS.CUSTOMER_CREATED:\n case WEBHOOK_EVENTS.CUSTOMER_UPDATED:\n return isRuntimeCustomerWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:\n return isRuntimeCheckoutCompletedWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_ABANDONED:\n return isRuntimeCheckoutAbandonedWebhookPayload(value);\n default:\n return false;\n }\n}\n","import { verifyWebhook } from \"../webhooks/verify.js\";\nimport { isRuntimeWebhookPayload, isWebhookEvent } from \"../webhooks/types.js\";\nimport type { WebhookEventHandler, WebhookHandlerOptions } from \"./types.js\";\n\nexport type HonoContextLike = {\n req: {\n text: () => Promise<string>;\n header: (name: string) => string | undefined;\n };\n json: (body: unknown, status?: number) => unknown;\n text: (body: string, status?: number) => unknown;\n};\n\nexport function createWebhookHandler(\n options: WebhookHandlerOptions\n): (c: HonoContextLike) => Promise<unknown> {\n return async function webhookHandler(c: HonoContextLike): Promise<unknown> {\n try {\n const rawPayload = await c.req.text();\n const signature = c.req.header(\"X-Webhook-Signature\") ?? \"\";\n const timestamp = c.req.header(\"X-Webhook-Timestamp\") ?? \"\";\n const event = c.req.header(\"X-Webhook-Event\") ?? \"\";\n const deliveryId = c.req.header(\"X-Webhook-Id\") ?? \"\";\n\n const isValid = verifyWebhook({\n payload: rawPayload,\n signature,\n timestamp,\n secret: options.secret,\n });\n\n if (!isValid) {\n return c.json({ error: \"Invalid signature\" }, 401);\n }\n\n if (!event) {\n return c.json({ error: \"Missing event header\" }, 400);\n }\n\n if (!isWebhookEvent(event)) {\n return c.json({ error: \"Invalid event header\" }, 400);\n }\n\n let parsedPayload: unknown;\n try {\n parsedPayload = JSON.parse(rawPayload) as unknown;\n } catch {\n return c.json({ error: \"Invalid JSON\" }, 400);\n }\n\n if (!isRuntimeWebhookPayload(parsedPayload, event)) {\n return c.json({ error: \"Invalid payload\" }, 400);\n }\n\n const context = { deliveryId, event, timestamp };\n const handler = options.handlers[event] as\n | WebhookEventHandler<typeof event>\n | undefined;\n\n if (handler) {\n await handler(parsedPayload, context);\n } else if (options.onUnhandledEvent) {\n await options.onUnhandledEvent(event, parsedPayload, context);\n }\n\n return c.text(\"OK\", 200);\n } catch {\n return c.json({ error: \"Internal error\" }, 500);\n }\n };\n}\n"],"mappings":"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","names":[]}
@@ -91,6 +91,9 @@ function isRuntimeCustomerWebhookPayload(value) {
91
91
  function isRuntimeCheckoutCompletedWebhookPayload(value) {
92
92
  return hasValidBasePayloadFields(value) && hasStringId(value.order) && hasStringId(value.customer) && isRuntimeWebhookStore(value.store);
93
93
  }
94
+ function isRuntimeCheckoutAbandonedWebhookPayload(value) {
95
+ return hasValidBasePayloadFields(value) && hasStringId(value.checkout) && isRuntimeWebhookStore(value.store);
96
+ }
94
97
  function isRuntimeWebhookPayload(value, event) {
95
98
  if (!isRecord(value)) return false;
96
99
  switch (event) {
@@ -108,6 +111,8 @@ function isRuntimeWebhookPayload(value, event) {
108
111
  return isRuntimeCustomerWebhookPayload(value);
109
112
  case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:
110
113
  return isRuntimeCheckoutCompletedWebhookPayload(value);
114
+ case WEBHOOK_EVENTS.CHECKOUT_ABANDONED:
115
+ return isRuntimeCheckoutAbandonedWebhookPayload(value);
111
116
  default:
112
117
  return false;
113
118
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/nextjs.ts","../../src/webhooks/verify.ts","../../src/webhooks/types.ts"],"sourcesContent":["import { verifyWebhook } from \"../webhooks/verify.js\";\nimport { isRuntimeWebhookPayload, isWebhookEvent } from \"../webhooks/types.js\";\nimport type { WebhookEventHandler, WebhookHandlerOptions } from \"./types.js\";\n\nexport function createWebhookHandler(\n options: WebhookHandlerOptions\n): (request: Request) => Promise<Response> {\n return async function webhookHandler(request: Request): Promise<Response> {\n try {\n const rawPayload = await request.text();\n const signature = request.headers.get(\"X-Webhook-Signature\") ?? \"\";\n const timestamp = request.headers.get(\"X-Webhook-Timestamp\") ?? \"\";\n const event = request.headers.get(\"X-Webhook-Event\") ?? \"\";\n const deliveryId = request.headers.get(\"X-Webhook-Id\") ?? \"\";\n\n const isValid = verifyWebhook({\n payload: rawPayload,\n signature,\n timestamp,\n secret: options.secret,\n });\n\n if (!isValid) {\n return new Response(\"Invalid signature\", { status: 401 });\n }\n\n if (!event) {\n return new Response(\"Missing event header\", { status: 400 });\n }\n\n if (!isWebhookEvent(event)) {\n return new Response(\"Invalid event header\", { status: 400 });\n }\n\n let parsedPayload: unknown;\n try {\n parsedPayload = JSON.parse(rawPayload) as unknown;\n } catch {\n return new Response(\"Invalid JSON\", { status: 400 });\n }\n\n if (!isRuntimeWebhookPayload(parsedPayload, event)) {\n return new Response(\"Invalid payload\", { status: 400 });\n }\n\n const context = { deliveryId, event, timestamp };\n const handler = options.handlers[event] as\n | WebhookEventHandler<typeof event>\n | undefined;\n\n if (handler) {\n await handler(parsedPayload, context);\n } else if (options.onUnhandledEvent) {\n await options.onUnhandledEvent(event, parsedPayload, context);\n }\n\n return new Response(\"OK\", { status: 200 });\n } catch {\n return new Response(\"Internal error\", { status: 500 });\n }\n };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst DEFAULT_TOLERANCE_SECONDS = 300;\n\nexport interface VerifyWebhookParams {\n /** Raw request body as a string (NOT parsed JSON) */\n payload: string;\n /** Value of the X-Webhook-Signature header */\n signature: string;\n /** Value of the X-Webhook-Timestamp header (Unix epoch in seconds) */\n timestamp: string | number;\n /** App's webhook secret (provided during app installation) */\n secret: string;\n /** Maximum timestamp drift allowed in seconds. Defaults to 300 seconds. */\n toleranceSeconds?: number;\n /** Current Unix epoch time in seconds. Intended for tests. */\n currentTime?: number;\n}\n\n/**\n * Verify webhook signature using HMAC-SHA256 over `${timestamp}.${payload}`.\n * Uses timing-safe comparison to prevent timing attacks.\n */\nexport function verifyWebhook({\n payload,\n signature,\n timestamp,\n secret,\n toleranceSeconds = DEFAULT_TOLERANCE_SECONDS,\n currentTime = Math.floor(Date.now() / 1000),\n}: VerifyWebhookParams): boolean {\n if (!payload || !signature || !secret || timestamp === \"\") return false;\n\n const timestampString = String(timestamp);\n const timestampValue =\n typeof timestamp === \"number\" ? timestamp : Number(timestamp);\n\n if (!Number.isInteger(timestampValue)) return false;\n if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) return false;\n if (!Number.isFinite(currentTime)) return false;\n\n const ageSeconds = Math.abs(currentTime - timestampValue);\n if (ageSeconds > toleranceSeconds) return false;\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestampString}.${payload}`, \"utf8\")\n .digest(\"hex\");\n\n if (expected.length !== signature.length) return false;\n if (!/^[a-f0-9]+$/i.test(signature)) return false;\n\n try {\n return timingSafeEqual(\n Buffer.from(expected, \"hex\"),\n Buffer.from(signature, \"hex\")\n );\n } catch {\n return false;\n }\n}\n","import {\n PUBLIC_WEBHOOK_EVENTS,\n isPublicWebhookEvent,\n type PublicWebhookEvent,\n} from \"@whatalo/protocol/events\";\nimport type { Product, Order, Customer } from \"../client/types.js\";\n\nexport const WEBHOOK_EVENTS = PUBLIC_WEBHOOK_EVENTS;\n\n/** All supported webhook event types */\nexport type WebhookEvent = PublicWebhookEvent;\n\n/** Validate an event header against the public runtime webhook event set. */\nexport function isWebhookEvent(value: string): value is WebhookEvent {\n return isPublicWebhookEvent(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasStringId(value: unknown): value is { id: string } {\n return isRecord(value) && typeof value.id === \"string\";\n}\n\nfunction isOptionalString(\n value: Record<string, unknown>,\n key: string\n): boolean {\n return value[key] === undefined || typeof value[key] === \"string\";\n}\n\nfunction isRuntimeWebhookStore(value: unknown): value is RuntimeWebhookStore {\n return (\n isRecord(value) &&\n typeof value.id === \"string\" &&\n typeof value.timezone === \"string\" &&\n (value.name === undefined || typeof value.name === \"string\")\n );\n}\n\nfunction hasValidBasePayloadFields(value: Record<string, unknown>): boolean {\n if (!isOptionalString(value, \"event_id\")) return false;\n if (!isOptionalString(value, \"occurred_at\")) return false;\n\n return isRuntimeWebhookStore(value.store);\n}\n\nfunction isRuntimeOrderWebhookPayload(value: Record<string, unknown>): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n (value.customer === undefined || hasStringId(value.customer))\n );\n}\n\nfunction isRuntimeProductWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.product);\n}\n\nfunction isRuntimeCustomerWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.customer);\n}\n\nfunction isRuntimeCheckoutCompletedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n hasStringId(value.customer) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nexport interface OrderCreatedWebhookData {\n order: Order;\n}\n\nexport interface OrderUpdatedWebhookData {\n order: Order;\n changes: string[];\n}\n\nexport interface OrderCancelledWebhookData {\n orderId: string;\n reason?: string;\n}\n\nexport interface OrderCompletedWebhookData {\n order: Order;\n}\n\nexport interface ProductCreatedWebhookData {\n product: Product;\n}\n\nexport interface ProductUpdatedWebhookData {\n product: Product;\n changes: string[];\n}\n\nexport interface ProductDeletedWebhookProduct {\n id: string;\n}\n\nexport interface ProductDeletedWebhookData {\n product: ProductDeletedWebhookProduct;\n}\n\nexport interface CustomerCreatedWebhookData {\n customer: Customer;\n}\n\nexport interface CustomerUpdatedWebhookData {\n customer: Customer;\n changes: string[];\n}\n\nexport interface CheckoutCompletedWebhookData {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Map of webhook events to their payload data shapes */\nexport interface WebhookEventData {\n \"order.created\": OrderCreatedWebhookData;\n \"order.updated\": OrderUpdatedWebhookData;\n \"order.cancelled\": OrderCancelledWebhookData;\n \"order.completed\": OrderCompletedWebhookData;\n \"product.created\": ProductCreatedWebhookData;\n \"product.updated\": ProductUpdatedWebhookData;\n \"product.deleted\": ProductDeletedWebhookData;\n \"customer.created\": CustomerCreatedWebhookData;\n \"customer.updated\": CustomerUpdatedWebhookData;\n \"checkout.completed\": CheckoutCompletedWebhookData;\n}\n\n/** Legacy envelope payload. Runtime webhook deliveries do not include the event field in the body. */\nexport interface WebhookPayload<E extends WebhookEvent = WebhookEvent> {\n event: E;\n timestamp: string;\n storeId: string;\n data: WebhookEventData[E];\n}\n\nexport interface RuntimeWebhookStore {\n id: string;\n name?: string;\n timezone: string;\n}\n\nexport interface RuntimeWebhookBasePayload {\n event_id?: string;\n occurred_at?: string;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookOrder {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookProduct {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCustomer {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeOrderWebhookPayload extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer?: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeProductWebhookPayload extends RuntimeWebhookBasePayload {\n product: RuntimeWebhookProduct;\n}\n\nexport interface RuntimeCustomerWebhookPayload extends RuntimeWebhookBasePayload {\n customer: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeCheckoutCompletedWebhookPayload\n extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookEventData {\n \"order.created\": RuntimeOrderWebhookPayload;\n \"order.updated\": RuntimeOrderWebhookPayload;\n \"order.cancelled\": RuntimeOrderWebhookPayload;\n \"order.completed\": RuntimeOrderWebhookPayload;\n \"product.created\": RuntimeProductWebhookPayload;\n \"product.updated\": RuntimeProductWebhookPayload;\n \"product.deleted\": RuntimeProductWebhookPayload;\n \"customer.created\": RuntimeCustomerWebhookPayload;\n \"customer.updated\": RuntimeCustomerWebhookPayload;\n \"checkout.completed\": RuntimeCheckoutCompletedWebhookPayload;\n}\n\n/** Runtime body delivered to subscriber endpoints. The event comes from X-Webhook-Event. */\nexport type RuntimeWebhookPayload<E extends WebhookEvent = WebhookEvent> =\n RuntimeWebhookEventData[E];\n\n/** Validate the minimum runtime payload shape before invoking typed handlers. */\nexport function isRuntimeWebhookPayload<E extends WebhookEvent>(\n value: unknown,\n event: E\n): value is RuntimeWebhookPayload<E> {\n if (!isRecord(value)) return false;\n\n switch (event) {\n case WEBHOOK_EVENTS.ORDER_CREATED:\n case WEBHOOK_EVENTS.ORDER_UPDATED:\n case WEBHOOK_EVENTS.ORDER_CANCELLED:\n case WEBHOOK_EVENTS.ORDER_COMPLETED:\n return isRuntimeOrderWebhookPayload(value);\n case WEBHOOK_EVENTS.PRODUCT_CREATED:\n case WEBHOOK_EVENTS.PRODUCT_UPDATED:\n case WEBHOOK_EVENTS.PRODUCT_DELETED:\n return isRuntimeProductWebhookPayload(value);\n case WEBHOOK_EVENTS.CUSTOMER_CREATED:\n case WEBHOOK_EVENTS.CUSTOMER_UPDATED:\n return isRuntimeCustomerWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:\n return isRuntimeCheckoutCompletedWebhookPayload(value);\n default:\n return false;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/adapters/nextjs.ts","../../src/webhooks/verify.ts","../../src/webhooks/types.ts"],"sourcesContent":["import { verifyWebhook } from \"../webhooks/verify.js\";\nimport { isRuntimeWebhookPayload, isWebhookEvent } from \"../webhooks/types.js\";\nimport type { WebhookEventHandler, WebhookHandlerOptions } from \"./types.js\";\n\nexport function createWebhookHandler(\n options: WebhookHandlerOptions\n): (request: Request) => Promise<Response> {\n return async function webhookHandler(request: Request): Promise<Response> {\n try {\n const rawPayload = await request.text();\n const signature = request.headers.get(\"X-Webhook-Signature\") ?? \"\";\n const timestamp = request.headers.get(\"X-Webhook-Timestamp\") ?? \"\";\n const event = request.headers.get(\"X-Webhook-Event\") ?? \"\";\n const deliveryId = request.headers.get(\"X-Webhook-Id\") ?? \"\";\n\n const isValid = verifyWebhook({\n payload: rawPayload,\n signature,\n timestamp,\n secret: options.secret,\n });\n\n if (!isValid) {\n return new Response(\"Invalid signature\", { status: 401 });\n }\n\n if (!event) {\n return new Response(\"Missing event header\", { status: 400 });\n }\n\n if (!isWebhookEvent(event)) {\n return new Response(\"Invalid event header\", { status: 400 });\n }\n\n let parsedPayload: unknown;\n try {\n parsedPayload = JSON.parse(rawPayload) as unknown;\n } catch {\n return new Response(\"Invalid JSON\", { status: 400 });\n }\n\n if (!isRuntimeWebhookPayload(parsedPayload, event)) {\n return new Response(\"Invalid payload\", { status: 400 });\n }\n\n const context = { deliveryId, event, timestamp };\n const handler = options.handlers[event] as\n | WebhookEventHandler<typeof event>\n | undefined;\n\n if (handler) {\n await handler(parsedPayload, context);\n } else if (options.onUnhandledEvent) {\n await options.onUnhandledEvent(event, parsedPayload, context);\n }\n\n return new Response(\"OK\", { status: 200 });\n } catch {\n return new Response(\"Internal error\", { status: 500 });\n }\n };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst DEFAULT_TOLERANCE_SECONDS = 300;\n\nexport interface VerifyWebhookParams {\n /** Raw request body as a string (NOT parsed JSON) */\n payload: string;\n /** Value of the X-Webhook-Signature header */\n signature: string;\n /** Value of the X-Webhook-Timestamp header (Unix epoch in seconds) */\n timestamp: string | number;\n /** App's webhook secret (provided during app installation) */\n secret: string;\n /** Maximum timestamp drift allowed in seconds. Defaults to 300 seconds. */\n toleranceSeconds?: number;\n /** Current Unix epoch time in seconds. Intended for tests. */\n currentTime?: number;\n}\n\n/**\n * Verify webhook signature using HMAC-SHA256 over `${timestamp}.${payload}`.\n * Uses timing-safe comparison to prevent timing attacks.\n */\nexport function verifyWebhook({\n payload,\n signature,\n timestamp,\n secret,\n toleranceSeconds = DEFAULT_TOLERANCE_SECONDS,\n currentTime = Math.floor(Date.now() / 1000),\n}: VerifyWebhookParams): boolean {\n if (!payload || !signature || !secret || timestamp === \"\") return false;\n\n const timestampString = String(timestamp);\n const timestampValue =\n typeof timestamp === \"number\" ? timestamp : Number(timestamp);\n\n if (!Number.isInteger(timestampValue)) return false;\n if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0) return false;\n if (!Number.isFinite(currentTime)) return false;\n\n const ageSeconds = Math.abs(currentTime - timestampValue);\n if (ageSeconds > toleranceSeconds) return false;\n\n const expected = createHmac(\"sha256\", secret)\n .update(`${timestampString}.${payload}`, \"utf8\")\n .digest(\"hex\");\n\n if (expected.length !== signature.length) return false;\n if (!/^[a-f0-9]+$/i.test(signature)) return false;\n\n try {\n return timingSafeEqual(\n Buffer.from(expected, \"hex\"),\n Buffer.from(signature, \"hex\")\n );\n } catch {\n return false;\n }\n}\n","import {\n PUBLIC_WEBHOOK_EVENTS,\n isPublicWebhookEvent,\n type PublicWebhookEvent,\n} from \"@whatalo/protocol/events\";\nimport type { Product, Order, Customer } from \"../client/types.js\";\n\nexport const WEBHOOK_EVENTS = PUBLIC_WEBHOOK_EVENTS;\n\n/** All supported webhook event types */\nexport type WebhookEvent = PublicWebhookEvent;\n\n/** Validate an event header against the public runtime webhook event set. */\nexport function isWebhookEvent(value: string): value is WebhookEvent {\n return isPublicWebhookEvent(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasStringId(value: unknown): value is { id: string } {\n return isRecord(value) && typeof value.id === \"string\";\n}\n\nfunction isOptionalString(\n value: Record<string, unknown>,\n key: string\n): boolean {\n return value[key] === undefined || typeof value[key] === \"string\";\n}\n\nfunction isRuntimeWebhookStore(value: unknown): value is RuntimeWebhookStore {\n return (\n isRecord(value) &&\n typeof value.id === \"string\" &&\n typeof value.timezone === \"string\" &&\n (value.name === undefined || typeof value.name === \"string\")\n );\n}\n\nfunction hasValidBasePayloadFields(value: Record<string, unknown>): boolean {\n if (!isOptionalString(value, \"event_id\")) return false;\n if (!isOptionalString(value, \"occurred_at\")) return false;\n\n return isRuntimeWebhookStore(value.store);\n}\n\nfunction isRuntimeOrderWebhookPayload(value: Record<string, unknown>): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n (value.customer === undefined || hasStringId(value.customer))\n );\n}\n\nfunction isRuntimeProductWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.product);\n}\n\nfunction isRuntimeCustomerWebhookPayload(value: Record<string, unknown>): boolean {\n return hasValidBasePayloadFields(value) && hasStringId(value.customer);\n}\n\nfunction isRuntimeCheckoutCompletedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.order) &&\n hasStringId(value.customer) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nfunction isRuntimeCheckoutAbandonedWebhookPayload(\n value: Record<string, unknown>\n): boolean {\n // An abandoned draft is pre-customer, so `customer` carries no `id` and is not required.\n return (\n hasValidBasePayloadFields(value) &&\n hasStringId(value.checkout) &&\n isRuntimeWebhookStore(value.store)\n );\n}\n\nexport interface OrderCreatedWebhookData {\n order: Order;\n}\n\nexport interface OrderUpdatedWebhookData {\n order: Order;\n changes: string[];\n}\n\nexport interface OrderCancelledWebhookData {\n orderId: string;\n reason?: string;\n}\n\nexport interface OrderCompletedWebhookData {\n order: Order;\n}\n\nexport interface ProductCreatedWebhookData {\n product: Product;\n}\n\nexport interface ProductUpdatedWebhookData {\n product: Product;\n changes: string[];\n}\n\nexport interface ProductDeletedWebhookProduct {\n id: string;\n}\n\nexport interface ProductDeletedWebhookData {\n product: ProductDeletedWebhookProduct;\n}\n\nexport interface CustomerCreatedWebhookData {\n customer: Customer;\n}\n\nexport interface CustomerUpdatedWebhookData {\n customer: Customer;\n changes: string[];\n}\n\nexport interface CheckoutCompletedWebhookData {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Contact captured on an abandoned draft. No `id` — the draft is pre-customer. */\nexport interface CheckoutAbandonedWebhookCustomer {\n name: string | null;\n email: string | null;\n phone: string | null;\n}\n\nexport interface CheckoutAbandonedWebhookData {\n checkout: RuntimeWebhookCheckout;\n customer: CheckoutAbandonedWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\n/** Map of webhook events to their payload data shapes */\nexport interface WebhookEventData {\n \"order.created\": OrderCreatedWebhookData;\n \"order.updated\": OrderUpdatedWebhookData;\n \"order.cancelled\": OrderCancelledWebhookData;\n \"order.completed\": OrderCompletedWebhookData;\n \"product.created\": ProductCreatedWebhookData;\n \"product.updated\": ProductUpdatedWebhookData;\n \"product.deleted\": ProductDeletedWebhookData;\n \"customer.created\": CustomerCreatedWebhookData;\n \"customer.updated\": CustomerUpdatedWebhookData;\n \"checkout.completed\": CheckoutCompletedWebhookData;\n \"checkout.abandoned\": CheckoutAbandonedWebhookData;\n}\n\n/** Legacy envelope payload. Runtime webhook deliveries do not include the event field in the body. */\nexport interface WebhookPayload<E extends WebhookEvent = WebhookEvent> {\n event: E;\n timestamp: string;\n storeId: string;\n data: WebhookEventData[E];\n}\n\nexport interface RuntimeWebhookStore {\n id: string;\n name?: string;\n timezone: string;\n}\n\nexport interface RuntimeWebhookBasePayload {\n event_id?: string;\n occurred_at?: string;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeWebhookOrder {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookProduct {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCustomer {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeWebhookCheckout {\n id: string;\n [key: string]: unknown;\n}\n\nexport interface RuntimeOrderWebhookPayload extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer?: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeProductWebhookPayload extends RuntimeWebhookBasePayload {\n product: RuntimeWebhookProduct;\n}\n\nexport interface RuntimeCustomerWebhookPayload extends RuntimeWebhookBasePayload {\n customer: RuntimeWebhookCustomer;\n}\n\nexport interface RuntimeCheckoutCompletedWebhookPayload\n extends RuntimeWebhookBasePayload {\n order: RuntimeWebhookOrder;\n customer: RuntimeWebhookCustomer;\n store: RuntimeWebhookStore;\n}\n\nexport interface RuntimeCheckoutAbandonedWebhookPayload\n extends RuntimeWebhookBasePayload {\n checkout: RuntimeWebhookCheckout;\n // Pre-customer draft: contact fields only, no customer id.\n customer?: CheckoutAbandonedWebhookCustomer;\n}\n\nexport interface RuntimeWebhookEventData {\n \"order.created\": RuntimeOrderWebhookPayload;\n \"order.updated\": RuntimeOrderWebhookPayload;\n \"order.cancelled\": RuntimeOrderWebhookPayload;\n \"order.completed\": RuntimeOrderWebhookPayload;\n \"product.created\": RuntimeProductWebhookPayload;\n \"product.updated\": RuntimeProductWebhookPayload;\n \"product.deleted\": RuntimeProductWebhookPayload;\n \"customer.created\": RuntimeCustomerWebhookPayload;\n \"customer.updated\": RuntimeCustomerWebhookPayload;\n \"checkout.completed\": RuntimeCheckoutCompletedWebhookPayload;\n \"checkout.abandoned\": RuntimeCheckoutAbandonedWebhookPayload;\n}\n\n/** Runtime body delivered to subscriber endpoints. The event comes from X-Webhook-Event. */\nexport type RuntimeWebhookPayload<E extends WebhookEvent = WebhookEvent> =\n RuntimeWebhookEventData[E];\n\n/** Validate the minimum runtime payload shape before invoking typed handlers. */\nexport function isRuntimeWebhookPayload<E extends WebhookEvent>(\n value: unknown,\n event: E\n): value is RuntimeWebhookPayload<E> {\n if (!isRecord(value)) return false;\n\n switch (event) {\n case WEBHOOK_EVENTS.ORDER_CREATED:\n case WEBHOOK_EVENTS.ORDER_UPDATED:\n case WEBHOOK_EVENTS.ORDER_CANCELLED:\n case WEBHOOK_EVENTS.ORDER_COMPLETED:\n return isRuntimeOrderWebhookPayload(value);\n case WEBHOOK_EVENTS.PRODUCT_CREATED:\n case WEBHOOK_EVENTS.PRODUCT_UPDATED:\n case WEBHOOK_EVENTS.PRODUCT_DELETED:\n return isRuntimeProductWebhookPayload(value);\n case WEBHOOK_EVENTS.CUSTOMER_CREATED:\n case WEBHOOK_EVENTS.CUSTOMER_UPDATED:\n return isRuntimeCustomerWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:\n return isRuntimeCheckoutCompletedWebhookPayload(value);\n case WEBHOOK_EVENTS.CHECKOUT_ABANDONED:\n return isRuntimeCheckoutAbandonedWebhookPayload(value);\n default:\n return false;\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
- import { W as WebhookHandlerOptions } from '../types-BU71_xQm.cjs';
2
- import '../types-rA6kVlpV.cjs';
1
+ import { W as WebhookHandlerOptions } from '../types-CqyAhbXM.cjs';
2
+ import '../types-CPnSzOfX.cjs';
3
3
  import '@whatalo/protocol/events';
4
4
  import '../types-B8BAV362.cjs';
5
5
 
@@ -1,5 +1,5 @@
1
- import { W as WebhookHandlerOptions } from '../types-BhtVJPgF.js';
2
- import '../types-Bo9GbqrM.js';
1
+ import { W as WebhookHandlerOptions } from '../types-Bzee9Hvr.js';
2
+ import '../types-CDKGAQ3o.js';
3
3
  import '@whatalo/protocol/events';
4
4
  import '../types-B8BAV362.js';
5
5
 
@@ -68,6 +68,9 @@ function isRuntimeCustomerWebhookPayload(value) {
68
68
  function isRuntimeCheckoutCompletedWebhookPayload(value) {
69
69
  return hasValidBasePayloadFields(value) && hasStringId(value.order) && hasStringId(value.customer) && isRuntimeWebhookStore(value.store);
70
70
  }
71
+ function isRuntimeCheckoutAbandonedWebhookPayload(value) {
72
+ return hasValidBasePayloadFields(value) && hasStringId(value.checkout) && isRuntimeWebhookStore(value.store);
73
+ }
71
74
  function isRuntimeWebhookPayload(value, event) {
72
75
  if (!isRecord(value)) return false;
73
76
  switch (event) {
@@ -85,6 +88,8 @@ function isRuntimeWebhookPayload(value, event) {
85
88
  return isRuntimeCustomerWebhookPayload(value);
86
89
  case WEBHOOK_EVENTS.CHECKOUT_COMPLETED:
87
90
  return isRuntimeCheckoutCompletedWebhookPayload(value);
91
+ case WEBHOOK_EVENTS.CHECKOUT_ABANDONED:
92
+ return isRuntimeCheckoutAbandonedWebhookPayload(value);
88
93
  default:
89
94
  return false;
90
95
  }