Gamebus Docs

Custom tasks

Add an external task that runs inside the GameBus task dialog.

Use this for interactive work

A custom task is an iframe that GameBus opens from a userTriggeredEmbedded task. The external page renders the experience, then sends an activity back to GameBus when the participant has done enough work.

Custom embedded tasks are useful when a campaign needs a quiz, game, survey, simulation, or partner tool that cannot be expressed as a standard GameBus task.

Parent

GameBus opens the configured task URL in a full-screen dialog iframe.

Child

Your external page sends and receives postMessage events.

Result

The child posts an activity payload. GameBus validates it, stores it, and updates task progress.

When to use it

Use a custom task when the participant must complete a focused action inside a mission milestone.

Good examples:

  • A quiz that calculates a score.
  • A mini game that returns a result.
  • A partner survey that returns a completion activity.
  • A simulator that records choices as activity properties.
  • A learning module that stores checkpoints with SILENT_ACTIVITY and completion with ACTIVITY.

For full-page campaign tools launched from the menu, use custom pages. For compact widgets rendered directly inside the side menu, use custom menu items.

Communication flow

Message protocol

DirectionTypeMeaning
Child to parentIFRAME_READYThe child has mounted and is ready to receive data. Required before GameBus sends TASK or INPUT_COLLECTIONS.
Parent to childTASKThe full active task object, including task metadata and activity templates available to the task.
Parent to childINPUT_COLLECTIONSData fetched by GameBus from the task's configured input requests.
Child to parentACTIVITYStore an activity, mark the task complete, show success feedback, and close the task dialog.
Child to parentSILENT_ACTIVITYStore an activity without closing the task dialog. Use this for checkpoints or background events.

Origin checks are strict

GameBus only accepts messages from the origin of the configured task.url, and it posts messages back to that same origin.

Configure the GameBus task

Create a task with these core settings:

type: userTriggeredEmbedded
url: https://tasks.example.org/quiz
inputCollections:
  - key: quiz
    inputRequests:
      - key: questions
        endpoint: /api/embed/quiz/questions
        doFailOnError: true
        doFailOnEmpty: true
activityTemplates:
  - reference: quiz-completed
    embeddedActivityProviders:
      - origin: https://tasks.example.org

The exact admin or import format can differ per deployment. The important parts are the task type, iframe URL, optional input collections, and an activity provider that allows the iframe origin to post the selected activity template.

Build the external task page

The child page can be hosted by GameBus or by another trusted domain.


The minimum child behavior is:

- Install a message listener before relying on parent data.
- Send `IFRAME_READY` after the iframe has mounted.
- Store `TASK` and `INPUT_COLLECTIONS` when they arrive.
- Post `ACTIVITY` or `SILENT_ACTIVITY` when the participant acts.

