Custom tasks
Add an external task that runs inside the GameBus task dialog.
Use this for interactive work
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
Child
postMessage events.Result
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_ACTIVITYand completion withACTIVITY.
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
| Direction | Type | Meaning |
|---|---|---|
| Child to parent | IFRAME_READY | The child has mounted and is ready to receive data. Required before GameBus sends TASK or INPUT_COLLECTIONS. |
| Parent to child | TASK | The full active task object, including task metadata and activity templates available to the task. |
| Parent to child | INPUT_COLLECTIONS | Data fetched by GameBus from the task's configured input requests. |
| Child to parent | ACTIVITY | Store an activity, mark the task complete, show success feedback, and close the task dialog. |
| Child to parent | SILENT_ACTIVITY | Store an activity without closing the task dialog. Use this for checkpoints or background events. |
Origin checks are strict
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.orgThe 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 completedUse 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: 10The parent behavior is:
- JSON responses are parsed as JSON.
- Non-JSON responses are sent as text.
- Failed requests are ignored unless
doFailOnErroris enabled. - Empty responses are ignored unless
doFailOnEmptyis 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
taskOfMissionwith 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 forSILENT_ACTIVITY.
Security checklist
- Prefer a specific target origin instead of
'*'in production child pages when you know the GameBus origin. - Ignore messages whose
event.originis not your expected GameBus origin. - Handle only known
event.data.typevalues. - Validate
INPUT_COLLECTIONSbefore 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
| Problem | Check |
|---|---|
| The iframe never receives task data | Make sure the child sends IFRAME_READY after mount. |
| The iframe is not shown | Check required input requests with doFailOnError or doFailOnEmpty. |
| Activity is rejected | Check that the activity template is allowed for the task and iframe origin. |
| The task does not close | Use ACTIVITY; SILENT_ACTIVITY intentionally keeps the dialog open. |
| Input data is missing | Check 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.