Multiple Choice Question

A custom HTML template for Multiple Choie Question for in-app messages. Copy the code, edit the code, and publish.

MCQ Quiz asks the user a short series of multiple-choice questions, shows green or red feedback after each answer, then gives them one of two rewards depending on how many they got right. It is best suited for campaigns such as live event tie-ins, product education, onboarding, and engagement pushes where participation matters more than the prize. It is the only template where the outcome depends on what the user does rather than on chance.

Configure MCQ Quiz for Custom HTML Code


📘

This Is a Code Template, Not a Product Feature

  • MCQ Quiz is not a widget you enable in the Netcore dashboard, and it will not appear in any menu. It is a block of HTML that you copy from this page and paste into the Code of HTML Editor
  • Netcore does not host, version, or update this template on your behalf.

You are copying code out of this page and pasting it into a html code editor for campaign. Follow these steps in order.

  1. Create an in-app template. See Custom HTML for the full template creation flow.

  2. Copy the entire code block from the Sample HTML Code section on this page, from the first line to the last. Partial copies will not work.

  3. Paste it into the Code Editor and select Validate.

  4. Edit only the CONFIG block at the top of the code. This is where your questions, answers, rewards, and deeplink go. See Editable and Non Editable Section in Custom HTML Code before you change anything.

  5. Replace the placeholder deeplink. The template ships with netcore://your-deeplink-here, which does nothing. Set claimButton.url to a real deeplink registered in your app.

  6. Preview the message in the right pane, then schedule and publish.

📘

Set Up the Underlying Offer Separately

This template displays a reward code and copies it to the user's clipboard. It does not create coupons, apply discounts, or deduct inventory. Create the actual offer in your commerce platform or in Coupon

🚧

The Correct Answers Are Visible in the Message Source

The correctIndex value sits in plain text inside the creative and the score is calculated on the device. A technically curious user can read the answers. Use this template for engagement, not for anything where winning carries real value or where fairness could be challenged.

Sample HTML Code

<!DOCTYPE html>
<html lang="en">

