Case study · WooCommerce migration

The hosting ended on 31 July. The store moved on 22 July.


Watermark Fixtures, a US home-fixtures retailer, ran its store on WooCommerce, and its hosting was ending on 31 July 2026. We moved the store inside a maintenance window in July 2026, and in the same month we found an automated card-testing operation running against it and stopped it. Below are the figures, each one with the date it was read.

We were responsible for the migration only, not for the store's design.

The long-form account of this project is on our blog: the full story.

Measured

Four facts from July 2026.

One
maintenance window for the whole cutover, 22 July 2026
0
orders lost — every order reconciled, same last order ID on both sides
1,494
published product URLs carried across byte-identical, checked by MD5 against the old host
Cleared
the card-testing tokens in the store's database
The starting state

What we found when we opened the site.

A migration runs on somebody else's clock. The previous hosting ended on 31 July 2026, and that set the date. What we found before it is what made the date hard.

Code that existed once

Three must-use plugins the store's operation depended on lived only on the server — no copy anywhere else, no history. Four WordPress drop-ins, the code that runs before WordPress itself loads, had zero copies outside the server until 6 August 2026. Three of the four were brought under version control that day; the fourth holds a third-party licence key and is deliberately excluded from it.

An attack already running

Through a page the bot protection in place did not reach, an automated card-testing operation had accumulated far more payment tokens against the store's payment account than the store has saved cards. A challenge rule was put on that page, and it was still firing when it was tested by hand on 10 August 2026.

22 July 2026

The move.

Rehearsed on a clone first, then executed inside a dual maintenance-mode window with both sides reconciled before the old host was released.

0

Orders lost

Orders were counted on both hosts, matching exactly, with the same last order ID on each side. No order was lost in the window.

1,494

Product URLs unchanged

The MD5 of every published product slug matched the old host. No redirects were needed for the move, and the 728 redirect rules the site already had were preserved.

9.30 GB

Footprint after the move

8.83 GB of files and a 473 MB database, read 22–23 July 2026. Plan usage measured in the host panel on 3 August 2026: 9.26 GB of a 10 GB quota.

92,000+

Failed jobs cleared

Background jobs that had been failing and accumulating since 2022. Order-confirmation email was repaired in the same pass, before it could fail silently after the cutover.

July 2026

The card testing, and the email that broke twice.

What was removed

The fraudulent payment tokens and their accumulated metadata rows, plus 12 attacker accounts, were removed in July 2026. A fixed mark was set on the payment-token table so the result could be checked rather than assumed: no new tokens were found above that mark when the table was read again on 6 August 2026.

Email, and why we removed a credential instead of re-entering it

On 24 July 2026 the site's security salts were regenerated during host-side maintenance. The mail plugin stored its API key encrypted against those salts, so outbound email stopped silently for about a day — the second outage from that same mechanism. We deactivated the plugin instead of re-keying it: the site was left sending through the host's own transactional mail, which holds no credential a salt change can invalidate. Inbox delivery was confirmed on 25 July 2026; on 10 August the mail configuration was read again and the plugin was still off.

10 August 2026

What a maintenance Monday looks like.

Seven steps, written down before any of them ran, each with the reading that would stop it. Six ran as written. One of them — a smoke-test script — was not applicable on that environment, and its hard criterion was met by a direct reading instead, recorded as such rather than ticked. Five of the seven are below.

01

Snapshot

A manual snapshot taken on the live environment and verified in the host's panel before anything was touched.

02

Updates

Yoast SEO 28.0 to 28.2 and the Turnstile plugin 1.41.1 to 1.42.1, each confirmed active with the home page answering 200.

03

Re-read

The bot-protection configuration read again and compared with the morning's baseline: 21 lines identical, exit status 0. The update changed none of its 18 settings.

04

Checkout

A live checkout exercised by hand. The challenge widget rendered and resolved, with no errors from the challenge provider.

05

Integrity

WordPress core verified against published checksums; 31 of 35 plugins verified and 4 skipped, each named, because no published checksums could be retrieved for those versions.

46 plugin entries inventoried 107 database tables recorded 14 sections in each dated site snapshot
Method

Three rules we did not bend.

A number carries its date

A reading without an environment and a date is not a baseline. Numbers in the register are never edited; a new row is added, with the value, the environment, the date and the source. At the close of one audit, twelve readings of the store's size were circulating in the project's documents — four order counts, four database figures, four disk figures. None of them was wrong. None of them was comparable, because almost none carried the moment and the environment it came from.

Every check gets a decoy

A green reading counts only when a red was provoked in the same run. The commit hook on this engagement's documentation repository was re-tested on 10 August with a planted decoy credential: the commit was rejected, exit status 1, and the value was shown only as REDACTED. A check that has never failed has never been tested.

Wrong documents are kept wrong

Four superseded documents sit in the repository with their false claims intact and labelled. Correcting a document after the fact erases how the belief was arrived at, and that is the question anyone asks when something goes wrong.

Limits

What this page does not claim.

Questions we left open

Several questions from the earlier compromise could not be settled from the site itself — the previous host's logs were gone before the move, and some answers sit with the payment processor rather than in the database. They were written down as open, in our own reporting on the engagement, and not dressed up as closed.

Four plugins, unverifiable

Four of the 35 plugins could not be checked for integrity: no published checksums could be retrieved for those versions. They were named in the report rather than rounded into "35 of 35".

Nothing here is the site today

Every figure above was measured in July or August 2026 and carries its date. The engagement ended on 31 August 2026; nothing on the site has been re-measured since 1 September 2026, and none of these readings should be taken as its current state.

Migrations · WooCommerce · Maintenance

Moving a store is a deadline you don't control.

If you have a date set by someone else — a host shutting down, a contract ending, a platform change — tell us the date first. We'll tell you what has to be measured before it.

Let's talk

[email protected]  ·  +504 9960 1911