On this page: Learn how to configure reward providers and reward definitions, write reward JSONata expressions, and understand the context used to generate fulfillment payloads.
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.
➡️ Watch how to set up a loyalty reward provider
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, mobile message, 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": [...] }How-to videos video
➡️ Watch how to set up a loyalty reward provider
This section contains structured knowledge intended to support interpretation, retrieval, and question answering related to this topic.
For complete understanding, this information should be combined with the documentation on this page. Neither source is intended to stand alone; the page describes the feature, while this section provides additional context that helps disambiguate terminology, intent, applicability, and constraints.
- TL;DR: This page explains how to configure reward providers and reward definitions for Loyalty Challenges, including the fields involved, the reward context passed to the rewardJsonata expression, and how to write that expression to build the payload posted to a reward provider’s endpoint.
Intents:
- Configure a reward provider (endpoint, authentication, headers) and one or more reward definitions under it.
- Understand the fields available on reward providers and reward definitions, including which are required.
- Write a rewardJsonata expression that maps reward context fields onto the payload a provider expects.
- Use kvpCustom on a challenge to pass campaign-specific or provider-specific metadata without editing the expression.
- Use task accumulator data, such as the most recent item in item_list, inside an expression.
- Distinguish task, milestone, and challenge reward sources and access the milestone object correctly.
- Validate a rewardJsonata expression before publishing and troubleshoot common mistakes.
Glossary:
- Reward Provider (product-specific): represents a single external rewards system; holds the delivery endpoint URL, authentication, and custom HTTP headers.
- Reward Definition (product-specific): describes a distinct reward type or denomination offered by a provider, including its rewardJsonata expression.
- rewardJsonata (product-specific): a JSONata expression configured on a reward definition that is evaluated at reward-issue time and must return the JSON payload to POST to the provider.
- Reward context (product-specific): the single root object passed to rewardJsonata at evaluation time, containing rewardContext, reward, task, milestone, challenge, and timestamp.
- kvpCustom (product-specific): custom key-value pairs configured on a challenge (challenge.kvpCustom) or available as constants on a reward (reward.kvpCustom), used to pass campaign IDs, product names, or provider-specific metadata.
- isDefault (product-specific): marks a reward definition as the sandbox-wide default, used to auto-populate reward details on personalized challenges at publish time.
- item_list (product-specific): the list of all qualifying items applied to a task’s accumulators; item_list[-1] accesses the most recently applied item via JSONata negative indexing.
Guardrails:
nameandurlare required fields on a reward provider;guidis system-assigned and read-only.nameandrewardJsonataare required fields on a reward definition;guidis system-assigned and read-only.- When a reward provider’s
enabledfield is false, reward delivery is suspended for all definitions under that provider. - When a reward definition’s
enabledfield is false, that definition is inactive and will not issue rewards. - Only one reward definition across all providers may be marked as default (
isDefault) at a time; setting a new default clears the previous one. maxRatePerSecondon a reward provider accepts a value from 1 to 5000 (per provider); null means unlimited.rewardContext.rewardValueis always a string; it must be converted with$number()if the provider expects a numeric value.milestoneis null when the reward source is"task"or"challenge"; it is populated (withcountandreward.rewardValue) only whenrewardContext.sourceis"milestone".- rewardJsonata expressions are validated for syntax at publish time; an invalid expression causes the API to return a 422 error with a description of the parse failure.
challenge.kvpCustom.someKeyreturns null if the key was not set on the challenge at authoring time.task.accumulators.item_list[-1]is null if no items were applied before the reward was issued (a non-purchase event).- If a rewardJsonata expression returns an array instead of an object, the provider receives an unexpected payload structure; it must be wrapped in an outer object.
Terminology:
- Canonical name: Reward Provider — holds the endpoint, authentication, and headers for an external rewards system.
- Canonical name: Reward Definition — describes a reward type or denomination and its rewardJsonata expression; belongs to one Reward Provider.
- Do not confuse: “Reward Provider” (the external system’s endpoint, auth, and headers) ≠ “Reward Definition” (a specific reward type/denomination with its own rewardJsonata, nested under a provider).
- Do not confuse: “challenge.kvpCustom” (per-challenge custom metadata) ≠ “reward.kvpCustom” (constants fixed for a given reward type).
- Do not confuse:
rewardContext.rewardValue(the reward value string that triggered issuance) ≠milestone.reward.rewardValue(the reward value specific to a milestone).
FAQ:
- Q: What triggers a reward to be issued? — When a challenge task, milestone, or challenge completes and has a reward value configured, the platform issues a reward by calling the reward provider’s HTTP endpoint with a JSON payload.
- Q: What fields are required to create a reward provider? —
nameandurlare required;guidis system-assigned and read-only. - Q: What fields are required to create a reward definition? —
nameandrewardJsonataare required;guidis system-assigned and read-only. - Q: How many reward definitions can be marked as default? — Only one definition across all providers may be default at a time; setting a new default clears the previous one.
- Q: Why does rewardContext.rewardValue need conversion before use as a number? — Because rewardContext.rewardValue is always a string; use $number() to convert it if the provider expects a numeric value.
- Q: Why would challenge.kvpCustom.someKey return null? — Because the key was not set on the challenge at authoring time; ensure the key is present in kvpCustom on every challenge that uses the definition.
- Q: What happens if an invalid rewardJsonata expression is published? — The API returns a 422 error with a description of the parse failure.
- Q: Where can I test a rewardJsonata expression before publishing? — In the JSONata Exerciser, by pasting the reward context JSON as the input document and the expression to verify the output.
Troubleshooting:
task.accumulators.item_list[-1] is null— Cause: no items were applied before the reward was issued (a non-purchase event). Fix: guard with a conditional or use timestamp from context instead.milestone accessed when source is "task" or "challenge"— Cause: milestone is null in that case. Fix: check rewardContext.source before accessing milestone, or only use milestone in definitions attached to milestone rewards.Expression returns an array instead of an object— Cause: the provider receives an unexpected payload structure. Fix: wrap array-returning expressions in an outer object, for example{ "items": [...] }.challenge.kvpCustom.someKey returns null— Cause: the key was not set on the challenge at authoring time. Fix: ensure the key is present in kvpCustom on every challenge that uses this definition.422 error— Cause: the rewardJsonata expression is invalid at publish time. Fix: the API returns a description of the parse failure; use the JSONata Exerciser to develop and test the expression before publishing.
Examples:
- Simple fixed payload: maps
challenge.profileId,$number(rewardContext.rewardValue), andreward.denominationinto a memberId/points/currency object, producing{"memberId": "ADB-0000030", "points": 50, "currency": "Stars"}. - Using kvpCustom: an expression references
challenge.kvpCustom.campaignIdalongsidechallenge.profileIdandrewardContext.rewardValueto keep the expression reusable across campaigns. - Using task accumulator data: an expression uses
task.accumulators.item_list[-1].transactionIdand.timestampfor audit trails and deduplication on the provider side. - Constructing a text message: an expression concatenates strings with
&, for example"You just earned " & rewardContext.rewardValue & " " & reward.denomination & "!", producing{"text": "You just earned 50 Stars!"}. - Example 1 — Simple points provider: a Reward Definition with rewardJsonata mapping
challenge.profileId,$number(rewardContext.rewardValue), andreward.denominationtomemberId,pointQuantity, anddenomination, producing a payload withpointQuantity: 50. - Example 2 — Provider payload with campaign metadata: a Reward Definition builds an
awardPointsarray usingchallenge.kvpCustom.parentCampaignIdandchallenge.kvpCustom.productName(set on the challenge) plus task accumulator transaction data, so the same definition works across campaigns without editing the expression. - Example 3 — Milestone reward: an expression includes
milestone.countandtask.schedule.currentStreak; whenrewardContext.sourceis"milestone", the milestone object is populated withcountandreward.rewardValue.
Personas:
- admin: Can configure a reward provider with its endpoint URL, authentication, and additional headers, and create reward definitions under it. Can set maxRatePerSecond between 1 and 5000, or leave it null for unlimited. Can mark enabled to false on a provider or definition to suspend or stop reward delivery. The page states that only one reward definition across all providers may be isDefault at a time. Where: “Reward provider & definition fields”.
Onboarding:
- Level: Intermediate
- First minimal step: Paste the reward context JSON as the input document in the JSONata Exerciser and add your rewardJsonata expression.
- Success check: The output matches what your provider expects.