<head>
  <!-- ============================================================= -->
  <!-- CUSTOMIZE ZONE - Configurable Settings                        -->
  <!-- Everything inside CONFIG is safe to edit. This is the ONLY    -->
  <!-- part a marketer/non-developer needs to touch.                 -->
  <!-- ============================================================= -->
  <script>
    const CONFIG = {
      // Show the X close button (true = show, false = hide)
      includeDismissButton: true,

      // Background gradient of the card
      background: "linear-gradient(180deg, #FFE3F1 0%, #FFD1E8 100%)",

      // Main headline (supports HTML like <br>)
      mainTitle: {
        text: "Play &amp; Win! \uD83C\uDFC6",
        fontSize: "22px",
        color: "#83164E",
      },

      // Subtitle / description
      subtitle: {
        text: "Answer correctly to unlock your reward!",
        fontSize: "13px",
        color: "#A24A75",
      },

      // THE QUIZ - add as many questions as you like.
      //   - question     - the question text
      //   - options      - 2 to 4 answer choices
      //   - correctIndex - WHICH option is correct, counting from 0:
      //                    0 = first option, 1 = second, 2 = third, 3 = fourth
      questions: [
        {
          question: "How many runs will Delhi score today?",
          options: ["120-140", "140-160", "160-180", "More than 180"],
          correctIndex: 2,
        },
        {
          question: "Which player has the most runs this season?",
          options: ["Player A", "Player B", "Player C"],
          correctIndex: 1,
        },
        {
          question: "Where is tonight's match being played?",
          options: ["Delhi", "Mumbai", "Chennai", "Kolkata"],
          correctIndex: 0,
        },
      ],

      // Correct answers needed to win the PASS reward
      passScore: 2,

      // Seconds the green/red feedback stays before the next question
      feedbackSeconds: 0.9,

      // Answer button colours
      optionColors: {
        background: "#ffffff",
        color: "#3A0B24",
        borderColor: "#F3B4D2",
        correctBackground: "#2FBF71",   // flashed on the right answer
        wrongBackground: "#E5484D",     // flashed on a wrong pick
        feedbackTextColor: "#ffffff",
      },

      // Result screen when the user scores passScore or more
      resultPass: {
        title: "You nailed it! \uD83C\uDF89",
        subtitle: "Great score! Here's your reward:",
        rewardValue: "QUIZ20",
        rewardSubtext: "Use QUIZ20 at checkout to get 20% off your next order.",
        showConfetti: true,
      },

      // Result screen when the user scores below passScore
      resultFail: {
        title: "Nice try! \uD83D\uDCAA",
        subtitle: "Not quite - but here's a little something anyway:",
        rewardValue: "TRY5",
        rewardSubtext: "Use TRY5 at checkout for 5% off. Better luck next time!",
        showConfetti: false,
        // Set to false to show NO consolation reward on a failed quiz
        showReward: true,
      },

      // Claim button (on the result screen)
      claimButton: {
        enabled: true,
        text: "Claim Reward",
        background: "#D6246E",
        color: "#ffffff",

        // Netcore deeplink fired on claim. Replace with your app's
        //    deeplink / URL scheme (e.g. "myapp://offers/quiz-win").
        url: "netcore://your-deeplink-here",

        // OPTIONAL extra data sent to the app alongside the click.
        //    Set to null if not needed.
        payload: { campaign: "mcq_quiz" },
      },

      // Copy-instruction text colors
      copyInstruction: {
        color: "#606060",
        copiedColor: "#D6246E",
      },
    };
  </script>
  <!-- ============================================================= -->
  <!-- END CUSTOMIZE ZONE                                              -->
  <!-- ============================================================= -->

  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Quiz - Play and Win!</title>

  <!-- ============================================================= -->
  <!-- DO NOT MODIFY - Netcore Smartech In-App SDK wiring              -->
  <!-- Order matters: jQuery -> Smartech App SDK -> init().            -->
  <!-- This is what makes the message trackable & interactive on the -->
  <!-- Netcore platform. Removing/reordering this breaks the message.-->
  <!-- ============================================================= -->
  <script src="https://code.jquery.com/jquery-3.4.1.min.js"
    integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
  <script src="https://cdnt.netcoresmartech.com/smartech-app-sdk/scripts/prod/smartech-app-sdk.js?v=1.0.1"></script>
  <script>
    SmartechAppSDK.init();
  </script>
  <!-- ============================================================= -->
  <!-- END SDK wiring                                                  -->
  <!-- ============================================================= -->

  <style>
    :root {
      --shimmer-hue-1: 330deg;
      --shimmer-hue-2: 320deg;
    }

    @property --mask {
      syntax: "<angle>";
      inherits: false;
      initial-value: 33deg;
    }

    @keyframes spin {
      0% { --mask: 0deg; }
      100% { --mask: 360deg; }
    }

    html,
    body {
      height: 100%;
      width: 100%;
      margin: 0;
      display: flex;
      align-items: center;
      justify-content: space-evenly;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
    }

    .modal {
      display: flex;
      flex-direction: column;
      align-items: center;
      height: 100%;
      max-height: 580px;
      max-width: 320px;
      padding: 20px 5%;
      justify-content: flex-start;
      gap: 14px;
      width: 100%;
      text-align: center;
      background: linear-gradient(180deg, #FFE3F1 0%, #FFD1E8 100%);
      border-radius: 36px;
      position: relative;
      box-sizing: border-box;
      overflow: hidden;
    }

    .modal.result {
      justify-content: space-evenly;
      max-height: 455px;
    }

    /* Close button */
    .close-btn {
      position: absolute;
      top: 18px;
      right: 18px;
      appearance: none;
      border: 0;
      background: none;
      color: #83164E;
      font-size: 32px;
      line-height: 1;
      cursor: pointer;
      z-index: 20;
    }

    h1 { margin: 10px 0 0; font-size: 22px; font-weight: 700; }
    p { margin: 0; font-size: 13px; line-height: 1.45; }

    /* Progress dots */
    .progress {
      display: flex;
      gap: 8px;
      justify-content: center;
      margin-top: 4px;
    }

    .progress .dot {
      width: 10px;
      height: 10px;
      border-radius: 50%;
      background: rgba(0, 0, 0, 0.15);
      transition: background .2s;
    }

    .progress .dot.done { background: #2FBF71; }
    .progress .dot.current { background: #D6246E; }

    /* Question + options */
    .question-text {
      font-size: 17px;
      font-weight: 700;
      min-height: 48px;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 0 6px;
    }

    .options {
      display: flex;
      flex-direction: column;
      gap: 10px;
      width: 100%;
    }

    .option-btn {
      width: 100%;
      padding: 13px 14px;
      border-radius: 12px;
      border: 1.5px solid;
      font-size: 14px;
      font-weight: 600;
      cursor: pointer;
      transition: transform .08s ease, background .15s ease, color .15s ease;
      box-sizing: border-box;
    }

    .option-btn:active { transform: scale(0.98); }
    .option-btn:disabled { cursor: default; }

    /* Reward display (result screen) */
    .reward-wrapper {
      display: none;
      width: 100%;
      position: relative;
      border-radius: 18px;
      overflow: visible;
    }

    .reward-wrapper.visible { display: block; }

    .reward-box {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 6px;
      padding: 16px 0;
      background: #ffffff;
      color: #1d1d1d;
      border-radius: 18px;
      width: 100%;
      box-sizing: border-box;
      cursor: pointer;
    }

    .reward-main {
      font-size: 24px;
      font-weight: 700;
      display: flex;
      align-items: center;
      gap: 3px;
    }

    .reward-subtext { padding: 0 20px; font-size: 14px; font-weight: 500; color: #4A4C4C; }

    .copy-instruction {
      font-family: 'Proxima Nova', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
      font-weight: 500;
      font-size: 13px;
      line-height: 100%;
      text-align: center;
    }

    .copy-btn {
      display: inline;
      appearance: none;
      border: none;
      background: none;
      cursor: pointer;
      font-size: 24px;
      line-height: 1;
      color: #1d1d1d;
      transition: transform 0.1s ease-in-out;
    }

    .copy-btn:active { transform: scale(0.9); }

    /* Claim button */
    .action-btn {
      width: fit-content;
      padding: 12.5px 40px;
      border: none;
      border-radius: 14px;
      background: #D6246E;
      color: #ffffff;
      font-size: 16px;
      font-weight: 700;
      cursor: pointer;
    }

    /* Pulsating reward ticket */
    @keyframes pulsate {
      0% { transform: scale(1); }
      50% { transform: scale(1.05); }
      100% { transform: scale(1); }
    }

    .pulsate-animation { animation: pulsate 1.5s 3; }

    /* Shimmer effect */
    .shimmer {
      position: absolute;
      inset: -40px;
      border-radius: inherit;
      mix-blend-mode: plus-lighter;
      pointer-events: none;
      mask-image: conic-gradient(from var(--mask, 0deg),
          transparent 0%, transparent 10%, black 36%, black 45%,
          transparent 50%, transparent 60%, black 85%, black 95%, transparent 100%);
      mask-size: cover;
      animation: spin 3s linear infinite both -0.5s;
      z-index: 10;
    }

    .shimmer::before, .shimmer::after {
      content: "";
      border-radius: inherit;
      position: absolute;
      inset: 40px;
      opacity: 1;
    }

    .shimmer::before {
      box-shadow: 0 0 2px 1px hsl(var(--shimmer-hue-1) 90% 58%),
        0 0 9px 6px hsl(var(--shimmer-hue-2) 70% 60%);
      z-index: -1;
    }

    .reward-wrapper.visible .shimmer { opacity: 1; visibility: visible; }
    .reward-wrapper.shimmer-active .shimmer { animation: spin 2s linear infinite; }
  </style>
</head>

<body>
  <!-- ---------------- Quiz screen ---------------- -->
  <div class="modal" id="quizModal">
    <!-- data-smt-action="SMTInAppClose" lets the Netcore SDK close
             the message AND record the close event automatically. -->
    <button class="close-btn" aria-label="Close" data-smt-action="SMTInAppClose">&times;</button>

    <h1 id="mainTitle"></h1>
    <p id="subtitle"></p>

    <div class="progress" id="progress"></div>
    <div class="question-text" id="questionText"></div>
    <div class="options" id="options"></div>
  </div>

  <!-- ---------------- Result screen ---------------- -->
  <div class="modal result" id="resultModal" style="display: none;">
    <button class="close-btn" aria-label="Close" data-smt-action="SMTInAppClose">&times;</button>

    <h1 id="resultTitle"></h1>
    <p id="resultSubtitle"></p>
    <p id="scoreLine" style="font-weight:700;"></p>

    <div class="reward-wrapper" id="rewardWrapper">
      <span class="shimmer"></span>
      <div class="reward-box" id="rewardBox">
        <div class="reward-main">
          <span id="rewardText"></span>
          <button id="copyBtn" class="copy-btn" title="Copy to clipboard">
            <svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
              <path fill-rule="evenodd" clip-rule="evenodd"
                d="M13 13.8444V17.8444C13 18.4444 12.6 18.8444 12 18.8444H3C2.4 18.8444 2 18.4444 2 17.8444V8.84442C2 8.24442 2.4 7.84442 3 7.84442H7V3.84442C7 3.24442 7.4 2.84442 8 2.84442H17C17.6 2.84442 18 3.24442 18 3.84442V12.8444C18 13.4444 17.6 13.8444 17 13.8444H13ZM12 13.8444H8C7.4 13.8444 7 13.4444 7 12.8444V8.84442H3V17.8444H12V13.8444ZM17 3.84442H8V12.8444H17V3.84442Z"
                fill="#4A4C4C" />
            </svg>
          </button>
        </div>
        <div class="reward-subtext" id="rewardSubtext"></div>
        <div class="copy-instruction">Click here to copy the code!</div>
      </div>
    </div>

    <button class="action-btn" id="claimButton">Claim Reward</button>
  </div>

  <script>
    (function () {
      // -------------------- Element refs --------------------
      const quizModal = document.getElementById('quizModal');
      const resultModal = document.getElementById('resultModal');
      const mainTitleEl = document.getElementById('mainTitle');
      const subtitleEl = document.getElementById('subtitle');
      const progressEl = document.getElementById('progress');
      const questionTextEl = document.getElementById('questionText');
      const optionsEl = document.getElementById('options');
      const resultTitleEl = document.getElementById('resultTitle');
      const resultSubtitleEl = document.getElementById('resultSubtitle');
      const scoreLineEl = document.getElementById('scoreLine');
      const rewardWrapper = document.getElementById('rewardWrapper');
      const rewardBox = document.getElementById('rewardBox');
      const rewardTextEl = document.getElementById('rewardText');
      const rewardSubtextEl = document.getElementById('rewardSubtext');
      const copyBtn = document.getElementById('copyBtn');
      const copyInstructionEl = document.querySelector('.copy-instruction');
      const claimButton = document.getElementById('claimButton');
      const closeButtons = document.querySelectorAll('.close-btn');

      const questions = CONFIG.questions;
      let currentQ = 0;
      let score = 0;
      let locked = false;
      let currentRewardValue = '';

      // ==========================================================
      // DO NOT MODIFY - Netcore SDK interaction helpers
      // ==========================================================

      // Fire a tracked In-App CLICK to the app (deeplink + optional data).
      // Uses the SDK when present; falls back to a plain redirect so the
      // template still "does something" in a bare browser preview.
      function netcoreTrackClick (deeplink, payload) {
        try {
          if (window.SmartechAppSDK && typeof SmartechAppSDK.trackInAppClick === 'function') {
            SmartechAppSDK.trackInAppClick(deeplink, payload || null);
            return;
          }
        } catch (e) { /* swallow - fall through to fallback */ }
        if (deeplink) window.location.href = deeplink;
      }
      // ==========================================================
      // END SDK helpers
      // ==========================================================

      // -------------------- Apply Config --------------------

      // Modal background
      if (CONFIG.background) quizModal.style.background = CONFIG.background;
      if (CONFIG.background) resultModal.style.background = CONFIG.background;

      // Dismiss button: hide if disabled. When enabled, the SDK handles
      // close+tracking via data-smt-action.
      if (!CONFIG.includeDismissButton && closeButtons.length > 0) {
        closeButtons.forEach(btn => { btn.style.display = 'none'; });
      }

      // Main Title & Subtitle
      mainTitleEl.innerHTML = CONFIG.mainTitle.text;
      mainTitleEl.style.fontSize = CONFIG.mainTitle.fontSize;
      mainTitleEl.style.color = CONFIG.mainTitle.color;
      resultTitleEl.style.color = CONFIG.mainTitle.color;
      scoreLineEl.style.color = CONFIG.mainTitle.color;

      subtitleEl.textContent = CONFIG.subtitle.text;
      subtitleEl.style.fontSize = CONFIG.subtitle.fontSize;
      subtitleEl.style.color = CONFIG.subtitle.color;
      resultSubtitleEl.style.color = CONFIG.subtitle.color;

      questionTextEl.style.color = CONFIG.optionColors.color;

      // Copy instruction color
      if (copyInstructionEl && CONFIG.copyInstruction.color) {
        copyInstructionEl.style.color = CONFIG.copyInstruction.color;
      }

      // -------------------- Progress dots --------------------
      function renderProgress () {
        progressEl.innerHTML = '';
        questions.forEach((_, i) => {
          const dot = document.createElement('div');
          dot.className = 'dot' + (i < currentQ ? ' done' : i === currentQ ? ' current' : '');
          progressEl.appendChild(dot);
        });
      }

      // -------------------- Render a question --------------------
      function renderQuestion () {
        locked = false;
        renderProgress();
        const q = questions[currentQ];
        questionTextEl.textContent = q.question;
        optionsEl.innerHTML = '';

        q.options.forEach((opt, idx) => {
          const btn = document.createElement('button');
          btn.className = 'option-btn';
          btn.textContent = opt;
          btn.style.background = CONFIG.optionColors.background;
          btn.style.color = CONFIG.optionColors.color;
          btn.style.borderColor = CONFIG.optionColors.borderColor;
          btn.addEventListener('click', () => answer(idx, btn));
          optionsEl.appendChild(btn);
        });
      }

      // -------------------- Handle an answer --------------------
      function answer (idx, clickedBtn) {
        if (locked) return;
        locked = true;

        const q = questions[currentQ];
        const buttons = optionsEl.querySelectorAll('.option-btn');
        buttons.forEach((b) => { b.disabled = true; });

        const correct = idx === q.correctIndex;
        if (correct) score++;

        // Colour feedback: wrong pick -> red; the right answer always green
        if (!correct) {
          clickedBtn.style.background = CONFIG.optionColors.wrongBackground;
          clickedBtn.style.color = CONFIG.optionColors.feedbackTextColor;
          clickedBtn.style.borderColor = CONFIG.optionColors.wrongBackground;
        }
        const correctBtn = buttons[q.correctIndex];
        if (correctBtn) {
          correctBtn.style.background = CONFIG.optionColors.correctBackground;
          correctBtn.style.color = CONFIG.optionColors.feedbackTextColor;
          correctBtn.style.borderColor = CONFIG.optionColors.correctBackground;
        }

        setTimeout(() => {
          currentQ++;
          if (currentQ < questions.length) {
            renderQuestion();
          } else {
            showResult();
          }
        }, (CONFIG.feedbackSeconds || 0.9) * 1000);
      }

      // -------------------- Result screen --------------------
      function showResult () {
        const passed = score >= CONFIG.passScore;
        const R = passed ? CONFIG.resultPass : CONFIG.resultFail;

        resultTitleEl.textContent = R.title;
        resultSubtitleEl.textContent = R.subtitle;
        scoreLineEl.textContent = 'Your score: ' + score + ' / ' + questions.length;

        const showReward = passed ? true : (CONFIG.resultFail.showReward !== false);

        if (showReward && R.rewardValue) {
          currentRewardValue = R.rewardValue;
          rewardTextEl.textContent = R.rewardValue;
          rewardSubtextEl.textContent = R.rewardSubtext || '';
          rewardWrapper.classList.add('visible');
          rewardWrapper.classList.add('shimmer-active');
          rewardBox.classList.add('pulsate-animation');
        }

        quizModal.style.display = 'none';
        resultModal.style.display = 'flex';

        // Claim button - fires a tracked Netcore In-App click
        if (CONFIG.claimButton.enabled && showReward) {
          claimButton.textContent = CONFIG.claimButton.text;
          claimButton.style.background = CONFIG.claimButton.background;
          claimButton.style.color = CONFIG.claimButton.color;
          claimButton.addEventListener('click', () => {
            netcoreTrackClick(CONFIG.claimButton.url, CONFIG.claimButton.payload);
          }, { once: true });
        } else {
          claimButton.style.display = 'none';
        }

        if (R.showConfetti) fireConfetti();
      }

      // -------------------- Copy to clipboard --------------------
      function copyToClipboard () {
        const text = currentRewardValue;
        if (!text) return;
        if (navigator.clipboard && navigator.clipboard.writeText) {
          navigator.clipboard.writeText(text).then(showCopySuccess).catch(() => fallbackCopy(text));
        } else {
          fallbackCopy(text);
        }
      }

      copyBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        copyToClipboard();
      });

      rewardWrapper.addEventListener('click', (e) => {
        if (e && e.target === copyBtn || (e.target.closest && e.target.closest('#copyBtn'))) return;
        copyToClipboard();
      });

      function showCopySuccess () {
        copyInstructionEl.textContent = "Code copied!";
        copyInstructionEl.style.color = CONFIG.copyInstruction.copiedColor;
      }

      function fallbackCopy (text) {
        const input = document.createElement('input');
        input.setAttribute('readonly', '');
        input.setAttribute('value', text);
        input.style.position = 'absolute';
        input.style.left = '-9999px';
        document.body.appendChild(input);
        input.select();
        try { document.execCommand('copy'); } catch (e) {}
        document.body.removeChild(input);
        showCopySuccess();
      }

      // -------------------- Confetti (self-contained, no library) ----
      function fireConfetti () {
        const colors = ["#FFC700", "#FF61E6", "#D6246E", "#41F4BC", "#943DFF", "#FFD724"];
        const particleCount = 120;
        const gravity = 0.3;
        const confettiCanvas = document.createElement('canvas');
        confettiCanvas.style.position = 'fixed';
        confettiCanvas.style.top = '0';
        confettiCanvas.style.left = '0';
        confettiCanvas.style.width = '100%';
        confettiCanvas.style.height = '100%';
        confettiCanvas.style.pointerEvents = 'none';
        confettiCanvas.style.zIndex = '9999';
        document.body.appendChild(confettiCanvas);

        const confCtx = confettiCanvas.getContext('2d');
        confettiCanvas.width = window.innerWidth;
        confettiCanvas.height = window.innerHeight;
        const particles = [];
        const randomRange = (min, max) => Math.random() * (max - min) + min;

        for (let i = 0; i < particleCount; i++) {
          particles.push({
            x: confettiCanvas.width / 2,
            y: confettiCanvas.height * 0.6,
            w: randomRange(6, 12),
            h: randomRange(8, 16),
            angle: randomRange(0, 360),
            tiltAngleSpeed: randomRange(0.05, 0.12),
            color: colors[Math.floor(Math.random() * colors.length)],
            velocityX: randomRange(-9, 9),
            velocityY: randomRange(-16, -8),
            opacity: 1,
            decay: randomRange(0.0075, 0.015),
          });
        }

        const update = () => {
          confCtx.clearRect(0, 0, confettiCanvas.width, confettiCanvas.height);
          particles.forEach((p, idx) => {
            p.x += p.velocityX;
            p.y += p.velocityY;
            p.velocityY += gravity;
            p.opacity -= p.decay;
            p.angle += p.tiltAngleSpeed * 180 / Math.PI;
            if (p.opacity <= 0 || p.y > confettiCanvas.height + 20) particles.splice(idx, 1);
          });
          draw();
          if (particles.length) requestAnimationFrame(update);
          else confettiCanvas.remove();
        };

        const draw = () => {
          particles.forEach((p) => {
            confCtx.save();
            confCtx.globalAlpha = Math.max(p.opacity, 0);
            confCtx.fillStyle = p.color;
            confCtx.translate(p.x, p.y);
            confCtx.rotate(p.angle * Math.PI / 180);
            confCtx.fillRect(-p.w / 2, -p.h / 2, p.w, p.h);
            confCtx.restore();
          });
        };

        update();
      }

      // -------------------- Kick-off --------------------
      renderQuestion();
    })();
  </script>
</body>

</html>

Editable and Non Editable Section in Custom HTML Code

The template is split into two zones. Everything you are meant to change sits in the first one.

📁 Structure of the Template
<script>
const CONFIG = { ... }
</script>
✓ Editable section
Update your questions, answers, pass score, rewards, colors, and deeplinks here.
<script src="jquery..."></script>
<script src="smartech-app-sdk..."></script>
✕ Do not edit
Loads the required libraries and Netcore SDK.
<style> ... </style>
✕ Do not edit
Defines the widget layout, progress dots, and styling.
<body> ... </body>
✕ Do not edit
Contains the card shell. Questions and options are rendered by the engine.
<script> ...engine... </script>
✕ Do not edit
Controls question flow, scoring, answer feedback, and SDK interactions.
📘

Important Point to Remember

Remember Changing Values Is Safe, Changing Key Names Is Not!

  • Inside the CONFIG block, edit the values on the right of each colon. Do not rename the settings themselves. Changing passScore: 2 to passScore: 3 is fine. Renaming it to passingScore: 3 means the template can no longer find it.
  • Keep the punctuation intact too. A missing comma or quotation mark stops the whole message from rendering.
  • The code below the CONFIG block is what runs the quiz, scores it, and reports back to Netcore, which is why DO NOT EDIT it.

Components You Must Not Remove

Three parts of the locked zone are worth knowing by name, because removing them is the most common way a campaign breaks. Each one connects the template to the Netcore Smartech SDK.

ComponentWhy it's required
SDK wiring block (<head>)Loads jQuery and the Netcore Smartech App SDK in the required order.
data-smt-action="SMTInAppClose"Allows the SDK to close the message and record the dismissal event.
netcoreTrackClick()Tracks the CTA click and opens the configured deeplink.

If any of these are missing, the message can still render in the preview pane while failing to close, failing to report, or failing to open your deeplink on a real device.

Update Custom HTML Code for MCQ Quiz

You can customize the following six sections to configure the MCQ Quiz widget for your In-app message campaign. All of them live inside the CONFIG block.

Layout: Configure the overall appearance and behavior of the widget.
SettingDescriptionExample
includeDismissButtonShow or hide the close (×) button.true
backgroundBackground color or CSS gradient. Applies to both screens."linear-gradient(180deg, #FFE3F1 0%, #FFD1E8 100%)"
passScoreNumber of correct answers needed to receive the pass reward.2
feedbackSecondsHow long the green and red feedback stays before the next question.0.9
Questions: Each object in the questions array is one question in the quiz.
FieldDescription
questionThe question text.
optionsThe answer choices. Two to four work best.
correctIndexWhich option is correct, counting from zero.
🚧

correctIndex Starts at Zero, Not One

The first option is 0, the second is 1, the third is 2, and the fourth is 3. Setting correctIndex: 1 marks the second answer correct, not the first. This is the most common way to misconfigure the quiz, and the mistake is invisible until someone plays it. Check every question before publishing.

📘

Making Every Answer Correct

If you are using the quiz to collect preferences rather than test knowledge, set passScore to 0. Everyone receives the pass reward regardless of what they pick, and nobody feels they lost.

Answer Buttons: Customize the answer options and their feedback colors.
SettingDescriptionExample
optionColors.backgroundAnswer button background in its resting state."#ffffff"
optionColors.colorAnswer button text color. Also sets the question text color."#3A0B24"
optionColors.borderColorAnswer button border color."#F3B4D2"
optionColors.correctBackgroundColor flashed on the correct answer after any pick."#2FBF71"
optionColors.wrongBackgroundColor flashed on the user's answer when it is wrong."#E5484D"
optionColors.feedbackTextColorText color used on both feedback states."#ffffff"
📘

The Correct Answer Is Always Revealed

After any pick, the correct option turns green whether or not the user chose it, and a wrong pick also turns red. Users always learn the right answer, which works well for product education but means you cannot collect answers without revealing them.

Text & Typography: Customize the content shown on the quiz screen.

Quiz Screen

SettingDescription
mainTitle.textMain heading. Supports basic HTML like <br>.
mainTitle.fontSizeHeading size.
mainTitle.colorHeading color. Also applied to the result heading and the score line.
subtitle.textSupporting text below the heading.
subtitle.fontSizeSubtitle size.
subtitle.colorSubtitle color. Also applied to the result subtitle.

Reward Ticket

SettingDescription
copyInstruction.colorInstruction text color before copying.
copyInstruction.copiedColorColor shown after the coupon is copied.
Result Screens: Configure the two outcomes, passing and not passing.

resultPass is shown when the score reaches passScore. resultFail is shown when it does not.

FieldDescription
titleResult heading.
subtitleSupporting text below the result heading.
rewardValueCoupon or reward code copied to the clipboard.
rewardSubtextSupporting message, such as expiry or terms.
showConfettiShow confetti on this result. Usually enabled for pass, disabled for fail.
showRewardOn resultFail only. Set to false to give no consolation reward at all.

The user's score is always shown as a line reading "Your score: 2 / 3" between the subtitle and the reward.

📘

Consolation Rewards Are a Judgement Call

The default configuration gives a smaller reward on a fail, which keeps the message from feeling punishing. Setting showReward to false hides the reward and the Claim button entirely, so the user sees only their score. That reads as colder, but it protects the value of the pass reward.

Buttons: Customize the primary action shown on the result screen.

Claim Button

SettingDescription
claimButton.enabledShow or hide the Claim button.
claimButton.textButton label.
claimButton.backgroundBackground color.
claimButton.colorText color.
claimButton.urlDeep link opened after the reward is claimed.
claimButton.payloadOptional payload passed to your app. Use null if not required.

The Claim button is hidden automatically when there is no reward to claim, so a failed quiz with showReward set to false shows no CTA.

Scoring Workflow

Unlike the other templates, the outcome is not weighted by chance. The user's correct answers are counted and compared against passScore.

For example, with three questions and passScore set to 2:

Correct answersResult shownReward given
3resultPassPass reward
2resultPassPass reward
1resultFailConsolation reward
0resultFailConsolation reward

Setting passScore higher than the number of questions makes it impossible to pass. Setting it to zero means everybody passes.

Troubleshoot and FAQs

Q. Why is the wrong answer being marked as correct?

A. The correctIndex value is off by one. Remember that the first option is 0, not 1.

Q. Why is every answer being marked wrong?

A. The correctIndex value may be set higher than the number of options available for that question.

Q. Why can nobody pass the quiz?

A. The passScore value may be higher than the total number of questions. Lower it to a value within range.

Q. Why does everybody pass the quiz?

A. The passScore value may be set to 0, which awards the pass reward to everyone. This may be intended if you are collecting preferences.

Q. Why are my answer options cut off or crowded?

A. There may be too many options, or the option text may be too long. Keep to four options and use short labels.

Q. Why does the quiz run off the bottom of the card?

A. There may be too many questions or options for the card height. There is no scrolling inside the message, so reduce the number of questions.

Q. Why doesn't anything happen when I click Claim Reward?

A. Verify that claimButton.url has been replaced with a valid deeplink and that the deeplink is registered in your app.

Q. Why doesn't the message close when I click the close button?

A. The data-smt-action="SMTInAppClose" attribute may have been removed from the close button. Restore the attribute to enable the SDK to close the message correctly.

Q. Why doesn't the reward code copy on some devices?

A. Some app webviews restrict clipboard access. If copying is unavailable, users can still view and manually enter the reward code.

Q. Why doesn't the template render at all?

A. The SDK block may have been modified or reordered, or the Netcore Smartech SDK may not be integrated into your app correctly. Ensure the SDK block remains unchanged and is loaded in the required order.



Did this page help you?