<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>CommerceHook changelog</title>
  <subtitle>Everything shipped, newest first.</subtitle>
  <id>https://commercehook.app/changelog/</id>
  <link href="https://commercehook.app/changelog/"/>
  <link rel="self" href="https://commercehook.app/changelog.xml"/>
  <updated>2026-09-17T00:00:00.000Z</updated>
  <entry>
    <title>Sign-in links survive corporate email scanners</title>
    <id>https://commercehook.app/changelog/#2026-09-17-link-scanners</id>
    <link href="https://commercehook.app/changelog/#2026-09-17-link-scanners"/>
    <updated>2026-09-17T00:00:00.000Z</updated>
    <category term="auth"/>
    <category term="fix"/>
    <summary>Fixed in Sign-in: If your email goes through Microsoft Defender, Mimecast, Proofpoint or
anything like them, your sign-in link was probably dead before you
clicked it. Those systems open every link in a message to check it is
safe. Ours signed you in the moment it was opened, so the scanner used
your link and you arrived to be told it had expired, seconds after
asking for it.

It was worse than it sounds. Even the lightest possible check, one that
asks for a page&apos;s headers without loading it, was enough to spend the
link.

Sign-in links now open a page with a button on it. Opening the page does
nothing; pressing the button signs you in. A scanner will follow a link,
but it will not press a button, so the link is still yours when you get
to it. Team invites work the same way, and for the same reason: a scanner
was able to accept an invitation on someone&apos;s behalf.

That is one extra click, and we would rather not have added it. The
alternatives do not survive contact with how these scanners actually
work. Letting a link work a few times does not help when the scan happens
at delivery and you read the email an hour later. Hiding the token from
the server does not help either, because the scan runs a real browser and
will run the page&apos;s code. A person pressing a button is the part no
scanner imitates.

Passkeys skip all of this, since nothing is emailed. Settings, then
Passkeys, once you are in.</summary>
  </entry>
  <entry>
    <title>Connecting an MCP client works now</title>
    <id>https://commercehook.app/changelog/#2026-09-17-mcp-consent-sign-in</id>
    <link href="https://commercehook.app/changelog/#2026-09-17-mcp-consent-sign-in"/>
    <updated>2026-09-17T00:00:00.000Z</updated>
    <category term="mcp"/>
    <category term="fix"/>
    <summary>Fixed in MCP: Connecting CommerceHook to Claude, Cursor or any other MCP client failed
at the point where you approve access. You were sent to sign in, signed
in fine, and landed back on a page asking you to sign in again. Try a few
times and the emails stopped arriving too, because sign-in links are
capped at five in fifteen minutes.

The cause was our session cookie. It was set to `SameSite=Strict`, which
tells your browser never to send it when you arrive from another site.
Approving an app means arriving from another site, every single time, so
the consent page never saw that you were already signed in. Nothing else
in the product asks you to come back from somewhere else, which is why
this sat unnoticed.

The cookie is now `SameSite=Lax`. That changes exactly one thing: your
browser sends it when you follow a link to us. It still refuses to send
it on anything that changes your data from another site, which is the
part that was ever doing the work.

We would rather that protection did not rest on a cookie setting at all,
so now it does not. A request that changes something and relies on your
session is checked against where it came from and refused if that is not
us. API keys are unaffected, because a key is only ever sent on purpose.

If you hit the sign-in limit while trying, it clears after fifteen
minutes. A passkey skips the wait.</summary>
  </entry>
  <entry>
    <title>The dashboard fits on a phone again</title>
    <id>https://commercehook.app/changelog/#2026-09-17-mobile-layout</id>
    <link href="https://commercehook.app/changelog/#2026-09-17-mobile-layout"/>
    <updated>2026-09-17T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="fix"/>
    <summary>Fixed in Dashboard: Every page in the dashboard scrolled sideways on a phone. The header was
the cause. Brand, four nav links, the theme toggle, your plan badge and
your email address were all held on one row that was never allowed to
wrap, which came to 506 pixels of content in a 375 pixel screen. Help ran
off the right edge, the account controls were off-screen entirely, and
the whole page drifted sideways as you scrolled it.

The header now wraps on narrow screens. Brand and account controls stay
on the top row, the nav sits on its own row underneath. Wide screens are
unchanged.

Signing out was impossible on a phone, which is the part worth
apologising for. Sign out lives in the menu behind your email address,
and that whole menu was hidden on small screens, so there was no way to
end a session without finding a laptop. The menu is there now, behind
your initial rather than the full address.

Registering webhooks was in the same state, and worse: that panel&apos;s
header came to 741 pixels, so the close control sat off the edge of the
screen. You could open the panel and then not shut it. It wraps now, the
platform tabs scroll sideways on their own, and the close button sits
next to the title where you can always reach it.

Content also stopped hiding in the middle of the screen. Cards were inset
by the page margin and drew a rounded border inside that, so text began
33 pixels in from each edge and roughly a fifth of a phone screen went on
framing. On narrow screens cards now run edge to edge and keep only the
horizontal rules between sections.

