Skip to content

Redirect after a quiz finishes

Goal: send the shopper to a page of your choice as soon as they finish a quiz, for example a collection that matches their answers, and send someone who opens their results link later to the same page.

Uses: Storefront JS API. Nothing to install: the API is part of the quiz embed already on the page. Events fire on the Plus and Enterprise plans.

quiz.finished carries everything the quiz collected: the answers, the points per dimension, the products shown and the discount codes. Each answer names the question (component_key), the value as stored (value), the option ids picked for a choice (option_ids) and the labels the shopper saw (values). Put only choice answers in the URL: a text, email or phone answer is the shopper’s own words and must never land in an address that gets logged.

<script>
window.octaneai = window.octaneai || [];
window.octaneai.push(function (octaneai) {
function redirect(event) {
var result = event.data.object;
var params = new URLSearchParams();
result.answers.forEach(function (answer) {
if (!answer.option_ids.length) return; // choice answers only, never free text
params.append(answer.component_key, answer.option_ids.join(','));
});
if (result.terminal_page) params.append('result', result.terminal_page);
window.location.href = '/collections/recommended?' + params.toString();
}
octaneai.on('quiz.finished', redirect);
octaneai.on('shared_result.viewed', redirect); // a results link opened later, from an email for example
});
</script>

quiz.finished fires once, after the result page is shown, so the shopper lands on your page rather than staying on the quiz’s own result page.

Every finished session has a results link (results_url on the people API, the webhooks and the exports), which is what a Klaviyo or other email flow sends as “see your results”. Whoever opens it lands on the quiz page, where the quiz shows its own result page and fires shared_result.viewed instead of quiz.finished: nobody finished a quiz, so no completion is counted. The event carries the same terminal_page, answers, points, formulas and products fields, which is why the one redirect function above serves both and the email visitor reaches your custom page too.

Two differences to keep in mind when a handler is shared: identities and discount_codes are always empty on shared_result.viewed and answers has no email or phone, because the person opening the link may not be the shopper who took the quiz; and session_id is null, with source_session_id naming the session the link belongs to. Keep conversion tracking on quiz.finished only.

Every snippet below runs inside the same window.octaneai.push(function (octaneai) { ... }) callback: before the quiz script loads, window.octaneai is a plain array and octaneai.on does not exist yet.

Read the answer to a specific question by its component key (the key the question has in the editor; a person’s latest_results[].answers[] on the REST API lists the same keys):

window.octaneai = window.octaneai || [];
window.octaneai.push(function (octaneai) {
octaneai.on('quiz.finished', function (event) {
var skinType = event.data.object.answers.find(function (answer) {
return answer.component_key === 'image_choice-48su5';
});
if (skinType && skinType.option_ids.indexOf('dry') !== -1) {
window.location.href = '/collections/dry-skin';
}
});
});

For a points quiz, points maps each dimension id to its total:

window.octaneai = window.octaneai || [];
window.octaneai.push(function (octaneai) {
octaneai.on('quiz.finished', function (event) {
var points = event.data.object.points;
var winner = Object.keys(points).sort(function (a, b) {
return points[b] - points[a];
})[0];
if (winner) window.location.href = '/collections/' + winner;
});
});

getState() returns what the quiz has so far: answers maps component keys to their values (saved or still on the page), page_key is the current page, and points and discount_codes are the running totals and the codes issued so far.

window.octaneai = window.octaneai || [];
window.octaneai.push(function (octaneai) {
octaneai.ready(function (api) {
api.on('page.viewed', function () {
var state = api.getState();
console.log('On page', state.page_key, 'with answers', state.answers);
});
});
});

Every answer shows up in question.answered as it is saved, so you can also react question by question:

window.octaneai = window.octaneai || [];
window.octaneai.push(function (octaneai) {
octaneai.on('question.answered', function (event) {
var answer = event.data.object;
console.log(answer.component_key, answer.value, answer.option_ids, answer.values);
});
});
  • Set localStorage['octaneai:debug'] = '1' in the console to see every event logged as it fires.
  • A listener that throws is logged as [OctaneAI] listener for quiz.finished threw and never breaks the quiz.
  • On a plan without the API, window.octaneai exists and on() registers, but no event fires and the console says so once.