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

hookwright

v2.3.7

Published

Build real, signed e-commerce webhooks from a live Shopify catalogue — interactive terminal UI, no backend

Readme

hookwright

Build real, signed e-commerce webhooks from a live Shopify catalogue — and send them to your own endpoint.

Nothing in the payload is invented. Product titles, variant ids, prices and image URLs all come from your Shopify store through the Admin API; totals are computed from those real prices. It is a terminal app built with Ink — there is no backend and no service to sign up for.

╭──────────────────────────────────────────────────────────────╮
│ hookwright                                   Addy Fashion │
╰──────────────────────────────────────────────────────────────╯
 real catalogue → signed provider webhooks

❯ Setup             Shopify and webhook destination
  Integrations      build and send a provider webhook
  History           review or re-send a saved payload
  Doctor            check credentials and connectivity
  Clear             remove cached data
  Quit

Install

npx hookwright          # no install
npm i -g hookwright     # or install it

Requires Node 20+.

Quick start

hookwright              # interactive UI — starts in the setup wizard on first run

Setup and Integrations sit side by side. Setup is global — configure it once and every integration uses it:

| Setup | asks for | |---|---| | Shopify | store domain + Admin API access token (needs read_products). Verifies immediately and picks up currency, country, province and contact details from the store record | | Webhook | one destination URL + signing secret per integration — Cashfree, Razorpay Magic and Nitro each get their own | | Customer | the shopper the abandoned cart belongs to | | Defaults | currency, platform |

Stored in ~/.config/hookwright/config.json, mode 0600 — it holds a Shopify token and a signing secret.

Then Integrations → Cashfree One Click Checkout builds the payload:

  1. Products are fetched live from your store and listed — pick what goes in the cart
  2. Variant and quantity per product
  3. Cart discount
  4. Either accept the suggested values, or review every field — each one is pre-filled with the generated suggestion, enter accepts it, typing replaces it
  5. Pre-flight report — every check, the full field coverage table, the signed request and the complete payload
  6. A ready-to-paste curl is copied to your clipboard automatically, then send

The report page is a compact status board with a command bar — s send · c copy curl · j copy json · u curl · p payload · f fields · v checks · r request. Panels are collapsed by default, so you see the verdict first and the detail only when you ask for it.

esc walks back one step at a time through the whole flow (report → mode → discount → quantity → variant → products → event), and only leaves the integration from the first step.

The curl is built for pasting

The command is emitted in the exact shape Postman itself exports — curl --location --request POST with unindented --header / --data lines — and the body is inlined, never @file. Drop it straight into Postman → Import → Raw text, a colleague's terminal, or a bug report.

