npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@shopkit/webhooks

v0.5.1

Published

Webhook utilities for Shopify storefront applications

Readme

@shopkit/webhooks

Webhook utilities for Shopify storefront applications. Handles webhook verification, cache invalidation, and Next.js page revalidation.

Installation

bun add @shopkit/webhooks

Quick Start

import { handleShopifyWebhook } from "@shopkit/webhooks";

// In your Next.js API route
export async function POST(request: Request) {
  const response = await handleShopifyWebhook(request, {
    secret: process.env.SHOPIFY_WEBHOOK_SECRET!,
  });

  return Response.json(response, {
    status: response.success ? 200 : 400,
  });
}

Features

  • HMAC Verification: Validates Shopify webhook signatures (SHA-256)
  • Cache Invalidation: Automatically determines which caches to invalidate based on entity type
  • Page Revalidation: Triggers Next.js ISR revalidation for affected pages
  • Entity Support: Products, collections, and inventory updates

Supported Webhooks

| Entity | Events | Revalidation Path | |--------|--------|-------------------| | Product | create, update, delete | /products/{handle} | | Collection | create, update, delete | /collections/{handle} | | Inventory | update | Requires product context |

Cache-tag taxonomy — who emits, who purges

On-demand invalidation is a two-sided contract: a purge only reaches a page if the read that built the page attached the same tag. Both sides import the strings from @shopkit/core/cache-tags, and both sides are covered by an invariant test, so the table below cannot silently drift:

  • @shopkit/data-layer → custom-cache-tag-coverage.test.ts forces every client method to be classified as a tagged read or as untagged-by-design.
  • @shopkit/webhooks → cache-tag-coverage.test.ts fails if any tag in the taxonomy has no event that purges it, or if the planner invents a tag outside the taxonomy.

| Tag | Emitted by (custom adapter read) | Purged by | |---|---|---| | product:{handle} | getProduct, getProductsByHandles | product.*, inventory.updated, pricing.updated, products.bulk_updated | | collection:{handle} | getCollection, getCollectionsByHandles, getCollectionFilters | collection.*, product.* (via the payload's collections), collections.bulk_updated | | collections | getCollection, getCollections, getCollectionsByHandles | collection.created/.deleted/rename, collections.bulk_updated | | products | getProducts (lists, sitemaps, search endpoints) | product.created/.deleted/rename, products.bulk_updated | | search | nothing — see below | nothing, deliberately | | nav-menu | @shopkit/navigation menu fetch | navigation.updated | | seo | @shopkit/seo remote-config fetch | seo.updated |

Two rules explain every row:

  1. Entity events purge entity tags; set-changing events also purge the broad tag. A product being created, deleted or renamed changes which URLs exist, and list surfaces (sitemap, search, PLP rails) carry only the broad tags. An ordinary edit does not: its entity tag already reaches every surface that rendered it.
  2. High-frequency events stay entity-scoped. inventory.updated and pricing.updated arrive on a schedule, so they never fire broad tags — that would keep every list page rebuilding. Bulk events do fire them, because the handles a bulk delivery reports are not a guarantee of everything that moved.

cache.clear_all appears in no row: it purges by layout scope (revalidatePath("/", "layout")), which reaches more than any tag list could. Next derives an implicit _N_T_/layout tag for every page, and a cache entry is validated against its own tags union the reading request's implicit tags — so rendered pages and the fetch entries behind them both go, without submitting a single entity tag.

CacheTags.search() is in the taxonomy but unused on both sides: search results are read through getProducts, which already carries products, so a search purge could never reach anything products does not. Purging a tag no read attaches is the failure that got the search.reindex event removed — it returns 200 having invalidated nothing, and the "purged nothing" guard cannot see it because a tag was submitted. The coverage test records it as unproduced rather than wiring it.

Per-shopper reads (cart, orders, customers) and every mutation carry no tags by design: a tag implies a shared cache entry, and sharing a cart between shoppers is a data leak rather than a stale page.

Not covered: the Shopify (direct Storefront API) adapter emits no tags at all. Its requests go through @shopify/storefront-api-client, which owns the fetch call and accepts no Next.js cache options, so tagging it needs a customFetchApi shim first. Every storefront in production runs the custom adapter (NEXT_PUBLIC_PLATFORM=custom).

Configuration

handleShopifyWebhook(request, {
  secret: process.env.SHOPIFY_WEBHOOK_SECRET!,
  // Additional options as needed
});

Environment Variables

| Variable | Description | |----------|-------------| | SHOPIFY_WEBHOOK_SECRET | Shopify webhook HMAC secret for signature verification |

Peer Dependencies

  • next >= 14.0.0

Architecture

See ARCHITECTURE.md for detailed system design.