Reward definition guide reward-definition-guide

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

Reward provider 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 is
suspended 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.
Reward 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 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 inactive
and 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"
}
Context fields
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.

Simple fixed payload

The simplest case: the provider needs a point count and a member ID, both known from the context.

code language-jsonata
{
  "memberId":   challenge.profileId,
  "points":     $number(rewardContext.rewardValue),
  "currency":   reward.denomination
}

Output:

code language-json
{
  "memberId": "ADB-0000030",
  "points":   50,
  "currency": "Stars"
}

rewardContext.rewardValue is always a string. Use $number() to convert it if your provider expects a numeric value.

Using kvpCustom for provider-specific metadata

Providers 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
{
  "memberId":         challenge.profileId,
  "points":           $number(rewardContext.rewardValue),
  "campaignId":       challenge.kvpCustom.campaignId,
  "transactionSource": "AJO"
}

You can also use reward.kvpCustom for constants that are fixed for a given reward type rather than per-challenge.

Using task accumulator data

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
{
  "memberId":       challenge.profileId,
  "points":         $number(rewardContext.rewardValue),
  "transactionId":  task.accumulators.item_list[-1].transactionId,
  "transactionDate": task.accumulators.item_list[-1].timestamp
}
Constructing a text message

For notification-based providers (Slack, mobile message, email), you can build a message string directly using JSONata’s & concatenation operator:

code language-jsonata
{
  "text": "You just earned " & rewardContext.rewardValue & " " & reward.denomination & "!"
}

Output:

code language-json
{
  "text": "You just earned 50 Stars!"
}

Examples

Example 1 — Simple points provider

Scenario: A basic loyalty points API expects a member ID and a point amount.

Reward Definition:

code language-json
{
  "name":         "Standard Points",
  "denomination": "Points",
  "desc":         "Award loyalty points",
  "enabled":      true,
  "rewardJsonata": "{\"memberId\": challenge.profileId, \"pointQuantity\": $number(rewardContext.rewardValue), \"denomination\": reward.denomination}"
}

Formatted expression:

code language-jsonata
{
  "memberId":      challenge.profileId,
  "pointQuantity": $number(rewardContext.rewardValue),
  "denomination":  reward.denomination
}

Payload POSTed to provider:

code language-json
{
  "memberId":      "ADB-0000030",
  "pointQuantity": 50,
  "denomination":  "Points"
}
Example 2 — Provider payload with campaign metadata

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
{
  "parentCampaignId": "CAMP-2026-Q1",
  "productName":      "Loyalty Program"
}

Reward Definition:

code language-json
{
  "name":         "Stars — Campaign Award",
  "denomination": "Stars",
  "desc":         "Issue Stars for completing a qualifying purchase",
  "enabled":      true,
  "rewardJsonata": "{\"awardPoints\":[{\"idType\":\"externalId\",\"id\":challenge.profileId,\"transactionId\":task.accumulators.item_list[-1].transactionId,\"transactionDate\":task.accumulators.item_list[-1].timestamp,\"originalTransactionId\":task.accumulators.item_list[-1].transactionId,\"transactionSource\":\"AJO\",\"channelSource\":\"Web\",\"parentCampaignId\":challenge.kvpCustom.parentCampaignId,\"productName\":challenge.kvpCustom.productName,\"memberAwardDescription\":reward.desc,\"pointQuantity\":$number(rewardContext.rewardValue)}]}"
}

Formatted expression:

code language-jsonata
{
  "awardPoints": [
    {
      "idType":                "externalId",
      "id":                    challenge.profileId,
      "transactionId":         task.accumulators.item_list[-1].transactionId,
      "transactionDate":       task.accumulators.item_list[-1].timestamp,
      "originalTransactionId": task.accumulators.item_list[-1].transactionId,
      "transactionSource":     "AJO",
      "channelSource":         "Web",
      "parentCampaignId":      challenge.kvpCustom.parentCampaignId,
      "productName":           challenge.kvpCustom.productName,
      "memberAwardDescription": reward.desc,
      "pointQuantity":         $number(rewardContext.rewardValue)
    }
  ]
}

Payload POSTed to provider:

code language-json
{
  "awardPoints": [
    {
      "idType":                "externalId",
      "id":                    "ADB-0000030",
      "transactionId":         "b4fa0e89-f4bb-41ce-b370-fb97f9c52f1a",
      "transactionDate":       "2026-02-08T00:12:00.000+00:00",
      "originalTransactionId": "b4fa0e89-f4bb-41ce-b370-fb97f9c52f1a",
      "transactionSource":     "AJO",
      "channelSource":         "Web",
      "parentCampaignId":      "CAMP-2026-Q1",
      "productName":           "Loyalty Program",
      "memberAwardDescription": "Issue Stars for completing a qualifying purchase",
      "pointQuantity":         50
    }
  ]
}
Example 3 — Milestone reward

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
{
  "memberId":       challenge.profileId,
  "points":         $number(rewardContext.rewardValue),
  "milestoneCount": milestone.count,
  "currentStreak":  task.schedule.currentStreak,
  "denomination":   reward.denomination,
  "source":         rewardContext.source
}