Two smaller things while we were in there. Opening an event gave you a
payload panel and a metadata panel that each scrolled inside a page that
also scrolled, so a thumb swipe often moved the wrong one; those separate
scrolling areas are now confined to wide screens, where the two sit side
by side. And the seam between Headers and Metadata was drawing two
one-pixel rules on top of each other, which looked like a double line
because it was one.</summary>
  </entry>
  <entry>
    <title>Your agent now knows which tools can destroy something</title>
    <id>https://commercehook.app/changelog/#2026-08-28-mcp-tool-annotations</id>
    <link href="https://commercehook.app/changelog/#2026-08-28-mcp-tool-annotations"/>
    <updated>2026-08-28T00:00:00.000Z</updated>
    <category term="mcp"/>
    <category term="improvement"/>
    <summary>Improved in MCP: The MCP server&apos;s tools now say what they do before they do it, using the
standard hints an MCP client can read: whether a tool only reads, whether
it can destroy something, and whether it reaches outside CommerceHook.

Two matter in practice. `delete_endpoint` is marked destructive, so
clients that pause before destructive calls will now pause before that
one, rather than relying on the description asking nicely. And
`replay_event` is marked open-world, because it fires a request at a URL
you supply and whatever your handler does next is outside our reach.

Every tool also carries a proper title now, so they read as names rather
than function calls in whichever client you use.</summary>
  </entry>
  <entry>
    <title>Stripe webhooks, next to the order they paid for</title>
    <id>https://commercehook.app/changelog/#2026-08-27-stripe</id>
    <link href="https://commercehook.app/changelog/#2026-08-27-stripe"/>
    <updated>2026-08-27T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="feature"/>
    <summary>New in Dashboard: Stripe is now a first-class platform alongside BigCommerce, WooCommerce, Square and Shopify. Paste a Stripe API key in the register panel, tick events across payments, checkout, subscriptions, invoices, refunds and disputes, and we create the webhook endpoint for you. Stripe hands back the signing secret at creation, so every delivery shows a verified tick or a clear cross from the very first one, secret rolls included.

Stripe&apos;s own tooling is the best of any platform we support, so this sits alongside it rather than against it. What it adds is the part Workbench cannot see: your storefront&apos;s `orders/create` and Stripe&apos;s `payment_intent.succeeded` for the same basket, interleaved in one timeline, kept for 90 days on Pro where the Dashboard&apos;s resend reaches 15 days and the CLI&apos;s 30.

Two smaller things worth knowing. Stripe is the first platform where a key can be cut down to exactly this job, so the panel steers you to a restricted key with write access to Webhook Endpoints rather than quietly taking your secret key. And every delivery says whether it is test or live money, so deep links land in the matching Dashboard instead of a live page insisting the payment does not exist.

Already registered in the Stripe Dashboard by hand? Paste that endpoint&apos;s signing secret and verification works identically.</summary>
  </entry>
  <entry>
    <title>The Stripe restricted-key instructions were wrong</title>
    <id>https://commercehook.app/changelog/#2026-08-27-stripe-key-permissions</id>
    <link href="https://commercehook.app/changelog/#2026-08-27-stripe-key-permissions"/>
    <updated>2026-08-27T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="fix"/>
    <summary>Fixed in Dashboard: If you set up Stripe today and followed our instructions to the letter,
registration refused your key. Sorry. We told you to create a restricted
key with **Webhook Endpoints: write** and **Account: read**, and there is
no &quot;Account&quot; row on Stripe&apos;s key-creation page to tick.

The permission you need is real, it is just filed somewhere unexpected:
**Connect → Accounts: read**. That name reads as though it concerns other
people&apos;s accounts under a platform, which is why we missed it, but it is
what lets a key read the account it belongs to, and it is the only row on
that page that grants it. Tick it even if you have never touched Connect.

So the minimum key is those two rows and nothing else. The docs now say
so, the register panel says so, and the error you get when the key is
missing that permission now names the row you are looking for instead of
sending you hunting for one that does not exist.</summary>
  </entry>
  <entry>
    <title>Adobe I/O&apos;s webhook check now passes</title>
    <id>https://commercehook.app/changelog/#2026-08-26-adobe-challenge</id>
    <link href="https://commercehook.app/changelog/#2026-08-26-adobe-challenge"/>
    <updated>2026-08-26T00:00:00.000Z</updated>
    <category term="ingestion"/>
    <category term="feature"/>
    <summary>New in Ingestion: Adobe I/O Events validates a webhook URL before it will register one: a GET carrying a `challenge` parameter that expects the value echoed straight back. Endpoints now answer that probe, so registering a CommerceHook URL with Adobe I/O (the delivery route for Adobe Commerce) passes validation first time instead of falling back to Adobe&apos;s manual dance.

