Skip to content

A check-in quiz after the order

Goal: two weeks after an order, the shopper gets a short quiz (“How is the dry-skin routine going?”) that already knows which routine they bought and which order it was. Their answers reach your team, a refill offer, or a support ticket.

Uses: Storefront JS API (prefill from the URL, as in Prefill what the shopper already told you) on the check-in page; Webhooks (quiz.finished) or the redirect recipe to act on the answers. Nothing to install on the quiz side; the link carries everything.

1. The check-in quiz

Build a second, short quiz in the editor: one choice question keyed to the routine (single_choice-routine, options dry, oily, combination), a text question for the order number (text_input-order), the questions you actually want answered, and a result page per outcome (a refill page with the products, a “let us help” page). Publish it on its own page, /pages/check-in.

The first two questions are filled in by the link; the shopper sees them answered and moves on.

The order knows the routine when the quiz result rides on it (Put the quiz result on the order writes quiz_result as an order attribute). The link is the check-in page plus the prefill parameters and campaign tags:

https://shop.example.com/pages/check-in?q_single_choice-routine=dry&q_text_input-order=1042&skip=1&utm_source=email&utm_campaign=checkin
  • q_<component_key>=<stored value> prefills a question; skip=1 submits the first page at once when the link filled it (see the URL variant for the page script that reads these).
  • utm_source and utm_campaign land on the session: quiz.started and quiz.finished carry the five utm_* fields, so the check-in runs are separable in analytics and in your webhook handler.
  • The order number is a visible answer, not a hidden one: it is in answers["text_input-order"] on every payload, so the handler can find the order without guessing.

The order confirmation email (Settings > Notifications > Order confirmation) is Liquid and already has the order and its attributes; no deploy:

{% assign routine = attributes.quiz_result %}
{% if routine != blank %}
<p>
In two weeks we will ask how it is going. You can also tell us any time:
<a href="https://shop.example.com/pages/check-in?q_single_choice-routine={{ routine | url_encode }}&q_text_input-order={{ order_number }}&skip=1&utm_source=email&utm_campaign=checkin">
How is your {{ routine }} routine?
</a>
</p>
{% endif %}

The order status page (the page the shopper lands on after paying and returns to from the confirmation email) is a checkout UI extension target, customer-account.order-status.block.render, in a Shopify CLI app. The block reads the order’s attributes with useAttributeValues(['quiz_result']) and the order name with useOrder(), and renders a Link to the same URL. The extension is a deploy of its own; the email is not, so start there.

Two weeks later: Shopify Flow, trigger Order created, condition order.customAttributes contains quiz_result, action Wait 14 days, then the action your messaging app installs in Flow (Klaviyo Send SMS, Postscript Send message, or Shopify Email). The message body takes the same link with utm_source=sms. This step is described, not run here: the messaging app’s Flow action is where the SMS is sent, and each app names the action differently.

4. Act on the answers

The check-in session is a normal session. Choose one:

  • Route on the page: the check-in quiz’s own result pages do the routing (a refill page with the routine’s products; a “we will call you” page). Nothing to code. For a page outside the quiz, Redirect after a quiz finishes reads answers and sends the shopper on.
  • Reach your tools: a quiz.finished webhook on the check-in quiz’s id carries the order number, the routine and the answers; the handler opens a ticket, or sends the CRM feed of Send every finished quiz to your CRM. Filter on quiz_id: one endpoint receives every quiz’s finishes.
def handle(event):
if event["type"] != "quiz.finished":
return
s = event["data"]["object"]
if s["quiz_id"] != "quiz_2ccb8aebb27c463fa783cafb3bf858e0": # the check-in quiz
return
order = s["answers"].get("text_input-order")
routine = s["answers"].get("single_choice-routine")
verdict = s["answers"].get("single_choice-verdict")
print(order and order["value"], routine and routine["value"], verdict and verdict["values"], s["utm_campaign"])
# 1042 dry ['Not for me'] checkin

Things to know

  • The link is a prefill, not a login: anyone with it can open the quiz with those values. Keep the values to what the email already shows the shopper (their routine, their order number).
  • The check-in runs are new sessions on a second quiz; the person’s latest_results gains an entry for the check-in quiz and keeps the original quiz’s entry. GET /v1/profiles?q=<email> finds both.
  • page_url on the session payloads is origin and path only; the query string is not stored. What must reach the handler goes in a prefilled question or in the utm_* fields.
  • A shopper who ordered without taking the quiz has no quiz_result attribute; the email’s {% if %} skips the paragraph and the Flow condition skips the message.