```svelte
<script lang="ts">
  type ParentMessage =
    | { type: 'TASK'; data?: { id?: string; title?: string; activityTemplates?: { reference?: string }[] } }
    | { type: 'INPUT_COLLECTIONS'; data?: Record<string, Record<string, unknown>> };

  let task: ParentMessage['data'] = $state();
  let inputCollections: Record<string, Record<string, unknown>> = $state({});
  let startedAt = new Date();

  $effect(() => {
    window.parent.postMessage({ type: 'IFRAME_READY' }, '*');
  });

  function handleMessage(event: MessageEvent<ParentMessage>) {
    if (!event.data || typeof event.data !== 'object') return;

    if (event.data.type === 'TASK') {
      task = event.data.data;
    }

    if (event.data.type === 'INPUT_COLLECTIONS') {
      inputCollections = event.data.data ?? {};
    }
  }

  function completeQuiz() {
    window.parent.postMessage(
      {
        type: 'ACTIVITY',
        data: {
          template: 'quiz-completed',
          start: startedAt,
          end: new Date(),
          properties: [
            {
              template: 'score',
              obj: { correct: 8, total: 10, percentage: 80 }
            }
          ]
        }
      },
      '*'
    );
  }
</script>

<svelte:window onmessage={handleMessage} />

<main>
  <h1>{task?.title ?? 'Quiz'}</h1>
  <pre>{JSON.stringify(inputCollections, null, 2)}</pre>
  <button type="button" onclick={completeQuiz}>Submit quiz</button>
</main>

Receive task data

After IFRAME_READY, GameBus sends the active task.

type: TASK
data:
  id: task-reference
  title: Embedded quiz
  type: userTriggeredEmbedded
  activityTemplates:
    - reference: quiz-completed
      name: Quiz completed

Use this data for display and template selection. Do not assume every task field is present; the parent sends the full task object available to the widget, and deployments can differ.

Receive input collections

Input collections let GameBus fetch campaign context before the iframe starts. The child receives the successful values grouped by collection key and input request key.

type: INPUT_COLLECTIONS
data:
  participant:
    current:
      name: Ada Lovelace
      team: Analytical Engines
  quiz:
    questions:
      - id: q1
        text: How many points is this worth?
        points: 10

The parent behavior is:

  • JSON responses are parsed as JSON.
  • Non-JSON responses are sent as text.
  • Failed requests are ignored unless doFailOnError is enabled.
  • Empty responses are ignored unless doFailOnEmpty is enabled.
  • If a required input fails, the task iframe is not shown and GameBus shows an error state.

Validate the shape you need before using input data for scoring or important decisions.

Send activity data

Use ACTIVITY for final completion.

window.parent.postMessage(
  {
    type: "ACTIVITY",
    data: {
      template: "quiz-completed",
      start: quizStartedAt,
      end: new Date(),
      properties: [
        {
          template: "score",
          obj: {
            correct: 8,
            total: 10,
            percentage: 80,
          },
        },
      ],
    },
  },
  "*",
);

Use SILENT_ACTIVITY for intermediate checkpoints.

window.parent.postMessage(
  {
    type: "SILENT_ACTIVITY",
    data: {
      template: "quiz-checkpoint",
      start: checkpointStartedAt,
      end: new Date(),
      properties: [
        {
          template: "step",
          obj: { value: "question-3-opened" },
        },
      ],
    },
  },
  "*",
);

GameBus adds the taskOfMission property automatically. Do not include it in the child payload.

What GameBus validates

When an activity message arrives, the task widget:

  • Rejects messages from origins other than new URL(task.url).origin.
  • Resolves an embedded activity provider for the task, activity template, and iframe origin.
  • Adds taskOfMission with the active task id.
  • Validates the payload with the activity create schema.
  • Posts the activity to /api/me/activities.
  • Calls the task completion callback.
  • Closes the dialog only for ACTIVITY, not for SILENT_ACTIVITY.

Security checklist

  • Prefer a specific target origin instead of '*' in production child pages when you know the GameBus origin.
  • Ignore messages whose event.origin is not your expected GameBus origin.
  • Handle only known event.data.type values.
  • Validate INPUT_COLLECTIONS before using nested fields.
  • Send only activity templates that are configured for the task and allowed for the iframe origin.
function handleMessage(event: MessageEvent) {
  if (event.origin !== "https://app.example.org") return;
  if (!event.data || typeof event.data !== "object") return;

  switch (event.data.type) {
    case "TASK":
    case "INPUT_COLLECTIONS":
      break;
    default:
      return;
  }
}

Troubleshooting

ProblemCheck
The iframe never receives task dataMake sure the child sends IFRAME_READY after mount.
The iframe is not shownCheck required input requests with doFailOnError or doFailOnEmpty.
Activity is rejectedCheck that the activity template is allowed for the task and iframe origin.
The task does not closeUse ACTIVITY; SILENT_ACTIVITY intentionally keeps the dialog open.
Input data is missingCheck collection keys, request keys, endpoint responses, and response content type.

Summary

To add a custom embedded task, configure a userTriggeredEmbedded task URL, send IFRAME_READY from the child, read TASK and INPUT_COLLECTIONS, then post an allowed ACTIVITY payload when the participant completes the work.

On this page