The probe is answered, not stored: it is Adobe checking the URL, not a delivery, so it never shows up in your timeline. Every other GET captures exactly as before.</summary>
  </entry>
  <entry>
    <title>CLI 0.4.0: listen goes live</title>
    <id>https://commercehook.app/changelog/#2026-08-26-cli-0-4-0</id>
    <link href="https://commercehook.app/changelog/#2026-08-26-cli-0-4-0"/>
    <updated>2026-08-26T00:00:00.000Z</updated>
    <category term="cli"/>
    <category term="improvement"/>
    <summary>Improved in CLI: `commercehook listen` and `events --tail` now ride the same live stream the dashboard uses, so a delivery forwards to your handler the moment it arrives rather than on the next poll. The old polling loop stays underneath as the safety net: if the socket drops, the CLI says so on stderr and polls every few seconds until the stream returns, and the at-least-once forwarding contract is unchanged either way.

Needs a Node with a built-in WebSocket (22 or newer); older Nodes keep polling exactly as before. Update with `commercehook upgrade`, whichever way you installed.</summary>
  </entry>
  <entry>
    <title>Compare two deliveries side by side</title>
    <id>https://commercehook.app/changelog/#2026-08-26-compare-deliveries</id>
    <link href="https://commercehook.app/changelog/#2026-08-26-compare-deliveries"/>
    <updated>2026-08-26T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="feature"/>
    <summary>New in Dashboard: Open an event and the inspector now offers a compare view: pick any other delivery on the endpoint and read a line-by-line diff of the headers and the payload, older on the left. The usual question, &quot;what changed since the previous delivery of this type&quot;, has a one-click shortcut; every other row in the timeline grows a ⇄ for the rest.

Payloads are compared as JSON, so a platform reordering its keys reads as no change, while a retry that re-signs the delivery shows precisely which headers moved. Long unchanged stretches fold away behind a count, and the comparison lives in the URL, so a diff worth staring at is one you can send to a colleague. If the older delivery has already left its retention window, the view says so plainly rather than showing half a diff.</summary>
  </entry>
  <entry>
    <title>Copy a delivery as code, export an endpoint as a file</title>
    <id>https://commercehook.app/changelog/#2026-08-26-export-and-copy-as-code</id>
    <link href="https://commercehook.app/changelog/#2026-08-26-export-and-copy-as-code"/>
    <updated>2026-08-26T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="feature"/>
    <summary>New in Dashboard: Every event in the inspector now has a Copy as code section: curl, fetch, and Python snippets that reproduce the delivery against your own handler. Method, platform headers, and the body byte for byte, which matters because a signature only verifies against the exact bytes. Replay sends the delivery for you; these hand it to you, for a test suite or a bug report.

Endpoints export too. Under the event list you can download the whole timeline, or just what the current search matches, as JSON or as a HAR capture that opens straight in your browser&apos;s devtools. The same export is on the REST API at `GET /api/endpoints/{id}/export` (streamed, so a full 90 days of Pro retention downloads without a timeout) and as the `export_events` MCP tool, so an agent can pull an endpoint&apos;s history in one call.</summary>
  </entry>
  <entry>
    <title>Adobe I/O deliveries badge as Magento</title>
    <id>https://commercehook.app/changelog/#2026-08-26-magento-detection</id>
    <link href="https://commercehook.app/changelog/#2026-08-26-magento-detection"/>
    <updated>2026-08-26T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="feature"/>
    <summary>New in Dashboard: Deliveries arriving through Adobe I/O Events, the delivery route for Adobe Commerce and Magento, are now recognised: the timeline badges them as Magento and shows the event code (`com.adobe.commerce.observer.sales_order_save_after` and friends) as the event type, the way BigCommerce scopes and Shopify topics already appear.

Detection keys on the signature headers Adobe documents, so it works whichever event provider fires. Full Magento registration support is still on the way; this covers anyone already pointing Adobe I/O at an endpoint.</summary>
  </entry>
  <entry>
    <title>Events appear the moment they arrive</title>
    <id>https://commercehook.app/changelog/#2026-08-26-realtime-timeline</id>
    <link href="https://commercehook.app/changelog/#2026-08-26-realtime-timeline"/>
    <updated>2026-08-26T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="feature"/>
    <summary>New in Dashboard: The timeline used to check for new events every five seconds. It now
holds a live connection, and a delivery shows up the instant it lands,
whether the tab is focused or not. The dot in the header tells you
which is happening: solid means live, softer means the connection is
re-establishing and the five-second check is covering the gap, so
nothing is ever missed either way.

Under the hood each endpoint gets its own switchboard that sleeps
between deliveries, and a connection re-proves its access every hour.
Nothing about your endpoints or webhook URLs changes.</summary>
  </entry>
  <entry>
    <title>Register Shopify webhooks from the dashboard</title>
    <id>https://commercehook.app/changelog/#2026-08-26-shopify-registration</id>
    <link href="https://commercehook.app/changelog/#2026-08-26-shopify-registration"/>
    <updated>2026-08-26T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="feature"/>
    <summary>New in Dashboard: Shopify joins BigCommerce, WooCommerce and Square as a first-class
platform. Hand over your custom app&apos;s details, tick the topics you care
about, and CommerceHook creates one subscription per topic over the
GraphQL Admin API. Save the app&apos;s API secret key with the store and
every delivery is checked against Shopify&apos;s signature, so a payload that
does not match shows a clear cross instead of slipping through.