Single quotes inside product titles (Levi's 501) are escaped, and the signature travels with the exact body it signs, so a pasted command verifies precisely as the original request would.

Two things keep this honest: a test that executes the generated command verbatim against a receiver doing real HMAC verification, and a test that runs it through curl-to-postmanv2 — the same parser Postman uses for Raw-text import — asserting the URL, method, every header and a byte-identical body all survive.

In the report screen: c re-copies the curl · j copies the raw payload JSON · p toggles the full payload view.

Headless usage

Every command works without a TTY, for CI or scripting:

hookwright build --items 2                              # cashfree-occ by default
hookwright build --provider razorpay-magic --items 2
hookwright build --provider nitro --event orders/create
hookwright build --provider nitro --event category_view --collection summer-sale
hookwright build --provider nitro --event view          # no product needed
hookwright build --search "cold brew"      # only products matching a title
hookwright build --items 3 --discount 250 --send
hookwright send --dry-run                  # print the request + curl, send nothing
hookwright send --file ~/.local/state/hookwright/payloads/2026-…json
hookwright doctor --json
hookwright history
hookwright clear --payloads --yes        # or --logs / --shop-cache / --disconnect / --all

Exit codes: 0 ok · 1 configuration/usage · 2 pre-flight failed · 3 endpoint rejected the webhook.

What it checks before sending

A generated payload is useless if the receiver silently drops it, so every build runs pre-flight:

  • Schema gatetype must equal ABANDONED_CHECKOUT, or the consumer ignores the event without an error.
  • JSON round-trip — receivers that verify HMAC(secret, timestamp + JSON.stringify(parsedBody)) can only match if JSON.stringify(JSON.parse(raw)) === raw. If it doesn't, you're told the exact byte offset.
  • Phone — parsed to E.164 using the shipping country_code, the same way the consumer does.
  • Phone allow-list — refuses to build for a number that isn't an approved test handset, so a live shopper can never be messaged by a test run.
  • Product images — every image_url is HEAD-checked. A product with no image warns rather than blocking: the message simply renders without one.
  • Endpoint exists — before a signed payload is handed over, the destination is verified: the hostname must resolve, something must accept a TCP connection on the port, and the path must not 404. A typo'd host is caught here instead of looking like a silent delivery failure. The probe is OPTIONS, falling back to HEADnever a POST and never a body, so it cannot create data on the receiving system. Timeouts and servers that reject OPTIONS warn rather than block. Skip with --no-endpoint-check, override with --force.
  • Field coverage — a table of every field the consumer reads, what it maps to, and its value. Missing required fields are flagged in red.

Sending is also blocked when the target looks like production, unless --force.

Safety

  • The config file is 0600 and secrets are redacted from the log file.
  • Dry-run prints a curl that reproduces the request exactly, using --data-binary so the signed bytes are preserved.
  • Payload history lives in ~/.local/state/hookwright/payloads/ and can be re-sent (re-signed with a fresh timestamp).

Override both locations with HOOKWRIGHT_HOME, or point at one config with --config <path> / HOOKWRIGHT_CONFIG.

Clear removes local state — saved payloads, the activity log, the cached store record, the Shopify credentials, or everything back to first-run state. It shows exactly what will be deleted and asks before doing it.

Providers

| provider | signature | headers | events | |---|---|---|---| | cashfree-occ | base64 HMAC-SHA256 over timestamp + body | x-webhook-signature, x-webhook-timestamp | abandoned checkout | | razorpay-magic | hex HMAC-SHA256 over the body | x-razorpay-signature | abandoned checkout | | nitro | static bearer token, no body signature | authorization | 8 — page view, category view, product view, add to cart, remove from cart, checkout, order created, order updated |

Each provider owns its gate, signature scheme, field map, payload shape and summary, so nothing is assumed across them:

  • Cashfree OCC nests everything under data; currency is read from line_items[0], not the root.
  • Razorpay Magic is root-level, uses capitalised shipping keys (Shipping_address.Address1) and sends line_items_total as a string in paise — matching Razorpay's abandoned cart webhook docs. Its gate is "non-empty abandoned_checkout_url" rather than an exact value.
  • Nitro keys off eventName and requires eventVal.customer.phone on every event, parsed against root country. Choose the event with --event, or from the menu in the UI.

Nitro's eight events are about different things, so each asks for a different subject:

| event | you pick | cart | |---|---|---| | view | nothing | — | | category_view | one collection (fetched from Shopify, custom and smart) | — | | product_view | one product | — | | addtocart · removefromcart · checkout · orders/create · orders/updated | products and quantities | ✅ |

A page view has no cart, so it is never asked for a discount; a category view emits the collection's real id, title and URL and carries no line items.

Adding one is a single file in src/providers/ exporting buildPayload, sign, webhookUrl, summarize, a gate and a fieldMap — see docs/cashfree-occ.md.

Development

npm install
npm run dev     # esbuild watch
npm test        # 190 tests, no network required
npm start       # build + run

The test suite includes an end-to-end run against a stub Shopify API and a stub consumer that verifies the signature exactly as the real one does, asserting the transmitted bytes are identical to the signed bytes.

License

MIT