---
title: 'Write a Stratus function for loyalty rule completion'
description: 'Learn the input/output contract and build a handler that awards points or badges when a loyalty rule is completed.'
keywords: ["custom loyalty rule function", "programmable loyalty", "Stratus loyalty integration", "serverless rewards"]
---

> **For AI agents:** the complete documentation index is at [llms.txt](https://docs.snagsolutions.io/llms.txt). Append `.md` to any page URL for its markdown version.

## Overview

Use a Stratus function to decide if a user should be rewarded for a specific loyalty rule and return the reward details. This page explains when your function runs, the required input and output shapes, validation rules, and includes a copy‑paste handler template.

<Info>
  If you're new to Stratus functions, read the [Function
  Syntax](https://docs.snagsolutions.io/stratus/syntax) first for required
  export patterns, allowed modules, and validation behavior.
</Info>

## When your function runs

Your function executes during a loyalty rule completion attempt for rules of type `stratus_function`. The platform calls your function with a structured `input` object containing the current user, userGroupUsers and the `loyaltyRule` being completed. Your function must return a parsed output describing the reward to grant.

## Input contract

Your handler receives two parameters `(input, output)` and must accept the following input shape. `input` may be a stringified JSON; parse it if needed.

```javascript Input shape
{
  user: {
    id: string,
    walletAddress: string,
    walletType: string,
  },
  loyaltyRule: {
    id: string,
    name: string,
    type: string,
    amount: number,
    metadata: object,
    startTime: string,
    endTime: string,
    interval: string,
    rewardType: string,
  }
}
```

<Note>
  `input` is passed exactly as shown above by the completion API for
  `stratus_function` rules.
</Note>

## Output contract

Set the result to an array of one reward object and return `output.buildOutput()`.

- **walletAddress**: string (required) – Address to reward
- **amount**: number (required if awarding fixed points) – Points to grant
- **idempotencyKey**: string (optional) – Only pass if you want to override the idempotency key for the rule that we set for managing how often a user can be rewarded for the same rule.
- **data**: string (optional) – A JSON.stringify()-ed object with extra context; must be valid JSON and under 1KB
- **loyaltyBadgeId**: string UUID (optional) – Badge to award instead of points or in addition to range-based rules
- **loyaltyMultiplierAmount**: number (optional) – Multiplier for range-based rules

<Warning>
  For now, only a single reward item is allowed. If you return more than one,
  validation will fail.
</Warning>

### Validation rules enforced by the platform

- Exactly one `handler` export; must call `output.setResult(...)` and then `return output.buildOutput()`
- Result must be an array with at most one reward object
- `data` must be stringified JSON and < 1KB
- Allowed modules: `axios`, `lodash`, `viem` and `viem/chains` only (see [Function Syntax](https://docs.snagsolutions.io/stratus/syntax))

## Copy‑paste handler template

Below is a ready‑to‑use template based on the attached snippet. Replace the eligibility logic and values with your own.

```javascript loyalty-rule.js
module.exports.handler = async (input, output) => {
  try {
    // Parse input if it was passed as a string
    let parsed = typeof input === 'string' ? JSON.parse(input) : input
    parsed = typeof parsed === 'string' ? JSON.parse(parsed) : parsed;
    parsed = typeof parsed === 'string' ? JSON.parse(parsed) : parsed;

    /**
     * input = {
     *   user: { id, walletAddress, walletType },
     *   userGroupUsers?: { id, walletAddress, walletType }[],
     *   loyaltyRule: { id, name, type, amount?, ... }
     * }
     */
    const { user, loyaltyRule } = parsed

    // Compute reward details
    const amount = 2 // Example fixed points
    const idempotencyKey = `rule:${loyaltyRule.id}:user:${user.id}` // Replace with a transaction hash or another unique key

    // Optional structured context under 1KB
    const data = JSON.stringify({
      reason: 'eligible via custom function',
      ruleType: loyaltyRule.type,
    })

    // Set result (single reward item only)
    output.setResult([
      {
        walletAddress: user.walletAddress,
        amount,
        idempotencyKey,
        data,
        // loyaltyBadgeId: '00000000-0000-0000-0000-000000000000',
        // loyaltyMultiplierAmount: 1.25,
      },
    ])

    return output.buildOutput()
  } catch (error) {
    console.error('Error in user code:', error)
    throw error
  }
}
```

## How to add and test your function

<Steps>
<Step title="Open the Loyalty Rule form">
  - Go to Admin → Loyalty → Rules
  - Click "Create Rule" or edit an existing rule
</Step>

<Step title="Choose rule type and add your code">
  - Set the rule type to `stratus_function` - In the function editor within the
  rule form, paste the handler template and customize it - Save the rule; no
  separate Stratus function is required
</Step>

<Step title="Trigger a completion attempt">
  - From your site or via API, attempt to complete the rule for a user
  - The platform invokes your function with the input shown above
  - If your function returns a valid reward, the user is queued for fulfillment

  <Check>
  Verify that your function run succeeded and a reward was recorded for the user.
  </Check>
</Step>
</Steps>

## Troubleshooting

<AccordionGroup>
<Accordion title="Validation error: Multiple rewards are not allowed">
Return exactly one reward object in the array passed to `output.setResult`.
</Accordion>

<Accordion title="Data must be a valid JSON object and less than 1kb">
Ensure `data` is produced by `JSON.stringify({ ... })` and the string length is under 1024 characters.
</Accordion>

<Accordion title="Handler must call output.setResult / output.buildOutput()">
  Confirm your handler sets a result and returns `output.buildOutput()` as the
  final statement.
</Accordion>

<Accordion title="Module 'X' is not approved">
Import only `axios`, `lodash`, `viem`, or `viem/chains`. Remove Node built-ins like `fs`.
</Accordion>
</AccordionGroup>

## See also

- [Function Syntax](https://docs.snagsolutions.io/stratus/syntax)
- [Function Templates](https://docs.snagsolutions.io/stratus/function-templates)
- Loyalty rule API reference for response data: `https://docs.snagsolutions.io/api-reference/loyalty/get-loyalty-rules#response-data`