The timing matters more on Shopify than anywhere else: a failed delivery
is retried eight times over four hours and then the subscription is
silently deleted. An endpoint that always answers in milliseconds never
gives it the reason, and everything that arrives is kept, linked
straight to the order, product or customer in your admin.</summary>
  </entry>
  <entry>
    <title>CLI 0.3.0: the signature verdict reaches your terminal</title>
    <id>https://commercehook.app/changelog/#2026-08-12-cli-0-3-0</id>
    <link href="https://commercehook.app/changelog/#2026-08-12-cli-0-3-0"/>
    <updated>2026-08-12T00:00:00.000Z</updated>
    <category term="cli"/>
    <category term="feature"/>
    <summary>New in CLI: `commercehook event &lt;id&gt;` now prints the same signature verdict the
dashboard shows: a tick when the delivery&apos;s body verifies against the
secret registered through CommerceHook, MISMATCH when it does not, and
nothing when there is nothing to check. Update with `commercehook
upgrade`, whichever way you installed.</summary>
  </entry>
  <entry>
    <title>Connected apps stop hiding between refreshes</title>
    <id>https://commercehook.app/changelog/#2026-08-12-connected-apps-listing</id>
    <link href="https://commercehook.app/changelog/#2026-08-12-connected-apps-listing"/>
    <updated>2026-08-12T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="fix"/>
    <summary>Fixed in Dashboard: The Connected apps section in settings only believed an app was
connected while it held an unexpired access token. An idle connector,
one whose token had lapsed and had not refreshed yet, disappeared from
the list, so settings could claim &quot;nothing is connected&quot; about a Claude
connector you had used that same day. Connections are now recognised by
the grant itself, so the list matches reality: connected until you
revoke it.</summary>
  </entry>
  <entry>
    <title>The dashboard follows your colour scheme too</title>
    <id>https://commercehook.app/changelog/#2026-08-12-dashboard-light-mode</id>
    <link href="https://commercehook.app/changelog/#2026-08-12-dashboard-light-mode"/>
    <updated>2026-08-12T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="feature"/>
    <summary>New in Dashboard: Light mode reached the dashboard, a day behind the site. It follows
your browser&apos;s preference, and the sun-and-moon button in the header
overrides it; the choice is remembered per device, separately from the
site&apos;s. Everything the inspector shows, from the JSON tree&apos;s syntax
tints to the platform badges, holds the same contrast bar in both
schemes. If you never leave dark mode, nothing has changed.</summary>
  </entry>
  <entry>
    <title>The site follows your colour scheme</title>
    <id>https://commercehook.app/changelog/#2026-08-12-light-mode</id>
    <link href="https://commercehook.app/changelog/#2026-08-12-light-mode"/>
    <updated>2026-08-12T00:00:00.000Z</updated>
    <category term="site"/>
    <category term="feature"/>
    <summary>New in Site: commercehook.app now has a light mode. It follows your browser&apos;s
preference by default, and the sun-and-moon button in the header
overrides it whenever you disagree; the choice is remembered on your
device. The dashboard is still dark only, and gets the same treatment
next.

Both schemes are held to the same accessibility bar: our CI audits
every page in each one, so neither can silently regress.</summary>
  </entry>
  <entry>
    <title>Register Square webhooks from the dashboard</title>
    <id>https://commercehook.app/changelog/#2026-08-12-square-registration</id>
    <link href="https://commercehook.app/changelog/#2026-08-12-square-registration"/>
    <updated>2026-08-12T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="feature"/>
    <summary>New in Dashboard: Square is now a first-class platform next to BigCommerce and
WooCommerce. Save a Square access token, pick the events you care about
and whether you are on sandbox or production, and CommerceHook creates
the subscription for you and holds on to its signing key. Every
delivery that lands is labelled by event type and checked against that
key, so a payload whose signature does not match shows a clear cross
instead of slipping through unnoticed.

Square is a little different from the others: it sends events from all
of your connected sellers to a single URL, and keeps sandbox and
production apart. The timeline tells you which merchant each delivery
came from and which environment it was, and where Square has a page for
the record behind an event, the inspector links you straight to it.</summary>
  </entry>
  <entry>
    <title>Replay and registration history, with names attached</title>
    <id>https://commercehook.app/changelog/#2026-08-11-activity-history</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-activity-history"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="feature"/>
    <summary>New in Dashboard: Replaying an event and registering webhooks both did their job and then
forgot it happened. On a team that forgetting has a cost: nobody could
say whether the staging handler was already retried, or which scopes a
colleague pointed at an endpoint last Tuesday.

Both actions are now remembered. The replay panel lists past attempts
under the form: destination, status (or &quot;unreachable&quot; when the handler
never answered), how long it took, when, and who ran it. The register
panel does the same for previous &quot;do it for me&quot; runs, per scope, with
the hook id BigCommerce assigned or the error it answered with. Commands
you copy and run in your own terminal never touch us, so they do not
appear.

Attribution survives account changes the way team activity already does:
an email address follows its owner, and a deleted account shows as &quot;a
former member&quot; rather than pretending nobody did it. Replay history also
rides on `GET /api/events/{id}`, so the REST API and the MCP `get_event`
tool return it too.

