io.tt SDK Docs
GuidesExperiences

Prize Draw

The Prize Draw template lets consumers enter a competition by scanning a QR/NFC code on a product. The server enforces all competition rules — availability window, duplicate submissions, per-scan uniqueness, and entry limits — so your frontend only needs to handle the UI states the SDK returns.

Overview

A typical Prize Draw experience follows this sequence:

  1. User scans the product → your app receives an itemSlug from the URL query string
  2. Call getCompetitionStatus to confirm the competition is open
  3. Collect the user's email and call checkCompetitionEntry to verify they haven't already entered
  4. If eligible, display the signup form
  5. On submit, call signupForCompetition
  6. Show a confirmation state
  7. Handle closed, duplicate, or invalid states as needed

Step-by-Step Implementation

1. Initialise the SDK

Create a single SDK instance using your Experience API key. If you haven't set this up yet, see Getting Started.

import { IOTT } from "@io-tt/sdk";

const sdk = new IOTT({
  apiKey: process.env.IOTT_API_KEY,
});

2. Check Competition Status

Before showing any UI, confirm the competition is currently open. This allows you to route the user to an appropriate closed or not-yet-started screen rather than displaying a form against an inactive experience. The server enforces the availability window, but checking status first avoids unnecessary API calls and gives you the startDate / endDate values needed to render helpful messaging.

const status = await sdk.getCompetitionStatus(EXPERIENCE_ID);

if (!status.isActive) {
  renderCompetitionEnded();
  return;
}

The full CompetitionStatus object includes:

FieldTypeDescription
isActivebooleanWhether the competition is currently active
hasEndedbooleanWhether the competition has ended
startDateDate | nullScheduled start date
endDateDate | nullScheduled end date
allowMultipleEntriesbooleanWhether a user may enter more than once
enforceUniqueScanboolean | undefinedWhether each physical scan must be unique

3. Check if the User Has Already Entered

Before showing the form, check whether this user has already submitted an entry. This prevents showing a form the server will reject and lets you display a more appropriate "already entered" state instead.

uid is the unique identifier you use to track this user — typically their email address, but it can be any consistent string.

const entry = await sdk.checkCompetitionEntry(EXPERIENCE_ID, uid);
// Optionally pass itemSlug as a third argument to scope the check to a specific product scan
// const entry = await sdk.checkCompetitionEntry(EXPERIENCE_ID, uid, itemSlug);

if (entry.hasEntered) {
  renderAlreadyEnteredState();
  return;
}

4. Submit a Competition Entry

Once the user is confirmed eligible, collect their details and submit the entry. Only uid is required — all other fields are optional and should only be collected if your experience form asks for them.

await sdk.signupForCompetition(
  EXPERIENCE_ID,
  {
    uid,                     // required — you provide this value, typically the user's email.
                             // io.tt uses it to detect whether this person has already entered.
    email,                   // if using email as the uid, pass the same value here too
    title,                   // must match Salesforce picklist exactly if using Salesforce: Mr. Ms. Mrs. Dr. Prof.
    firstName,
    lastName,
    gender,                  // 'male' | 'female' | 'other'
    phoneNumber: { countryCode: 44, number: '7700900000' },
    dateOfBirth,             // ISO 8601 string, e.g. "1990-01-15"
    addressLine1,
    addressLine2,
    locality,
    administrativeDivision,
    postalCode,
    country,
    marketingConsent,
    profilingConsent,
    // Proof of purchase (optional — see note below)
    proofOfPurchaseData: [
      {
        type: 'image',       // free-form string identifying the field type
        label: 'Receipt',
        value: imageBlob,    // Blob for images
      },
      {
        type: 'text',
        label: 'Barcode',
        value: '123456789',  // string for text values
        itemId: 'abc',       // optional — links to a specific item
      },
    ],
    // Custom fields (optional)
    extra: {
      customProperty: formData.customField1,
    },
  },
  images,                    // optional: Array<File | Blob | Buffer>
);

Registration data is automatically routed to your configured CRM. See the CRM Integration Guide for details on field mapping and how extra fields are handled.

Proof of Purchase

proofOfPurchaseData is stored separately in io.tt and linked to the participant record. It is not forwarded to native CRM integrations (Salesforce or Klaviyo) — it remains within io.tt's infrastructure where it can be reviewed in the dashboard and used for fraud prevention and prize fulfilment workflows. If you are using a custom webhook integration, the full proofOfPurchaseData array is included in the webhook payload for downstream processing.


5. Render the Confirmation State

After a successful submission, show the user appropriate confirmation. All competition rules (entry limits, duplicate checks) are enforced server-side — a successful response from signupForCompetition means the entry has been accepted.

renderSuccessConfirmation();

Your UI may include:

  • A success message and entry confirmation
  • Draw date and how winners will be notified
  • Links to Terms & Conditions and Privacy Policy

Summary

MethodSignaturePurpose
getCompetitionStatusgetCompetitionStatus(experienceId: string): Promise<CompetitionStatus>Confirms the competition is open and returns availability window and entry rule flags
checkCompetitionEntrycheckCompetitionEntry(experienceId: string, uid: string, itemSlug?: string): Promise<CompetitionEntryStatus>Returns whether this user has already entered, optionally scoped to a specific product scan
signupForCompetitionsignupForCompetition(id: string, entry: Omit<CompetitionEntry, 'images'>, images?: Array<File | Blob | Buffer>): Promise<void>Submits the entry and validates all server-side rules

On this page