Stitchwork Docs

Nudges — messages to a browsing visitor

Everything else in Stitchwork points at your staff. A nudge points at the shopper: a short message across the top of your own site, shown to a visitor whose browsing matched a rule you wrote, while they are still in the session — and measured against a control group that saw nothing, so you can tell whether it did anything.

Nudges are one of the optimisation tools: part of Collection, and an add-on to Independent. On a plan without them your site shows no nudges and the rules you wrote are kept for when they are switched on; tracking carries on either way.

What a nudge is

One line of text, your own words, shown on your own storefront to a visitor who matches a rule. It can ask for an email address, ask for a phone number, point at a page, or just say the thing and get out of the way. It works on visitors who have never told you who they are, which is most of them.

Your tracking tag renders it. There is no second script to paste and nothing to add to your theme — if the tag is installed and consented, nudges work.

What it is not:

Where it fits

A nudge is the front end of the same loop the rest of the product runs. The visitor profile behind recommendations is what a rule reads. A phone number typed into a nudge opens a follow-up, so somebody owes that person a call. An email typed into one runs the same identity stitching as every other identification, so their whole prior trail joins a customer record.

Every nudge holds people back

This is the part worth reading before you write one, because it is the part that decides whether the report means anything.

The obvious version of this feature cannot be falsified. Show a banner to interested people, watch some of them buy, credit the banner — when interested people were always the ones most likely to buy. A conversion rate measured on the people who saw it is a measurement of your targeting, and it would look impressive for a nudge that rendered a blank bar.

So a visitor who matches your rule is assigned an arm before anything renders:

ArmWhat happens
shown They match, and they see the message.
holdout They match, and they see nothing at all.

Both arms are recorded. Lift is the difference between them — the same people, wanting the same things, split by nothing except whether the bar appeared. That is the only number in this feature that is about the message rather than about who you aimed it at.

The default holdout is 10% — one matching visitor in ten. You can set anything from 0% to 50%. A bigger holdout settles the question faster and costs more showings; 0% turns measurement off entirely, and the report then says so rather than quietly reporting the treated arm's conversion rate as a result.

The control group cannot tell it is the control group

A held-back visitor receives exactly what a visitor who matched nothing receives: no message, no flag, no marker in the response. There is nothing on the page to inspect.

The arm is derived from the nudge and the visitor's own token, so a refresh cannot move somebody from control into treatment — and it is written down as well as derived, so a visitor assigned under a 10% split keeps reporting under it after you change the split.

Writing one

Settings → Nudges. A nudge has five parts:

FieldNotes
Name Internal only. The shopper never sees it. "Stalled bed browsers" beats "Nudge 4" in three weeks' time.
Message One line, 240 characters maximum. It has to read across the top of a page.
What to ask for Nothing, an email, a phone number, or a button. See below.
Who sees it The rule. The form asks for four numbers and a yes/no; the API takes the full grammar.
Holdout How many matching visitors are held back to measure against. Default 10%.

A nudge is draft, live or paused. Draft is the default — Save as draft writes it without showing it to anybody, and Save & turn on puts it live. You can give one a start date, an end date, or both, which is the difference between a promotion that stops on time and one a customer tells you about a fortnight later.

Write it the way you would say it across a shop floor. A message that reads as "we can see exactly what you have been looking at" lands badly, however true it is. "Still deciding? Our bed specialist can call you back" says the same thing from the customer's side.

Start from your own buyers, not a guess

Writing your first nudge means picking numbers — how many product views counts as interested, how long a pause counts as stalling — with nothing to base them on. A guess produces a rule that fires on everybody or on nobody, and both look like the feature not working.

Seed from my buyers answers it from your own history. It measures what customers who actually bought had done before their first paid order — how many products they had looked at, across how many distinct items, over how many visits — and fills the thresholds in with those numbers. It prints the sentence saying how many customers it learned from.

Below 25 converting customers in the window it returns the shipped defaults and says so. Percentiles over a dozen orders are noise wearing a number's clothes.

It calibrates cut points, never weights

Seeding sets the thresholds in a rule. It does not train a scorer, and it does not touch how Stitchwork decides who is interested. That matters because the ranking engine on your storefront and the lead list on your shop floor have to agree about what "interested" means — a second model here would be a third opinion that could disagree with both and explain itself to neither. What comes back is an ordinary rule set you can edit by hand, and it behaves identically to one you typed yourself.

What you can ask for