History lives with what it describes: replay attempts are kept as long
as the event they replayed, registration records as long as the
endpoint.</summary>
  </entry>
  <entry>
    <title>This changelog</title>
    <id>https://commercehook.app/changelog/#2026-08-11-changelog</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-changelog"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="site"/>
    <category term="feature"/>
    <summary>New in Site: Everything that ships now lands here, filterable by the part of the
product it touches. Versions are dated rather than numbered, because
CommerceHook is deployed continuously and the only thing a version can
honestly tell you is when. The running version sits in the dashboard
footer, so you never have to guess whether the fix you just read about is
live yet. There is an [Atom feed](/changelog.xml) if you would rather not
check.</summary>
  </entry>
  <entry>
    <title>A CLI, on npm</title>
    <id>https://commercehook.app/changelog/#2026-08-11-cli</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-cli"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="cli"/>
    <category term="feature"/>
    <summary>New in CLI: CommerceHook now has a command line: `npx commercehook` (or install it
globally) gives you `commercehook version` today, with sign-in, `tail`,
`inspect` and `replay` on the way. It is 7 kB with zero dependencies,
every command takes `--json` for scripting, and it checks once a day
whether a newer release exists and says so without getting in your way.

The CLI has its own version numbers (0.1.0 as of this entry), because
installed software and a deployed service age differently.</summary>
  </entry>
  <entry>
    <title>Install the CLI without Node</title>
    <id>https://commercehook.app/changelog/#2026-08-11-cli-binaries</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-cli-binaries"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="cli"/>
    <category term="improvement"/>
    <summary>Improved in CLI: Prefer not to have Node in the loop? Each CLI release now ships
standalone binaries for macOS, Linux and Windows, and
`brew install commercehook/tap/commercehook` works. Run
`commercehook upgrade` to see how to update whichever way you
installed.</summary>
  </entry>
  <entry>
    <title>The CLI learns endpoints, events, tail, inspect and replay</title>
    <id>https://commercehook.app/changelog/#2026-08-11-cli-commands</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-cli-commands"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="cli"/>
    <category term="feature"/>
    <summary>New in CLI: The CLI now does the actual work: `endpoints` lists (and creates,
renames, deletes, with a confirmation that names what it is about to
remove), `events &lt;endpoint&gt;` shows deliveries with search and
pagination, `event &lt;id&gt;` is the inspector in your terminal, and
`replay &lt;id&gt; --to &lt;url&gt;` re-sends a delivery at your handler.

The one to try is `events &lt;endpoint&gt; --tail`: one line per delivery as
it arrives, updating live while you work on the handler next to it.
With `--json`, every command emits the API response as-is, and tail
becomes a stream of JSON lines, ready for jq.

Endpoints are referenced by name prefix as well as id, and an ambiguous
prefix is an error listing the candidates, never a guess.</summary>
  </entry>
  <entry>
    <title>commercehook listen, webhooks on your laptop</title>
    <id>https://commercehook.app/changelog/#2026-08-11-cli-listen</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-cli-listen"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="cli"/>
    <category term="feature"/>
    <summary>New in CLI: `commercehook listen &lt;endpoint&gt; --forward-to http://localhost:3000/webhooks`
streams every delivery into the handler on your machine: original
method, platform headers and raw body, with the event id in
`x-commercehook-replay` and `x-commercehook-forwarded: 1` so a handler
can tell live traffic from a manual replay. Register the CommerceHook
URL with your platform once and develop locally ever after. It fills
the ngrok role in the webhook loop, except every delivery is also
captured and inspectable afterwards, which a tunnel never gave anybody.

A non-2xx from your handler is reported and the stream continues,
because your handler erroring mid-edit is the loop doing its job.
`--since 15m` and `--replay-last 5` start with history; Ctrl-C prints
what was forwarded and where to resume. Delivery is at-least-once with
a lag of a few seconds (it polls, as the dashboard does), so
deduplicate by event id if your handler cares.</summary>
  </entry>
  <entry>
    <title>Sign in to the CLI with your browser</title>
    <id>https://commercehook.app/changelog/#2026-08-11-cli-login</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-cli-login"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="cli"/>
    <category term="feature"/>
    <summary>New in CLI: `commercehook login` (CLI 0.2.0) opens your browser on the same consent
screen any connected app gets; approve it and the terminal is signed
in. No password typed into a prompt, no token pasted from a settings
page. The CLI then appears under Connected apps in settings, where it
can be disconnected like anything else. `commercehook logout` revokes
that grant properly rather than just forgetting it locally, and
`commercehook whoami` answers the question you ask right before doing
anything important.

Sign-ins refresh themselves silently for 30 days of use; after that the
CLI says plainly to log in again. For CI and machines with no browser,
`login --with-api-key` stores an API key instead, or set `CH_API_KEY`
and store nothing.</summary>
  </entry>
  <entry>
    <title>The CLI has a home: /cli and /docs/cli</title>
    <id>https://commercehook.app/changelog/#2026-08-11-cli-pages</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-cli-pages"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="site"/>
    <category term="feature"/>
    <summary>New in Site: The CLI now has a product page at [/cli](/cli/) and a full reference at
