io.tt SDK Docs
GuidesExperiences

Gifting

Implementing Product-Linked Greeting Messages with the io.tt SDK

The Gifting template allows a sender (the gifter) to attach a personal message to a unique product code, and a receiver to reveal that message later by scanning the same code.

The SDK exposes six Gifting methods:

  • getGiftingStatus
  • getGiftingMessageStatus
  • getGiftingMessage
  • createGiftingMessage
  • deleteGiftingMessage
  • signupForGifting

This guide explains how to implement both sides of the journey:

  • Gifter flow – creating and attaching a message to a code
  • Receiver flow – scanning and viewing the attached message

All rules around message expiry (e.g. "expires after 3 months") are configured in the io.tt dashboard.


High-Level Behaviour

When a QR/NFC code configured for Gifting is scanned:

  • If no valid message exists, the user is treated as the gifter and taken through the message creation flow.
  • If a valid message exists, the user is treated as the receiver and sees the "You have a message" experience.
  • If the message is processing (e.g. audio transcription in progress), show an appropriate loading state.

The following sections describe how to use the SDK methods to power this behaviour.


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 Experience Status (getGiftingStatus)

Before showing any content, confirm the experience is currently active. If it isn't, route the user to an appropriate ended or not-yet-started screen rather than displaying the gifting journey.

const status = await sdk.getGiftingStatus(EXPERIENCE_ID);

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

The full GiftingExperienceStatus object includes:

FieldTypeDescription
isActivebooleanWhether the experience is currently active
hasEndedbooleanWhether the experience has ended
startDateDate | nullScheduled start date
endDateDate | nullScheduled end date

3. Check Item Message Status (getGiftingMessageStatus)

Once the experience is confirmed active, check whether this specific item already has a message attached. The result determines which flow to show — gifter (create a message) or receiver (view the existing message).

const msgStatus = await sdk.getGiftingMessageStatus(EXPERIENCE_ID, itemSlug);

The GiftingMessageStatus object includes:

FieldTypeDescription
validboolean | undefinedWhether a valid message exists for this item
processingboolean | undefinedWhether the message is still being processed (e.g. audio transcription)
errorMessagestring | undefinedError detail if something went wrong

Route based on status:

if (msgStatus.processing) {
  return renderProcessingScreen();
}

if (msgStatus.valid) {
  // Receiver flow — fetch and display the message
} else {
  // Gifter flow — build a new message
}

4. Gifter Flow – Creating a Message

The gifter journey in your UI normally includes:

  1. Enter recipient and sender names
  2. Write a personal message (text and/or audio)
  3. Optionally add extra data
  4. Confirm and save

The SDK methods you use:

  • signupForGifting – register the gifter and capture their contact / consent
  • createGiftingMessage – attach the actual message content to the code

4.1 Register the Gifter (signupForGifting)

Register the gifter's contact details and consent before creating the message. Only itemSlug and email are required — all other fields are optional. 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.

await sdk.signupForGifting(
  EXPERIENCE_ID,
  {
    itemSlug,                // required
    email,                   // required
    // Personal details (all optional)
    title,
    firstName,
    lastName,
    gender,                  // 'male' | 'female' | 'other'
    phoneNumber: { countryCode: 44, number: '7700900000' },
    dateOfBirth,             // ISO 8601 string, e.g. "1990-01-15"
    // Address (all optional)
    addressLine1,
    addressLine2,
    locality,
    administrativeDivision,
    postalCode,
    country,
    // Consent (all optional)
    marketingConsent,
    profilingConsent,
    // Custom fields (optional)
    extra: {
      // additional custom fields
    },
  },
);

4.2 Create the Message (createGiftingMessage)

Attach the personal message content to the code. to and from are required — everything else is optional. Pass any audio recordings as the fourth files argument; the SDK handles the presigned URL upload automatically.