CTAWhat it does
none A statement, with a dismiss control. Often the least intrusive thing available — and the cheapest thing to test a message with.
button A label and a destination. Booking a video call, a collection slot, a size guide. The URL must start http:// or https://, checked when you save it and again in the database — it ends up as a link in your own page.
email An address, routed to the same door every other identification goes through. The browser's entire prior trail attaches to a customer record retroactively and appears on their timeline.
phone A callback request. Where the browser is already stitched to a customer this opens a follow-up — somebody now owes that person a call. Where it is not, it joins the callback queue.

An email or phone CTA should carry a confirmation line — shown once they submit. Without one the bar simply disappears and they type it in again.

A callback from an anonymous browser

Where the browser has no customer record yet, the number is held rather than turned into a customer. A Stitchwork customer record requires an email address, and inventing a placeholder one to route around that is not a decision a storefront banner should make quietly.

These land in the callback queue on Leads — the number, how long they have waited, and what they were looking at. Somebody rings them, takes an email on the call, and claims it into a real customer and a real follow-up. The nudge report counts what is still waiting as callbacks_unclaimed; the queue is where you actually work them.

Answering twice is one answer. A shopper who submits, reloads and submits again does not open two follow-ups or create two customer records.

Who sees it

A rule is conditions over facts you can count. There is no intent score, no engagement stage and no interest ranking to target on — nothing where the platform decided something on your behalf and you would have to take our word for the result. If a visitor matches what the conditions say, they see the message; if they do not, they do not. The settings form is a builder over this same grammar, and every rule is read back as a plain sentence, so nothing anywhere re-derives what a rule means.

{"all": [
  {"fact": "who.identified",        "op": "is_false"},
  {"fact": "history.product_views", "op": "gte", "value": 6, "within_days": 14},
  {"fact": "history.products",      "op": "gte", "value": 3, "within_days": 14},
  {"fact": "history.orders",        "op": "eq",  "value": 0, "within_days": 365},
  {"fact": "visit.seconds_since_action", "op": "gte", "value": 90}
]}

Read that back and it says what it does: somebody we cannot email, who has looked at six products across three lines in the last fortnight, has never ordered, and has now stopped moving. That last clause is the one worth copying — it stops the bar interrupting somebody who is already busy buying, the single moment a message across the top of the page can cost you a sale.

Every count says over how long

Each history.* condition carries its own within_days, between 1 and 365, and a condition without one is refused rather than given a default. A window nobody chose is a rule nobody can read: "viewed 3 products" means something very different over a day than over a year, and a returning customer would clear a long window before they had done anything at all today.

Per condition rather than per nudge, because the useful rules need two windows at once — "looked at three beds this week and has not ordered anything in a year" is one rule.

The facts a rule can name

FactMeans
everyoneEvery visitor. Targeting everybody is available, but you have to say so.
Who they are
who.identifiedWe have an email address for them.
who.sourceutm_source of their most recent campaign touch, or the ad platform behind the click.
who.mediumutm_medium.
who.campaignutm_campaign.
This visit — counted by the tag. A visit is a browser tab.
visit.page_viewsPages viewed in this visit.
visit.product_viewsProduct pages viewed in this visit.
visit.productsDifferent products viewed or basketed in this visit. Counted in the browser from the codes your storefront sends, so two variants of one product count as two here where history.products resolves them against your catalogue and counts one.
visit.cartsThings added to the basket in this visit.
visit.searchesSearches run in this visit.
visit.seconds_since_actionSeconds since they last clicked, typed, scrolled or tapped.
visit.pathPath of the page they are on now.
Their history — every one of these needs within_days.
history.product_viewsProduct pages viewed.
history.productsDifferent products viewed or added to the basket.
history.cartsThings added to the basket.
history.searchesSearches run.
history.purchasesPurchases your website reported through the tag.
history.ordersOrders on their customer record. Kept separate from purchases on purpose: one is what the site said, the other is what your books say, and which you mean is your call.
history.quotesQuotes raised on their customer record.
history.categoriesCategories of products they viewed or basketed — the raw set, not a ranking (a list).
history.brandsBrands of products they viewed or basketed (a list).
history.days_since_first_seenDays since we first saw this browser. Not windowed.
history.days_since_last_seenDays since we last saw this browser. Not windowed.