[/docs/cli](/docs/cli/): install, browser sign-in, every command with an
example, and a plainly-stated section on `listen` semantics
(at-least-once, poll-bounded lag, dedupe by event id). The docs page is
the canonical reference: `commercehook --help` points at it, so what
you read there is current even when the binary on your machine is not.
`llms.txt` and the authentication guide now describe the CLI too.</summary>
  </entry>
  <entry>
    <title>Fold away the parts of an event you are not reading</title>
    <id>https://commercehook.app/changelog/#2026-08-11-collapsible-sections</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-collapsible-sections"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="improvement"/>
    <summary>Improved in Dashboard: Every section of the inspector (payload, query params, headers, metadata,
replay) collapses when you click its heading, and stays that way as you
click through the timeline. Comparing headers across ten deliveries no
longer means scrolling past ten payloads.</summary>
  </entry>
  <entry>
    <title>A way to reach us that is not a mailto link</title>
    <id>https://commercehook.app/changelog/#2026-08-11-contact-form</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-contact-form"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="site"/>
    <category term="feature"/>
    <summary>New in Site: There is a form at [/contact](/contact/) for bugs, questions and
&quot;why does it not do X&quot;. Signed-in users get the same thing on a Help
page in the dashboard, which attaches your plan, endpoint count and the
version you are running, so a reply does not have to start by asking.

Messages are written down before they are emailed, so nothing is lost if
our mail provider is having a bad afternoon, and the address you give is
used to reply and nothing else. No marketing, ever.</summary>
  </entry>
  <entry>
    <title>Deleting your account disconnects your apps</title>
    <id>https://commercehook.app/changelog/#2026-08-11-delete-disconnects-apps</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-delete-disconnects-apps"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="auth"/>
    <category term="fix"/>
    <summary>Fixed in Sign-in: Connected apps (a Claude connector, for instance) kept working until
their tokens expired after an account was deleted. Deletion now revokes
every grant in the same request, so a connected app stops on its next
call, and an hourly sweep clears anything orphaned. Deleted means
deleted, including the credentials you had forgotten about.</summary>
  </entry>
  <entry>
    <title>An empty body no longer reads as broken JSON</title>
    <id>https://commercehook.app/changelog/#2026-08-11-empty-body</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-empty-body"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="fix"/>
    <summary>Fixed in Dashboard: A delivery with no body was labelled &quot;not valid JSON&quot;, which sent people
hunting a parse error that did not exist. It now says &quot;no body&quot;.</summary>
  </entry>
  <entry>
    <title>The health check says which version is deployed</title>
    <id>https://commercehook.app/changelog/#2026-08-11-health-version</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-health-version"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="api"/>
    <category term="improvement"/>
    <summary>Improved in REST API: `GET /api/health` now includes a `version` field carrying the deployed
release, so a monitor, a script or the forthcoming CLI can tell what it
is talking to without authenticating. Nothing else about the response
changed.</summary>
  </entry>
  <entry>
    <title>Headers and query params, readable and copyable</title>
    <id>https://commercehook.app/changelog/#2026-08-11-inspector-headers</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-inspector-headers"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="improvement"/>
    <summary>Improved in Dashboard: Headers used to sit at the bottom of the narrow right-hand column, which
on a laptop-height window meant scrolling past the metadata, the
BigCommerce panel and replay to reach a squeezed box where
`application/json` wrapped in the middle. They have moved under the
payload, in the wide column, where they belong: they are what the
platform sent, the same as the body.

Every value is now one click to select and one click to copy, with a
copy-the-lot button per section that gives you JSON. Query params get the
same treatment.</summary>
  </entry>
  <entry>
    <title>OAuth tokens now work on the whole REST API</title>
    <id>https://commercehook.app/changelog/#2026-08-11-oauth-rest</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-oauth-rest"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="api"/>
    <category term="feature"/>
    <summary>New in REST API: An OAuth access token used to work only on the MCP endpoint; the rest
of the API wanted an API key or a browser session. Now any endpoint
that takes `Authorization: Bearer chk_...` takes an OAuth token too,
with the same 120-requests-a-minute budget, counted per grant.

This makes a connected app and an API key the same kind of thing: both
can drive the full API, both are visible in your settings, and
disconnecting an app under Connected apps cuts off its API access on
the next call. API keys and their behaviour are completely unchanged.</summary>
  </entry>
  <entry>
    <title>The OpenAPI spec now documents OAuth tokens</title>
    <id>https://commercehook.app/changelog/#2026-08-11-openapi-oauth</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-openapi-oauth"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="api"/>
    <category term="fix"/>
    <summary>Fixed in REST API: OAuth access tokens from connected apps have worked on every API route
since connected apps shipped, but the machine-readable spec at
[/openapi.json](/openapi.json) still claimed API keys were the only way
in. It now describes both credentials, so a client generated from the
spec asks for the right thing.