await sdk.createGiftingMessage(
  EXPERIENCE_ID,
  itemSlug,
  {
    to: form.toName,         // required
    from: form.fromName,     // required
    occasion: form.occasion, // optional
    text: form.messageBody,  // optional
    extra: {                 // optional
      // additional custom fields
    },
  },
  audioFiles,                // optional: Array<File | Blob | Buffer> for audio upload
);

The GiftingMessage object includes:

FieldTypeDescription
tostringRecipient name — required
fromstringSender name — required
occasionstring | undefinedOccasion label (e.g. "Birthday")
textstring | undefinedWritten message body
audioobject | undefinedAudio message — populated automatically when files are passed
extraRecord<string, unknown> | undefinedAdditional custom fields

On success, show a confirmation screen such as:

"Your message is ready – tell them to scan the QR code."


5. Gifter Flow – Reset / Delete a Message (deleteGiftingMessage)

To remove an existing message so the code can start a new gifting journey, call deleteGiftingMessage. After deletion, the next scan of this item will enter the gifter flow again.

await sdk.deleteGiftingMessage(EXPERIENCE_ID, itemSlug);

6. Receiver Flow – Fetching and Rendering the Message (getGiftingMessage)

When getGiftingMessageStatus returns valid: true, fetch the full message content. Use the returned data to populate the receiver card UI — the to, from, occasion, text, and audio fields are all available.

const message = await sdk.getGiftingMessage(EXPERIENCE_ID, itemSlug);

The returned GiftingMessage shape:

{
  "to": "Sara",
  "from": "Daniel",
  "occasion": "Birthday",
  "text": "Happy Birthday, my dear friend...",
  "audio": {
    "src": "fileId",
    "fileName": "message.webm",
    "transcription": [
      { "start": 0, "end": 2.5, "text": "Happy Birthday" }
    ]
  }
}

After reading, subsequent scans will continue to show the same message until it is deleted or the experience ends.


7. End-to-End Example

export async function handleGiftingEntry(experienceId, itemSlug, form) {
  // 1. Check experience is active
  const status = await sdk.getGiftingStatus(experienceId);
  if (!status.isActive) return renderExperienceEndedScreen();

  // 2. Check item message state
  const msgStatus = await sdk.getGiftingMessageStatus(experienceId, itemSlug);

  if (msgStatus.processing) {
    return renderProcessingScreen();
  }

  if (msgStatus.valid) {
    // Receiver flow
    const message = await sdk.getGiftingMessage(experienceId, itemSlug);
    return renderReceiverCard(message);
  }

  // Gifter flow
  if (!form) return renderGifterBuilder();

  await sdk.signupForGifting(experienceId, {
    itemSlug,
    email: form.email,
    firstName: form.firstName,
    lastName: form.lastName,
    marketingConsent: form.marketingConsent,
  });

  await sdk.createGiftingMessage(
    experienceId,
    itemSlug,
    {
      to: form.toName,
      from: form.fromName,
      occasion: form.occasion,
      text: form.messageBody,
    },
    form.audioFiles,
  );

  return renderGifterSuccess();
}

8. Summary Table

MethodSignatureUsed ByPurpose
getGiftingStatus(experienceId)BothCheck whether the experience is active.
getGiftingMessageStatus(experienceId, itemSlug)BothDetermine whether this item has a valid/processing message.
getGiftingMessage(experienceId, itemSlug)ReceiverRetrieve message content for display.
createGiftingMessage(experienceId, itemSlug, message, files?)GifterAttach or update the personal message on a code.
deleteGiftingMessage(experienceId, itemSlug)GifterRemove the message so the code can start a new gifting journey.
signupForGifting(experienceId, entry)GifterRegister sender details and consent.

Notes

  • Message expiry duration and validation rules are configured on the io.tt dashboard, not in client code.
  • All state (message existence, processing, validity) is enforced server-side. Treat SDK responses as the source of truth and handle UI branching only.

On this page