How Shopcheck works,
step by step.
Each part of the app, shown the way it looks in your Shopify admin, so you can follow along without reading a manual.
How Shopcheck catches issues before they reach a real customer, and what it checks to do it — uptime, buyer journeys, Shopify data and performance.
Install the app, enable customer events, and set where its alerts go.
Shopcheck is not on the Shopify App Store yet. Request access with your store's .myshopify.com address and we'll send you an install link.
Set up a buyer journey monitor
Learn more about journeys
Set up a store data check
Learn more about store data
Every check in Shopcheck is a monitor. Which type to reach for, at a glance.
Choosing a type
| Type | What it checks | How it runs | Reach for it when |
|---|---|---|---|
| Uptime | A page responds with the status and content you expect | On a schedule, as often as every 15 minutes | You want a fast, frequent signal that a page is alive |
| Latency | A page answers within a time you set, or within twice its own recent average | On a schedule | A page being slow is the failure, not it being down |
| Buyer journey | A real browser walks a multi-step path through the storefront | In a real browser, hourly at most | The thing you care about is a sequence, not a page |
| Content check | Specific copy, prices or promo text are visible on a page | On a schedule | A campaign, price or banner going missing would cost sales |
| Store data | Rules over your Shopify Admin data rather than a page | Against the Shopify Admin API, on a schedule or when a product changes | Configuration drift matters more than page rendering |
| Apps in your theme | The app embeds and app blocks you tick are still switched on in your live theme | Reads your live theme, on a schedule or when a theme is published | A theme publish, app update or theme editor change could silently remove a widget |
| API endpoint | An address your storefront or an app calls returns the right status and content in time | On a schedule | A headless storefront or integration depends on an endpoint |
| Shopify status | The parts of Shopify you tick, as Shopify's own status page reports them | On a schedule, one per store | You want to know a storefront failure is Shopify-wide, not yours |
| AI agent readiness | AI search crawlers can read the store, and the agent guide, llms.txt and shopping profile all answer | On a schedule | You want ChatGPT, Claude and Perplexity to be able to find and shop the store |
| Heartbeat | That one of your jobs checked in on time | Waits for your job to ping it | A scheduled job failing silently would go unnoticed |
| Incoming alert | Your own system reporting a failure to Shopcheck | Waits for your system to call it | Your middleware already knows when it breaks and needs somewhere to say so |
| Custom check | A path, status, text, selector or blocked-text rule you assemble yourself | On a schedule; in a real browser if a step clicks or types | None of the above fits exactly |
The rule of thumb: uptime checks tell you a page is up, buyer journeys tell you the store works, and store data checks tell you the things the journey relies on are still configured. Most stores want a handful of frequent uptime checks, two or three buyer journeys, and a store data check on whatever those journeys add to cart.
Monitors
What each type is for, and its set-up played through step by step.
Pick a type. The screen plays through the set-up on its own; click a step to jump to it, or pause it any time.
- The homepage, your top collection and your best-selling product page, every 15 minutes.
- A landing page for a campaign that is live for a week and must not 404.
- A page behind an app redirect, where a broken app returns a 500 instead of the page.
Monitor type
What do you want to protect?
Monitors
A buyer journey is a multi-step chain run in a real headless browser: browse → product → add to cart → checkout. It is the check that answers the question uptime monitoring cannot — not "did the page respond" but "can somebody still buy something". A theme deploy can kill add-to-cart while the homepage still loads; a journey fails there, because it tried to add something to a cart and could not.
Setting one up is shown step by step in Getting started. The ready-made journeys are written against Shopify conventions that do not change between themes — routes, form actions, input names — so most work without editing. This section covers how steps and selectors behave, devices, visual checks, and what to do when a journey fails.
Step types
A journey is a sequence of steps, each of which either acts on the page or asserts something about it.
| Step | What it does |
|---|---|
| Navigate | Go to a URL |
| Click | Click the element matching a selector |
| Type | Enter text into a field |
| Wait | Wait for an element, or for a fixed duration |
| Assert | Require that something is true before continuing |
Steps run in order and the run stops at the first failure — which is the point. The failing step is the diagnosis, so a chain of small steps tells you more than one large one.
Writing selectors that survive
The selector is the fragile part of any journey, and how you write it decides how often you will be maintaining this monitor.
| Selector | Use it? | Why |
|---|---|---|
A data- attribute, a form name, an ARIA role or an accessible name | Prefer | Stable and semantic: it describes what the element is |
Layout, like div > div:nth-child(3) > button | Avoid | Describes where a button happens to sit today, not what it is |
| Class names generated by a build step | Avoid | They change when the theme is rebuilt, not when anything meaningful changed |
Backup selectors and suggested fixes
A step that clicks, types or waits for an element keeps an ordered list of selectors — the main one, then backups — under Technical selector. More details:
| Under Pending review on Home | When |
|---|---|
| Selector healed | The step passed on a backup selector |
| Selector broken | Nothing matched, and Shopcheck re-scanned the page for a replacement |
Either way the fix waits for you to approve it. It never applies a fix automatically. A wrong selector silently turns a monitor into a check of something else, and a monitor that quietly verifies the wrong button reports green while your cart is broken. A failing monitor gets fixed; a lying one does not. Dismissing a fix keeps the step as it is, and it is not suggested again until that selector changes.
Screenshots
A scheduled run keeps a screenshot of the step that failed. Choose Settings → Journeys → Screenshots to keep one of every step instead, or none. A check you run from the monitor editor always keeps one of every step, whatever that setting says.
Passing for the right reason
A journey that passes for the wrong reason is the failure mode to watch for. If your add-to-cart step would still pass on a page where the button no longer exists, the assertion is too loose. End each journey on an Assert step that checks the outcome — the cart count went up, the checkout page shows the right line item — not just that a click happened.
Checkout, and how far to go
You can walk into checkout. What you should not do is complete a real order on every run.
The practical shape most stores land on is: browse → product → add to cart → open checkout → assert the checkout page loaded with the right line item. That covers the entire path where breakage actually happens, and stops before it creates data you then have to clean up.
Devices
Any page-based monitor — not only a journey — can run against desktop, tablet and mobile. Each device is a separate check row with its own history, so a problem that only affects one of them shows up as exactly that — you do not need to duplicate the monitor per device.
This matters more than it sounds. Most storefront breakage is not universal: it is the mobile drawer cart, or the tablet breakpoint nobody tests, or a sticky header that eats the add-to-cart button below a certain width.
The three screen sizes are set once for the whole store under Settings → Journeys:
| Device | Screen size |
|---|---|
| Desktop | 1920 × 1080 |
| Tablet | 768 × 1024 |
| Mobile | 414 × 896 |
Practical notes:
- Store data, heartbeat and incoming-alert monitors have no page to render, so they have no device setting.
- Each enabled device is its own run. A journey on three devices at hourly frequency is three runs an hour — worth knowing when you set the interval.
- History is per device, so you can see that mobile has been failing for a week while desktop has been fine throughout.
Visual checks
More details:
- Baselines are kept per monitor, per step and per device, so a mobile layout is compared with mobile.
- A changed screenshot does not fail the check or open an incident. It goes to Pending review on Home as Visual drift (or Baseline pending for a first capture), so a cosmetic change never pages anyone.
- Start the Diff threshold loose, then tighten it once you have seen what a normal week of drift looks like for that page. Countdown timers, rotating banners, "N people are viewing this" and live stock counts all belong under Ignore selectors.
When a journey starts failing
Other things to check:
- Check whether it is one device or all of them. One device points at a breakpoint or a device-specific theme block; all of them points at the page or an app.
- Run test and watch the recording if the screenshot does not explain it. Timing problems and interactions that fail silently only show up in the replay, which is recorded for test runs from the monitor editor.
Monitors
Rather than loading a page, a store data check asks Shopify a question about your Admin data and fails when the answer is wrong. These failures never produce a broken page: a bundle with a deleted component still renders, a wrong price still displays, an empty collection still returns a page. No page-based check can find them.
One check watches one resource:
| Resource | What a rule can look at |
|---|---|
| Products | Title, handle, inventory, status, type, vendor, tags, collections, metafield values |
| Product variants | Price, compare-at price, SKU, barcode, inventory quantity, metafield values |
| Collections | Product count, title, handle, empty collections |
| Navigation menus | Each link you tick is still present and still points where it did |
| Metaobjects | Entry counts per definition, definitions left empty |
| Discount codes | Status, percentage, expiry, codes with no expiry, codes at 100% off |
| Unfulfilled orders | Orders unfulfilled for longer than a number of days you set |
Rules
Each rule is a quantifier, a field and a comparison, in plain words: every variant's price is more than 0; no discount's percentage equals 100; the collection's product count is at least 20. Every rule must pass. While the list is empty Shopcheck suggests rules to start from — inventory above zero, active status, metafield filled in, in a named collection, has a SKU, no empty collections, all menu links still there.
For products and variants, Match a filter — by product type, vendor or tag — covers products added later too; Pick specific products is a fixed list. Store data checks have no page to render, so they have no device setting.
Monitors
How often each monitor runs, the change triggers that run it on demand, and how noisy it is allowed to be.
More scheduling details:
- Browser checks run at most once an hour. Buyer journeys, and any check with a step that clicks or types, run in a real browser. Pair one with a frequent uptime check on the same page for a fast signal in between. A new journey runs once a day until you change it.
- A product is updated also runs the check when product data changes. A catalogue sync fires it once per product, so a burst is collapsed into one run.
- Give up after (ms) can be at most 30,000. For a journey it is how long each step may take.
- Recover after N passes is how many passes in a row close the incident again, one to start with. Raise it for something that flaps, so you are not told it is fixed three times before it is.
- For a journey, Estimated usage counts each device as its own run, so three devices use three times the browser minutes. See Plans & billing.
- Heartbeat and incoming-alert monitors wait to be called rather than running checks, and a change to your store cannot change what Shopify's status page says, so none of these has store-change triggers and the panel is called Timing and retries.
- API and latency monitors set their speed limit under What to check, beside their other expectations, so this panel does not offer Slow if over (ms) for them. Shopify status and Apps monitors have no speed limit at all.
Where an incident's alert goes is covered in alerts and incidents.
Who hears about a failure and how loudly: channels, severity, escalation and quiet hours, then what an incident shows.
More details on when incidents open and close, and who hears about them.
When an incident opens and closes
- Alert after N failures and Recover after N passes, on a monitor's Schedule step, decide when an incident opens and closes. Both are one to start with.
- A page check that fails with a network error or a server error is retried once, two seconds later, before it counts.
- Each device is tracked separately, but a monitor failing on several devices at once sends one alert.
- An incident closes on its own when the monitor recovers, and channels set to hear When it recovers are told. Resolve incident closes it by hand and sends no recovery notice.
Other channel settings
| Setting | What it does | To start with |
|---|---|---|
| Only alert at or above | The lowest severity the channel hears about. A monitor set to Info reaches nobody until you lower it | Warning |
| Which monitors | All active monitors, or only the ones that opt in to that channel | All active monitors |
| When Shopcheck can't reach the store | Tells you when Shopify or Cloudflare turns checks away. It is for whoever runs Shopcheck rather than the store | Off |
| Tier 2 and Escalate after (minutes) | Tier 2 channels hear only if the incident is still open after that long. That one escalation is the only follow-up; there are no repeating reminders | 30 minutes |
| Send test alert | Shows what arrives before a real failure does | — |
Email is the only channel on to start with. Email has a Subject prefix and Include monitor notes; Slack and webhook payloads always carry the notes. Slack's Mention on failure never fires on a recovery.
Not hearing the same thing twice
| Setting | What it does |
|---|---|
| Don't re-alert the same monitor within | Off, 15 minutes, 30 minutes (the starting point) or an hour, under Settings → Notifications. A new failure inside the window stays quiet; if the monitor is still failing when the window ends, you are told then. A monitor can override it on its Alerts step |
| Don't alert on manual runs | A failure from Run now is still recorded as an incident, but notifies no one |
| Grouping | When several monitors fail in the same scheduled run, which is what a platform outage looks like, they arrive as one notification at the highest severity among them |
Quiet hours
Set under Settings → Schedule → Quiet hours, in the store's Timezone. During them, scheduled checks are skipped for the whole store, so nothing fails and nothing alerts. It is a pause, not a mute: a problem that starts at 2am is found when quiet hours end. Pings from your own jobs that arrive in the meantime are checked afterwards, and checks triggered by a theme publish or a product update still run.
Because quiet hours pause every monitor, keep them short. To quieten one noisy monitor, raise its Alert after N failures or lower its severity instead.
Verifying a webhook
Signing is off until you turn it on. Tick Add a signature header so you can verify it came from
Shopcheck and enter a Shared secret of your own. Every request then carries an
X-Shopcheck-Signature header: the HMAC-SHA256 of the raw request body, keyed with that secret, as
hex.
import { createHmac, timingSafeEqual } from "node:crypto";
const expected = createHmac("sha256", process.env.SHOPCHECK_SECRET).update(rawBody).digest("hex");
const received = request.headers["x-shopcheck-signature"] ?? "";
const valid =
received.length === expected.length && timingSafeEqual(Buffer.from(received), Buffer.from(expected));
Compare against the raw body before it is parsed, and with a constant-time comparison. Shopcheck does not follow redirects and gives up after five seconds, so point it at the final URL and answer quickly.
More incident details
| On an incident | What it means |
|---|---|
| Revenue impact | Downtime weighed by how much the store usually sells at that hour of the week. Needs at least 10 paid orders in the last 30 days |
| Likely cause | Shopcheck's AI comparing the failure, and any errors the page threw, with the store changes on the timeline. It says so when nothing stands out |
| Monitoring limited | Not a failure of your store: Shopify or Cloudflare turned the check away before it saw the page, so the result says nothing either way. A crawler access signature under Settings → Store lets the checks through |
Incidents can be filtered by kind — failing checks, monitoring limited, conversion drops and Web Vitals — and sorted by age or revenue impact. Resolve selected closes several at once.
A setup that works for most stores
| Monitors | Severity | Who hears |
|---|---|---|
| Buyer journeys | Critical | A Slack channel people watch, with a note on each |
| Key page uptime | Warning | |
| Everything else | Info | Nobody. With the channels left at Warning, these open incidents in the dashboard without notifying anyone |
Core Web Vitals from real shoppers, Shopcheck's own runs and Google, side by side and never blended, with alerts when a vital slips.
More details on real-shopper data, the three sources and performance alerts.
When real-shopper data does not appear
| Cause | Why nothing arrives |
|---|---|
| Consent | The Shopcheck RUM embed only measures shoppers who accept analytics cookies, through Shopify's Customer Privacy API. A store with no consent banner never grants it, so nothing is sent. On a development store, turn off Require shopper analytics consent in the embed's settings to test |
| Leaving the page | The vitals are reported when the shopper switches tab, navigates away or closes the page. A page loaded and left open has sent nothing yet |
| Traffic | A password-protected or pre-launch storefront has no shoppers to measure |
Checkout pages sit outside your theme, so the embed cannot see them. The Shopcheck web pixel, added when you install and set to Always on under Settings → Customer events, counts checkout starts and completions there. Until it has reported, Checkout completion carries an asterisk.
Reading the three sources
| Source | What it measures | Over |
|---|---|---|
| Lab | Shopcheck's own runs, unthrottled from a datacenter | Each run |
| Your real users | The 75th percentile of real shoppers' page loads | The last 24 hours |
| Google CrUX | The 75th percentile of Google's field data. Only exists once a store has enough Chrome traffic | A rolling 28 days |
- Lab runs usually beat both field rows, and PageSpeed Insights' throttled mobile numbers too, because they run unthrottled. A gap is expected, not a contradiction.
- The Performance score is the real-shopper score, and falls back to the lab score while there is no shopper data. The label under it says which.
- The date, device and visitor filters apply to real-shopper data only. Speed vs competitors and the Google row always use Google's 28-day data, for up to five competitor domains.
- A breakdown row needs 50 page loads before it gets a pass or fail.
How performance alerts decide
| Alert | Watches | Fires when |
|---|---|---|
| Core Web Vitals thresholds | The 75th percentile of your latest 20 or more page loads from up to the last hour, checked every 15 minutes | A vital crosses its line, and again when it comes back under |
| Conversion and errors | Add-to-cart rate, checkout-start rate, add-to-cart failure rate and JS error rate, compared with your store's own last 24 hours so a normal quiet night is not flagged | A rate changes by 25, 35, 50 or 75% — 35% to start with |
- Each Core Web Vitals rule can be limited to devices and page types, and set to alert, track only or off.
- Conversion and error rules start as Track only, which records an incident without notifying anyone. Stores with little traffic are never flagged.
- Both show up under Incidents, as Web Vitals or conversion drops.
Reading a regression
| Ask | What the answer tells you |
|---|---|
| Which metric moved? | LCP, CLS and INP fail for different reasons — a heavy hero image, a shifting layout, and blocking JavaScript |
| Lab or shoppers first? | If the lab row moved, something on the page changed. If only real shoppers moved, the change is in who is visiting, from where, or on what |
| Which page? | The breakdown by page type tells you whether it is store-wide, which points at the theme or an app embed, or one template |
| What shipped? | Nearly every real regression lines up with a theme publish or an app update |
What performance monitoring does not do
It does not tell you the store works. A storefront can post excellent Core Web Vitals with a completely broken add-to-cart button — the page paints fast, it just does not function.
That is what buyer journeys are for. Speed and function are separate questions, and both need an answer.
Fixed tiers with usage included, what counts as an advanced monitor, and what happens when an allowance runs out.
Unlimited on every plan; they use check-minutes.
- Advanced monitors
- 4
- Browser minutes
- 648 min
- Check-minutes
- 288 min
- Essential$129.99/mo
3 advanced monitors included, plus 1 extra at $30.00 each. Needs more than its 450 browser minutes.
- GrowthBest fit$149.99/mo
5 advanced monitors included. 750 browser minutes covers it.
- Scale$299.99/mo
10 advanced monitors included. 1,500 browser minutes covers it.
Minutes are estimates from the same figures as the app's Estimated usage line, before a monitor has its own history. Prices in USD, billed through Shopify.
Shopcheck is priced as fixed monthly tiers with usage included, billed through Shopify. The one lever that changes your plan is how many advanced monitors you run. Try your own numbers in the calculator above; current prices are also on the pricing page.
Plans
| Plan | Price (USD/mo) | Advanced monitors included | Each extra advanced monitor | Browser minutes / mo | Check-minutes / mo |
|---|---|---|---|---|---|
| Essential | $99.99 | 3 | $30/mo | 450 | 1,500 |
| Growth | $149.99 | 5 | $30/mo | 750 | 2,500 |
| Scale | $299.99 | 10 | $20/mo | 1,500 | 5,000 |
Every plan starts with a 7-day free trial, run by Shopify, and keeps 90 days of history. Page checks run as often as every 15 minutes and browser checks as often as every hour on all three.
You are not billed per run, per screenshot or per minute. Storefront monitoring is the wrong product to hand anyone an unpredictable bill for: the moment frequency costs money per run, people check less often, and a check that runs rarely finds out late.
What counts as an advanced monitor
Any monitor that runs in a real browser — a buyer journey, or any check with a step that clicks or types — and every store data check. Uptime, latency, content, apps in your theme, API endpoint, Shopify status, AI agent readiness, heartbeat and incoming-alert monitors that do not open a browser are unlimited on every plan.
Two things do not change the count:
- Devices. One journey on desktop, tablet and mobile is one monitor.
- Frequency. Running a journey hourly or daily is the same monitor.
Extra advanced monitors
Past your plan's allowance, each extra advanced monitor is billed at your plan's add-on price rather than blocked, because adding one is the intended way to widen coverage. Shopcheck asks before saving one, and suggests the next plan up when that is the cheaper option. Charges are prorated.
The calculator above works out which plan is cheapest for your monitors: one or two extras cost less than moving up a plan, but once you need as many as the next plan includes, upgrading is cheaper.
Usage limits
Each plan includes two monthly allowances. Both are limits, never a bill, and both reset with your Shopify billing cycle.
| Allowance | What uses it | When it runs out |
|---|---|---|
| Browser minutes | Time your buyer journeys and other browser checks spend in a real browser | Browser checks are skipped until the cycle renews, each with the reason. Every other check keeps running |
| Check-minutes | Checks that do not open a browser: page, API and store data checks | Those checks pause until the cycle renews. Browser checks keep running |
Every channel at Tier 1 on the Notifications tab is told when an allowance is 80% used and again when it is used up, and Billing & Subscription shows where each one stands.
Nothing is silently dropped or shown as passing. A skipped run says it was skipped and why, because a monitoring product that quietly stops monitoring is worse than none: you still believe you are covered.
The Billing page
Billing & Subscription in the app shows:
| Panel | What it shows |
|---|---|
| Current plan | The plan, or the trial and when it ends, with Change plan |
| Usage this billing cycle | Check-minutes, browser minutes, advanced monitors and runs, when the cycle resets, and the Estimated charge |
| Extra advanced monitors | How many your plan includes, how many extras are approved and running, and what they have cost this cycle |
| Projected usage and Usage history | The check-minutes and browser minutes a month your current monitors are on course to use, and what each past billing cycle used |
Billing through Shopify
Charges appear on your Shopify invoice, so there is no card to store with us. You approve the charge in Shopify when you pick a plan.
Without a plan, Shopcheck is read-only: your monitors and history stay, but nothing runs until you choose one.
Changing plans
Change plan whenever your monitoring changes; monitors, history and baselines carry over. If a smaller plan includes fewer advanced monitors than you run, the ones beyond it become extras, and any extra you have not approved is paused, most recently enabled first.
What Shopcheck can see, what it keeps and for how long, and who else is involved in running it.
Permissions
Shopcheck asks for the minimum it needs to start, and for the rest only when you first use the feature that needs it. Decline any later request and everything else keeps working.
Requested at install:
| Scope | Why |
|---|---|
read_products | Product checks, and product updates on an incident's change timeline |
read_themes | Theme and app embed checks, theme publishes on the change timeline, and checking whether the Shopcheck RUM embed is on |
read_customer_events, write_pixels | The web pixel that counts checkout starts and completions on Shopify's checkout |
Requested later, only if you use it:
| Scope | Feature |
|---|---|
read_orders | Revenue impact on incidents, and store data checks on unfulfilled orders |
read_metaobjects, read_metaobject_definitions | Store data checks on metaobjects |
read_online_store_navigation | Store data checks on navigation menus |
read_discounts | Store data checks on discounts |
Revenue impact asks through Enable revenue tracking on an incident; the store data scopes are asked for in the check builder, when you pick that source.
What Shopcheck keeps
| Data | Kept for |
|---|---|
| Runs, check results and step results | 90 days |
| Screenshots of failing steps (or of every step, under Settings → Journeys → Screenshots) | 90 days |
| Resolved incidents | 90 days after they last changed |
| Real-shopper performance, aggregated, and the change timeline | 90 days |
| Visual regression baselines and your configuration | Until you uninstall |
Your configuration includes monitors, steps, selectors and thresholds, alert channels and their recipients, webhook secrets, monitor notes, and the storefront password if you set one.
Recordings are made only of a Run test you start from the monitor editor, never of scheduled or manual runs.
What that means for customer data
Screenshots and recordings capture whatever was on the page. A journey walking a real storefront records the storefront. If you build a journey that signs in to a customer account or types real personal details into a form, that content is in the screenshot and the recording. Use test data in journey steps, never real customer data.
Real-shopper monitoring is performance data, not session replay. The embed reports page speed, errors and add-to-cart events, only for shoppers who accept analytics cookies, and never collects personal details.
Revenue impact reads order totals and times only, to estimate what an outage cost.
Where it runs
Shopcheck runs on Cloudflare: checks at the edge, buyer journeys in Cloudflare Browser Rendering, data in Cloudflare D1 and R2. A few services outside Cloudflare handle specific features:
| Service | What it receives | When |
|---|---|---|
| Klaviyo | Alert emails and their recipients | Email alerts are on |
| Slack | Alert messages | Slack is connected |
| Google Gemini | Page structure, to draft monitors and suggest selectors; failure details and recent store changes, to suggest a likely cause | You use the AI assistant or a selector suggestion, or an incident opens with evidence to compare |
| Google Chrome UX Report | Your storefront domain and any competitor domains you add | The Performance page loads Google's field data |
Outbound webhooks
Webhook alerts can be signed with HMAC-SHA256 over the request body, in an X-Shopcheck-Signature
header. Signing is off until you turn it on and enter your own shared secret; see
verifying a webhook. An endpoint that does not verify the signature is an
open door for anyone who learns the URL.
Uninstalling
Uninstalling revokes Shopcheck's access immediately and deletes your store's data: monitors, runs, incidents, screenshots and baselines, performance data, the change timeline, alert channels and the Slack connection. Shopify asks every app to delete a store's data again 48 hours later, and Shopcheck does.
Reporting a security issue
Email the team through the contact page with the detail needed to reproduce it. Please do not open a public issue for anything exploitable.