The spec&apos;s version is no longer a frozen `1.0.0` either. It carries the
same dated version as this changelog, stamped at build time, so it tells
you when the description you are reading was current.</summary>
  </entry>
  <entry>
    <title>Query params in the inspector</title>
    <id>https://commercehook.app/changelog/#2026-08-11-query-params</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-query-params"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="feature"/>
    <summary>New in Dashboard: Some platforms put meaning in the URL rather than the body: a shop
domain, a topic, a channel id, occasionally a signature. The inspector
showed every header and threw the query away, so a delivery could arrive
with half its information invisible.

Deliveries now record the query string exactly as sent, and the inspector
lists it above Headers. A repeated key such as `?id=1&amp;id=2` shows both
values, because that is what arrived. Events captured before this show no
params: we cannot invent what was never stored.</summary>
  </entry>
  <entry>
    <title>Events carry their query string</title>
    <id>https://commercehook.app/changelog/#2026-08-11-query-params-api</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-query-params-api"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="api"/>
    <category term="improvement"/>
    <summary>Improved in REST API: `GET /api/events/:id` now returns `query` (the raw string, encoding and
ordering intact) and `params` (the same thing as `[name, value]` pairs).
Pairs rather than an object, because a name may legally repeat and an
object would silently drop half of it. The MCP `get_event` tool inherits
both.</summary>
  </entry>
  <entry>
    <title>The sign-in email stops looking like a phish</title>
    <id>https://commercehook.app/changelog/#2026-08-11-sign-in-email</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-sign-in-email"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="auth"/>
    <category term="improvement"/>
    <summary>Improved in Sign-in: Corporate mail filters were quarantining sign-in links, and the shape of
the message was why: a short body that is mostly one large button,
arriving from a domain you have never corresponded with, is structurally
identical to credential phishing.

It now carries the things a phish does not. The address it was requested
for and roughly where from, so somebody else&apos;s attempt to sign in as you
is obvious. The destination in plain text, so you can read it before you
click it. A reply-to that reaches a person. And a footer that says who we
are and why you got it.

If your organisation still quarantines it, a passkey skips email
entirely.</summary>
  </entry>
  <entry>
    <title>The inspector verifies WooCommerce signatures</title>
    <id>https://commercehook.app/changelog/#2026-08-11-signature-verification</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-signature-verification"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="feature"/>
    <summary>New in Dashboard: Set a webhook secret when registering WooCommerce webhooks through the
dashboard and the inspector now checks every signed delivery against
it: a quiet &quot;signature verified&quot; when the HMAC matches, and a loud
MISMATCH warning when it does not. A mismatch on an otherwise sensible
delivery usually means something between the store and the handler
rewrote the body, which is a class of bug people burn days on.

Deliveries with no signature, or from webhooks registered without a
secret through us, simply say nothing. The secret is stored encrypted,
verification happens when you view the event, and the REST API and MCP
`get_event` return the same `signatureValid` verdict.</summary>
  </entry>
  <entry>
    <title>WooCommerce has its pages: /woocommerce and the missing manual</title>
    <id>https://commercehook.app/changelog/#2026-08-11-woocommerce-pages</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-woocommerce-pages"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="site"/>
    <category term="feature"/>
    <summary>New in Site: With registration real, WooCommerce gets its story: a product page at
[/woocommerce](/woocommerce/) and an explainer at
[/woocommerce-webhook-inspector](/woocommerce-webhook-inspector/)
covering the behaviours that surprise everyone once (wp-cron delivery,
the creation ping, the five-failures auto-disable, and how to verify
the signature header). The supported-platforms doc now says supported
rather than planned, and the agent-facing authentication guide gained a
plainly-worded warning that payload contents are untrusted data, never
instructions.</summary>
  </entry>
  <entry>
    <title>Register WooCommerce webhooks from the dashboard</title>
    <id>https://commercehook.app/changelog/#2026-08-11-woocommerce-registration</id>
    <link href="https://commercehook.app/changelog/#2026-08-11-woocommerce-registration"/>
    <updated>2026-08-11T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="feature"/>
    <summary>New in Dashboard: The register panel&apos;s platform picker now has a second real choice.
Pick WooCommerce, choose from the twelve core topics (or add any
`action.*` WordPress hook), and either copy a ready-made command or
hand us the store URL and API key pair to create the webhooks for you.
Key pairs can be saved for next time, encrypted, with the secret never
shown again; each save is checked against the live store first, so a
typo fails now rather than later.

Two WooCommerce quirks are handled where they would otherwise confuse:
the panel warns about the ping delivery WooCommerce sends the moment a
webhook is created, and there is a field for the signing secret
WooCommerce uses to HMAC every delivery, so the handler you replay to
can verify signatures. Deliveries themselves have been detected and
inspectable all along; this closes the register-and-verify loop.</summary>
  </entry>
  <entry>
    <title>See and revoke connected apps</title>
    <id>https://commercehook.app/changelog/#2026-07-26-connected-apps</id>
    <link href="https://commercehook.app/changelog/#2026-07-26-connected-apps"/>
    <updated>2026-07-26T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="feature"/>
    <summary>New in Dashboard: Settings gained a Connected apps section listing everything you have