Each fact has a type, and the type decides its operators. Numbers take eq, ne, gt, gte, lt, lte; text takes eq, ne, in, not_in, starts_with; lists take includes, not_includes; yes/no facts take is_true, is_false. An operator the fact cannot answer is refused when you save it rather than quietly matching nobody for ever. Conditions combine under all or any. GET /api/v1/nudges returns the facts, their types and the operators each one accepts, so a form of your own can build itself from the contract instead of duplicating it.

Unrecognised means no match, never a match

An unknown fact, an unknown operator, a malformed condition and an empty rule set all evaluate to no match — never to a match, and never to an error. The direction is deliberate: the failure mode of "unrecognised means true" is a broadcast to every visitor you have, caused by a typo.

A history.* condition with no within_days, or one naming a window nothing was counted for, is in the same category: no match. We would rather answer nothing than answer a question you did not ask.

Facts under visit. are reported by the browser and treated as such — they are clamped, and the worst a forged one can do is show somebody a message meant for a different sort of shopper. Every history. and who. fact is computed on our side from the visitor token and cannot be influenced from the page.

How often it appears

FrequencyBehaviour
once_per_visitor
default
Once, ever. The safest setting and the right one for most messages.
once_per_sessionAt most once in any 24 hours. A server cannot see a session boundary, so this is what it honestly means.
every_n_daysAt most once every n days, 1–365.

Three things stop a nudge coming back regardless of frequency:

Where several live nudges match the same visitor, the highest priority wins and only one is shown. A visitor never sees two at once.

What it does to your page

Your storefront is not ours to reflow, so the bar overlays rather than pushing the page down. Covering a sticky header for a few seconds is a smaller risk than reflowing a layout we did not write, possibly mid-checkout.

A visitor with no __sw_v cookie — no consent, first request, cookies cleared — is a visitor Stitchwork has nothing to target on, and gets no nudge. Nudges are for the second half of a visit, not the first second of it.

Reading the report

Every nudge row has a report behind it. It answers one question — did the message do anything? — and it is written to be capable of answering "we cannot tell yet".

VerdictMeans
countingFewer than 30 visitors in one of the arms. No verdict is offered, and the note says how many each arm has.
unmeasuredThe holdout is 0%. There is no control group, so no lift figure is offered at all.
betterThe shown arm converted more, at 95% confidence.
worseThe shown arm converted less, at 95% confidence. This is a real result and the report will say it.
inconclusiveA difference, not a confident one — and the note says how many more visitors per arm would settle a difference that size.
no_differenceBoth arms converted at the same rate.

Alongside the verdict:

A nudge that cannot beat its own holdout is one to turn off

That is the whole reason the holdout exists, and the report is built to be able to say so. A nudge reporting worse is more useful than one reporting a 200% conversion rate on the treated arm, because somebody will spend money on the second one.

The API

The tag calls these for you. They are documented for headless storefronts and native apps, where there is no page for our script to run in.

get/api/v1/public/nudgepk_live

Query: visitor_token (required), path, seconds_since_action, and this visit's tally — page_views, product_views, products, carts, searches. CORS-enabled with OPTIONS preflight.

Returns {nudge: {id, message, cta_type, cta_label, cta_url, confirm_text}}, or {nudge: null}. No match, a holdout, an unknown token and an absent token all return the same empty answer — so this endpoint cannot be used to work out whether a token is live, or whether a visitor is in the control group.

This is a GET that writes: matching assigns an experiment arm. Responses are no-store, private and must never be cached — the arm and the frequency cap both live server-side. Any internal failure degrades to {nudge: null} rather than an error in your page.

post/api/v1/public/nudge/responsepk_live

Body: visitor_token, nudge_id, kind — one of click, email, phone, dismiss — and value for the two that carry one. Returns 204 and nothing else, for the same reason /identify does: anyone can type an address into a box on a public page, so an answer that confirmed anything about the customer behind it would be readable with a key that ships in your page source.

The visitor token is the credential. A response can only ever move the row belonging to the browser holding that token, so the arms cannot be stuffed from a script.

Rate limited per workspace per minute (2000 by default; ask us if your traffic needs more). Authoring, reporting and seeding are done from Settings → Nudges — those screens authenticate with your signed-in session, so there is no credential to hold and nothing to call them with from outside.

What a nudge can never do

Every write to a nudge is recorded in your audit log with who made it — a note is internal, but a nudge is text shown to a stranger on your storefront, and "who turned this on" is a question that gets asked eventually. A proposal is audited under the agent's own name, and the approval under yours.