Table of contents
When a challenge task, milestone, or challenge completes and has a reward value configured, the platform issues a reward by calling your reward provider’s HTTP endpoint with a JSON payload. A Reward Definition describes what reward to issue and provides a JSONata expression — rewardJsonata — that shapes the exact payload your provider expects.
This guide covers how to configure a reward provider, create reward definitions, write the rewardJsonata expression, and understand what context is available to it at evaluation time.
Two-level model
Rewards are organized in two levels:
Reward Provider (endpoint, auth, headers)
└── Reward Definition (denomination, rewardJsonata)
└── Reward Definition
└── ...
A Reward Provider represents a single external rewards system — it holds the delivery endpoint URL, authentication, and any custom HTTP headers. One provider can hold multiple Reward Definitions, each describing a distinct reward type or denomination offered by that provider (e.g. “50 Stars”, “Double Stars”, “Free Item”).
A challenge references the provider and definition by GUID. When a reward is issued, the platform evaluates the definition’s rewardJsonata expression and POSTs the result to the provider’s endpoint.
Reward provider & definition fields
| table 0-row-4 1-row-4 2-row-4 3-row-4 4-row-4 5-row-4 6-row-4 7-row-4 8-row-4 html-authored | |||
|---|---|---|---|
| Field | Type | Required | Description |
guid |
String |
No (system-assigned) | Unique identifier. Read-only. |
name |
String |
Yes | Display name, unique within the org. |
desc |
String |
No | Human-readable description of the provider. |
enabled |
Boolean |
No | When false, reward delivery issuspended for all definitions under this provider. |
url |
String |
Yes | HTTP endpoint that receives the reward payload. The platform POSTs the evaluated rewardJsonata output to this URL. |
additionalHeaders |
Object |
No | Custom HTTP headers to include in every delivery request (e.g. API keys, content-type overrides). |
maxRatePerSecond |
Integer |
No | Optional per-provider rate limit (1–5000). Null means unlimited. |
enableMTLS |
Boolean |
No | Whether the endpoint requires mutual TLS. |
| table 0-row-4 1-row-4 2-row-4 3-row-4 4-row-4 5-row-4 6-row-4 7-row-4 html-authored | |||
|---|---|---|---|
| Field | Type | Required | Description |
guid |
String |
No (system-assigned) | Unique identifier. Read-only. |
name |
String |
Yes | Display name, unique within the provider. |
denomination |
String |
No | The unit of the reward, used in display and available in expressions as reward.denomination(e.g. "Stars", "Points", "Miles"). |
desc |
String |
No | Description of the reward, available in expressions as reward.desc. |
enabled |
Boolean |
No | When false, this definition is inactiveand will not issue rewards. |
isDefault |
Boolean |
No | Marks this as the sandbox-wide default reward definition. Only one definition across all providers may be default at a time; setting a new default clears the previous one. Used to auto-populate reward details on personalized challenges at publish time. |
rewardJsonata |
String |
Yes | JSONata expression evaluated at reward-issue time. Receives the full reward context and must return the JSON payload to POST to the provider. |
The reward context
When rewardJsonata is evaluated, it receives a single root object containing everything known about the reward event. All paths in your expression are relative to this root.
{
"rewardContext": {
"rewardValue": "50",
"source": "challenge"
},
"reward": {
"name": "500 Stars",
"desc": "Issue 500 Stars to the member",
"denomination": "Stars",
"enabled": true
},
"task": { ... },
"milestone": { ... },
"challenge": { ... },
"timestamp": "2026-02-10T00:29:22.538+00:00"
}
| table 0-row-2 1-row-2 2-row-2 3-row-2 4-row-2 5-row-2 6-row-2 7-row-2 8-row-2 9-row-2 10-row-2 11-row-2 12-row-2 13-row-2 14-row-2 15-row-2 16-row-2 | |
|---|---|
| Field | Description |
rewardContext.rewardValue |
The reward value string configured on the challenge, task, or milestone that triggered this issuance. |
rewardContext.source |
What triggered the reward: "task", "challenge", or "milestone". |
reward |
The RewardDefinition itself — name, desc, denomination. |
task |
The completing task, including its accumulators, schedule, and reward. |
task.accumulators.spend |
Total qualifying spend accumulated by the task. |
task.accumulators.qty |
Total qualifying item count accumulated by the task. |
task.accumulators.item_list |
All qualifying items applied to the task. Each entry has item, transactionId, timestamp, utcOffset, locationId. |
task.accumulators.item_list[-1] |
The most recent item applied (JSONata negative index). Useful for sourcing the last transaction ID or timestamp. |
task.schedule.currentStreak |
Current consecutive-visit streak count (for streak challenges). |
task.schedule.currentVisits |
Total visit count (for visit challenges). |
milestone |
The milestone that triggered this reward, or null if not a milestone reward. Includes count and reward.rewardValue. |
challenge.profileId |
The member’s loyalty ID. |
challenge.kvpCustom |
Custom key-value pairs configured on the challenge. A common pattern for passing campaign IDs, product names, or provider-specific metadata. |
challenge.name |
Challenge name. |
challenge._id |
Challenge ID. |
timestamp |
ISO 8601 timestamp of the reward issuance. |
Writing the rewardJsonata expression
The expression receives the reward context as its input and must return a JSON object — the payload POSTed to the provider’s endpoint. The shape of that object is entirely up to the provider’s API; you map context fields onto whatever structure the provider expects.
The simplest case: the provider needs a point count and a member ID, both known from the context.
| code language-jsonata |
|---|
|
Output:
| code language-json |
|---|
|
rewardContext.rewardValueis always a string. Use$number()to convert it if your provider expects a numeric value.
kvpCustom for provider-specific metadataProviders often require fields like campaign IDs or source system codes that are specific to each challenge run. Store these in challenge.kvpCustom when authoring the challenge, then reference them in the expression — keeping the expression reusable across campaigns.
| code language-jsonata |
|---|
|
You can also use reward.kvpCustom for constants that are fixed for a given reward type rather than per-challenge.
Task accumulators hold a record of every qualifying event. Use item_list[-1] to access the most recently applied item — its transactionId and timestamp are useful for audit trails and deduplication on the provider side.
| code language-jsonata |
|---|
|
For notification-based providers (Slack, SMS, email), you can build a message string directly using JSONata’s & concatenation operator:
| code language-jsonata |
|---|
|
Output:
| code language-json |
|---|
|
Examples
Scenario: A basic loyalty points API expects a member ID and a point amount.
Reward Definition:
| code language-json |
|---|
|
Formatted expression:
| code language-jsonata |
|---|
|
Payload POSTed to provider:
| code language-json |
|---|
|
Scenario: The provider requires a structured award record that includes audit fields, campaign references, and member description. Campaign-specific values are stored in challenge.kvpCustom so the same reward definition works across campaigns without editing the expression.
Challenge kvpCustom (set when authoring the challenge):
| code language-json |
|---|
|
Reward Definition:
| code language-json |
|---|
|
Formatted expression:
| code language-jsonata |
|---|
|
Payload POSTed to provider:
| code language-json |
|---|
|
Scenario: A streak challenge issues a milestone reward every N visits. The expression includes the milestone count and the current streak for provider-side context.
Formatted expression:
| code language-jsonata |
|---|
|
Payload POSTed to provider (at 2nd visit milestone):
| code language-json |
|---|
|
When
rewardContext.sourceis"milestone", themilestoneobject is populated withcountandreward.rewardValue. When the source is"task"or"challenge",milestoneisnull.
API reference
| code language-http |
|---|
|
All requests require x-gw-ims-org-id and x-sandbox-name headers.
Create a provider:
| code language-http |
|---|
|
| code language-http |
|---|
|
Create a reward definition:
| code language-http |
|---|
|
Expression validation
rewardJsonata expressions are validated for syntax at publish time. If the expression is invalid, the API returns a 422 error with a description of the parse failure.
To develop and test an expression before publishing, use the JSONata Exerciser. Paste the reward context JSON as the input document and your expression to verify the output matches what your provider expects. A representative reward context for each trigger type (task, milestone, challenge) is shown in the examples above.
Common mistakes
rewardContext.rewardValue used as a number without conversion$number(rewardContext.rewardValue)challenge.kvpCustom.someKey returns nullkvpCustom on every challenge that uses this definitiontask.accumulators.item_list[-1] is nulltimestamp from context insteadmilestone accessed when source is "task" or "challenge"milestone is null; expression throws or produces null fieldsrewardContext.source before accessing milestone, or only use milestone in definitions attached to milestone rewards{ "items": [...] }