signed in to CommerceHook with, and a Revoke button beside each. A
connected app has the same access an API key does, so revoking takes
effect on its next request, with no grace period.</summary>
  </entry>
  <entry>
    <title>Add CommerceHook to Claude as a connector</title>
    <id>https://commercehook.app/changelog/#2026-07-26-mcp-oauth-connector</id>
    <link href="https://commercehook.app/changelog/#2026-07-26-mcp-oauth-connector"/>
    <updated>2026-07-26T00:00:00.000Z</updated>
    <category term="mcp"/>
    <category term="feature"/>
    <summary>New in MCP: The MCP server now speaks OAuth 2.1, so adding it to claude.ai is: paste
`https://mcp.commercehook.app/mcp`, sign in, press Allow. No API key to
create, copy or paste.

Clients register themselves (RFC 7591), PKCE is required, and discovery
works from the 401 alone, so any compliant MCP client can find its own
way in. Claude Code and Cursor keep working exactly as before with an
`Authorization: Bearer chk_...` header, and that path is untouched.</summary>
  </entry>
  <entry>
    <title>A failed payment no longer cuts a team off</title>
    <id>https://commercehook.app/changelog/#2026-07-26-subscription-events</id>
    <link href="https://commercehook.app/changelog/#2026-07-26-subscription-events"/>
    <updated>2026-07-26T00:00:00.000Z</updated>
    <category term="billing"/>
    <category term="improvement"/>
    <summary>Improved in Billing: Every subscription state is now handled, and read from the subscription
itself rather than the name of the event that carried it. A payment being
retried keeps the team working (it stops the team growing until it is
settled, but nothing stops functioning), and cancelling schedules an
ending rather than performing one: you keep the period you paid for.</summary>
  </entry>
  <entry>
    <title>Team seats show who holds them</title>
    <id>https://commercehook.app/changelog/#2026-07-26-team-seats</id>
    <link href="https://commercehook.app/changelog/#2026-07-26-team-seats"/>
    <updated>2026-07-26T00:00:00.000Z</updated>
    <category term="billing"/>
    <category term="fix"/>
    <summary>Fixed in Billing: Polar&apos;s seat panel showed every seat unassigned however many people were
in a team, and a member who left kept theirs. Membership is now mirrored
into Polar: joining takes a seat, leaving releases one, and an hourly
sweep brings any team back into line without anybody having to open a
page.</summary>
  </entry>
  <entry>
    <title>Notifications when events arrive</title>
    <id>https://commercehook.app/changelog/#2026-07-25-endpoint-notifications</id>
    <link href="https://commercehook.app/changelog/#2026-07-25-endpoint-notifications"/>
    <updated>2026-07-25T00:00:00.000Z</updated>
    <category term="dashboard"/>
    <category term="feature"/>
    <summary>New in Dashboard: A bell beside Register on any endpoint. Switch it on and your browser
tells you when that endpoint receives something, even while you are in
another tab; clicking the notification opens the endpoint. Off by
default, remembered per endpoint, and capped at one alert a minute so a
busy store cannot bury you.</summary>
  </entry>
  <entry>
    <title>Sign in with a passkey</title>
    <id>https://commercehook.app/changelog/#2026-07-25-passkeys</id>
    <link href="https://commercehook.app/changelog/#2026-07-25-passkeys"/>
    <updated>2026-07-25T00:00:00.000Z</updated>
    <category term="auth"/>
    <category term="feature"/>
    <summary>New in Sign-in: Add a passkey in settings and sign in with Touch ID, Windows Hello or a
password manager, with no email round trip. Magic links still work and
remain the way in on a new device.</summary>
  </entry>
  <entry>
    <title>Shared BigCommerce credentials</title>
    <id>https://commercehook.app/changelog/#2026-07-25-team-credentials</id>
    <link href="https://commercehook.app/changelog/#2026-07-25-team-credentials"/>
    <updated>2026-07-25T00:00:00.000Z</updated>
    <category term="teams"/>
    <category term="feature"/>
    <summary>New in Teams: A team can hold store credentials once instead of everybody pasting their
own. Admins add and remove them; members can use them to register
webhooks and run store API commands, but can never reveal or export the
token itself.</summary>
  </entry>
  <entry>
    <title>Teams</title>
    <id>https://commercehook.app/changelog/#2026-07-25-teams</id>
    <link href="https://commercehook.app/changelog/#2026-07-25-teams"/>
    <updated>2026-07-25T00:00:00.000Z</updated>
    <category term="teams"/>
    <category term="feature"/>
    <summary>New in Teams: Endpoints, captured events and replay, shared with a whole team. Invite
by email with a role (owner, admin or member), and every endpoint records
who created it. Team endpoints keep their URL, so nothing has to be
re-registered with the platform.

Seat pricing is graduated: $6 each up to five seats, $4 from six to
fifteen, $3 beyond that, with a two-seat minimum. A seat includes Pro on
that person&apos;s own account.</summary>
  </entry>
</feed>