Payload POSTed to provider (at 2nd visit milestone):

code language-json
{
  "memberId":       "ADB-0000030",
  "points":         20,
  "milestoneCount": 2,
  "currentStreak":  2,
  "denomination":   "Stars",
  "source":         "milestone"
}

When rewardContext.source is "milestone", the milestone object is populated with count and reward.rewardValue. When the source is "task" or "challenge", milestone is null.

API reference

Reward providers
code language-http
POST   /loyalty/metadata/config/rewards/providers
GET    /loyalty/metadata/config/rewards/providers
GET    /loyalty/metadata/config/rewards/providers/{providerId}
PUT    /loyalty/metadata/config/rewards/providers/{providerId}
DELETE /loyalty/metadata/config/rewards/providers/{providerId}

All requests require x-gw-ims-org-id and x-sandbox-name headers.

Create a provider:

code language-http
POST /loyalty/metadata/config/rewards/providers
x-gw-ims-org-id: {ORG_ID}
x-sandbox-name: {SANDBOX}
Content-Type: application/json

{
  "name":    "My Points Provider",
  "desc":    "Issues loyalty points via REST",
  "enabled": true,
  "url":     "https://rewards.example.com/award",
  "additionalHeaders": {
    "x-api-key": "YOUR_API_KEY"
  }
}
Reward definitions
code language-http
POST   /loyalty/metadata/config/rewards/definitions/{providerId}
GET    /loyalty/metadata/config/rewards/definitions/{providerId}
GET    /loyalty/metadata/config/rewards/definitions/{providerId}/{rewardId}
PUT    /loyalty/metadata/config/rewards/definitions/{providerId}/{rewardId}
DELETE /loyalty/metadata/config/rewards/definitions/{providerId}/{rewardId}

Create a reward definition:

code language-http
POST /loyalty/metadata/config/rewards/definitions/{providerId}
x-gw-ims-org-id: {ORG_ID}
x-sandbox-name: {SANDBOX}
Content-Type: application/json

{
  "name":         "50 Stars",
  "denomination": "Stars",
  "desc":         "Award 50 Stars on task completion",
  "enabled":      true,
  "rewardJsonata": "{ \"memberId\": challenge.profileId, \"points\": $number(rewardContext.rewardValue) }"
}

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

Mistake
Effect
Fix
rewardContext.rewardValue used as a number without conversion
Type mismatch if provider validates the field as numeric
Wrap with $number(rewardContext.rewardValue)
challenge.kvpCustom.someKey returns null
Key not set on the challenge at authoring time
Ensure the key is present in kvpCustom on every challenge that uses this definition
task.accumulators.item_list[-1] is null
No items were applied before reward issued (non-purchase event)
Guard with a conditional or use timestamp from context instead
milestone accessed when source is "task" or "challenge"
milestone is null; expression throws or produces null fields
Check rewardContext.source before accessing milestone, or only use milestone in definitions attached to milestone rewards
Expression returns an array instead of an object
Provider receives unexpected payload structure
Wrap array-returning expressions in an outer object: { "items": [...] }

How-to videos video

➡️ Watch how to set up a loyalty reward provider

AI Knowledge Reference

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:

  • name and url are required fields on a reward provider; guid is system-assigned and read-only.
  • name and rewardJsonata are required fields on a reward definition; guid is system-assigned and read-only.
  • When a reward provider’s enabled field is false, reward delivery is suspended for all definitions under that provider.
  • When a reward definition’s enabled field 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.
  • maxRatePerSecond on a reward provider accepts a value from 1 to 5000 (per provider); null means unlimited.
  • rewardContext.rewardValue is always a string; it must be converted with $number() if the provider expects a numeric value.
  • milestone is null when the reward source is "task" or "challenge"; it is populated (with count and reward.rewardValue) only when rewardContext.source is "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.someKey returns 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? — name and url are required; guid is system-assigned and read-only.
  • Q: What fields are required to create a reward definition? — name and rewardJsonata are required; guid is 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), and reward.denomination into a memberId/points/currency object, producing {"memberId": "ADB-0000030", "points": 50, "currency": "Stars"}.
  • Using kvpCustom: an expression references challenge.kvpCustom.campaignId alongside challenge.profileId and rewardContext.rewardValue to keep the expression reusable across campaigns.
  • Using task accumulator data: an expression uses task.accumulators.item_list[-1].transactionId and .timestamp for 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), and reward.denomination to memberId, pointQuantity, and denomination, producing a payload with pointQuantity: 50.
  • Example 2 — Provider payload with campaign metadata: a Reward Definition builds an awardPoints array using challenge.kvpCustom.parentCampaignId and challenge.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.count and task.schedule.currentStreak; when rewardContext.source is "milestone", the milestone object is populated with count and reward.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.
recommendation-more-help
journey-optimizer-help