---
title: "Reward Rules"
description: "Reward rules govern how affiliate commissions are determined across workspace programs, resolving event structures, payout calculations, and conditional overrides for sales, leads, clicks, referral..."
last_updated: "2026-10-12T07:00:10.092093+00:00"
canonical_url: "https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/partner-programs/reward-rules"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [apps/web/lib/api/sales/construct-reward-amount.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/sales/construct-reward-amount.ts)
- [apps/web/lib/zod/schemas/rewards.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/zod/schemas/rewards.ts)
- [apps/web/lib/api/rewards/validate-reward.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/rewards/validate-reward.ts)
- [apps/web/lib/partners/determine-partner-reward.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/determine-partner-reward.ts)
- [apps/web/ui/partners/rewards/rewards-logic.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/rewards/rewards-logic.tsx)
- [apps/web/lib/partners/get-reward-amount.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/get-reward-amount.ts)
- [apps/web/app/app.dub.co/dashboard/slug/ee/program/groups/groupSlug/rewards/page.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/groups/%5BgroupSlug%5D/rewards/page.tsx)
- [apps/web/lib/partners/evaluate-reward-conditions.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts)
- [apps/web/lib/rewards/resolve-partner-link-rewards.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/rewards/resolve-partner-link-rewards.ts)
- [apps/web/lib/rewards/format-reward-condition.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/rewards/format-reward-condition.ts)
- [apps/web/lib/rewards/validate-tooltip-suggestion.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/rewards/validate-tooltip-suggestion.ts)
- [apps/web/lib/ai/review-reward-tooltip.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/ai/review-reward-tooltip.ts)
- [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/add-edit-bounty/bounty-criteria-manual-submission.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/add-edit-bounty/bounty-criteria-manual-submission.tsx)
- [apps/web/lib/partners/get-group-rewards-and-discount.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/get-group-rewards-and-discount.ts)
- [apps/web/ui/partners/rewards/reward-event-descriptions.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/rewards/reward-event-descriptions.tsx)
- [apps/web/app/ee/app.dub.co/embed/referrals/page-client.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/app.dub.co/embed/referrals/page-client.tsx)
- [apps/web/lib/rewards/resolve-click-reward.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/rewards/resolve-click-reward.ts)
- [apps/web/ui/partners/rewards/partner-referral-reward-builder.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/rewards/partner-referral-reward-builder.tsx)
- [apps/web/app/ee/api/workflows/create-partner-commission/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/workflows/create-partner-commission/route.ts)
- [apps/web/lib/ai/generate-reward.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/ai/generate-reward.ts)
- [apps/web/ui/partners/rewards/custom-reward-builder.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/rewards/custom-reward-builder.tsx)
- [apps/web/lib/partner-referrals/create-referral-commission.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partner-referrals/create-referral-commission.ts)
- [apps/web/ui/partners/custom-reward-description.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/custom-reward-description.tsx)
- [apps/web/ui/partners/rewards/ai-reward-builder.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/rewards/ai-reward-builder.tsx)
- [apps/web/ui/partners/rewards/reward-quality.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/rewards/reward-quality.tsx)
- [apps/web/ui/partners/format-reward-description.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/format-reward-description.ts)
- [apps/web/ui/partners/program-reward-modifiers-tooltip.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/program-reward-modifiers-tooltip.tsx)
- [apps/web/lib/api/sales/calculate-sale-earnings.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/sales/calculate-sale-earnings.ts)
- [apps/web/app/ee/app.dub.co/new-program/slug/program/new/rewards/form.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/app.dub.co/(new-program)/%5Bslug%5D/program/new/rewards/form.tsx)
- [apps/web/lib/ai/ai-reward-schema.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/ai/ai-reward-schema.ts)
</details>

## Overview

Reward rules govern how affiliate commissions are determined across workspace programs, resolving event structures, payout calculations, and conditional overrides for sales, leads, clicks, referrals, and custom schedules. They provide the foundational engine for calculating precise compensation by evaluating contextual attributes such as partner statistics, customer geolocations, and product metadata against configured reward modifiers. The system resolves hierarchy rules from workspace groups down to individual partner links while enforcing spend limits and duration constraints. Additionally, built-in validation pipelines, natural-language formatting utilities, and AI consistency auditing support program managers in creating and maintaining accurate payout configurations.

Sources: [apps/web/lib/api/sales/construct-reward-amount.ts:6-79](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/sales/construct-reward-amount.ts#L6-L79), [apps/web/lib/zod/schemas/rewards.ts:1-253](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/zod/schemas/rewards.ts#L1-L253), [apps/web/lib/api/rewards/validate-reward.ts:15-212](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/rewards/validate-reward.ts#L15-L212), [apps/web/lib/partners/determine-partner-reward.ts:78-245](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/determine-partner-reward.ts#L78-L245), [apps/web/lib/partners/evaluate-reward-conditions.ts:10-69](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L10-L69), [apps/web/lib/rewards/format-reward-condition.ts:14-70](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/rewards/format-reward-condition.ts#L14-L70), [apps/web/lib/rewards/validate-tooltip-suggestion.ts:156-245](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/rewards/validate-tooltip-suggestion.ts#L156-L245), [apps/web/lib/ai/generate-reward.ts:108-315](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/ai/generate-reward.ts#L108-L315), [apps/web/ui/partners/rewards/reward-quality.tsx:10-125](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/rewards/reward-quality.tsx#L10-L125), [apps/web/ui/partners/format-reward-description.ts:14-78](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/format-reward-description.ts#L14-L78), [apps/web/app/ee/api/workflows/create-partner-commission/route.ts:364-444](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/workflows/create-partner-commission/route.ts#L364-L444)

## Reward Schema Definition

### Commission and Reward Entities

Reward structures dictate how payouts are calculated for distinct conversion events across programs. The validation and schema layer establishes structural constraints for flat, percentage, tiered, and custom event rewards. Conditional attributes are organized by entity categories, allowing rules to inspect contextual data for partners, customers, sales, and leads. 

| Entity ID | Label | Attributes | Sources |
| :--- | :--- | :--- | :--- |
| `lead` | Lead | `metadata` | [apps/web/lib/zod/schemas/rewards.ts:52-62](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/zod/schemas/rewards.ts#L52-L62) |
| `partner` | Partner | `country`, `totalClicks`, `totalLeads`, `totalConversions`, `totalSaleAmount`, `totalCommissions` | [apps/web/lib/zod/schemas/rewards.ts:64-99](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/zod/schemas/rewards.ts#L64-L99) |
| `customer` | Customer | `country` | [apps/web/lib/zod/schemas/rewards.ts:101-111](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/zod/schemas/rewards.ts#L101-L111) |
| `sale` | Sale | `productId`, `amount`, `type` | [apps/web/lib/zod/schemas/rewards.ts:228-253](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/zod/schemas/rewards.ts#L228-L253) |

Sources: [apps/web/lib/zod/schemas/rewards.ts:52-111](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/zod/schemas/rewards.ts#L52-L111), [apps/web/lib/zod/schemas/rewards.ts:228-253](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/zod/schemas/rewards.ts#L228-L253)

### Validation Rules and Constraints

> [!CAUTION]
> Percentage rewards are strictly forbidden for `click`, `lead`, and `custom` events. Attempting to assign `amountInPercentage` or setting `type` to `percentage` for these event types triggers an immediate `bad_request` API error during validation.

Sources: [apps/web/lib/api/rewards/validate-reward.ts:18-76](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/rewards/validate-reward.ts#L18-L76)

Validation checks enforce strict boundaries depending on the target reward event type:
- **Click and Lead Rewards:** Must use flat structures with `amountInCents` defined. `amountInPercentage` is disallowed, and `maxDuration` must be set to `0`.
- **Custom Rewards:** Require a valid `config` payload including frequency, interval, and an `anchorDate` that cannot be set in the past. Spend limits (`spendLimitAmount` and `spendLimitInterval`) and `modifiers` are disallowed, and `maxDuration` cannot be `0`.
- **Sale Rewards:** Require either `amountInCents` or `amountInPercentage` to be provided, but both cannot be used simultaneously. Flat sales mandate `amountInCents`, while percentage sales mandate `amountInPercentage`.
- **Referral Rewards:** Require a configuration object containing a valid trigger type. Percentage referral rewards must reference percentage triggers, while flat referral rewards must reference flat triggers. Reward modifiers are prohibited.

Sources: [apps/web/lib/api/rewards/validate-reward.ts:15-212](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/rewards/validate-reward.ts#L15-L212)

## Condition Evaluation Pipeline

### Field Value Resolution and Context Mapping

The evaluation pipeline resolves dynamic rule conditions by inspecting incoming reward contexts across entities such as partner stats, customer attributes, and metadata fields. When evaluating modifier conditions, field values are extracted based on the target entity and attribute type. For metadata attributes, the pipeline utilizes `prepareMetadataFieldValue` to sanitize and map the custom metadata value keyed by `metadataField`.

```typescript
function resolveConditionFieldValue({
  condition,
  context,
}: {
  condition: RewardCondition;
  context: RewardContext;
}): string | number | string[] | number[] | undefined {
  if (condition.attribute === "metadata") {
    const metaKey = condition.metadataField?.trim();

    if (!metaKey) {
      return undefined;
    }

    const entityMap = {
      partner: undefined,
      customer: undefined,
      lead: context.lead,
      sale: context.sale,
    } as const;

    return prepareMetadataFieldValue(
      entityMap[condition.entity]?.metadata?.[metaKey],
      condition,
    );
  }

  const entityMap = {
    partner: context.partner,
    customer: context.customer,
    lead: undefined,
    sale: context.sale,
  } as const;

  return entityMap[condition.entity]?.[condition.attribute];
}
```

Sources: [apps/web/lib/partners/evaluate-reward-conditions.ts:71-106](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L71-L106)

| Entity | Mapped Context Source | Handled Attributes | Sources |
| :--- | :--- | :--- | :--- |
| `partner` | `context.partner` | Partner metrics, country, metadata | [apps/web/lib/partners/evaluate-reward-conditions.ts:98-99](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L98-L99) |
| `customer` | `context.customer` | Country, metadata | [apps/web/lib/partners/evaluate-reward-conditions.ts:100-100](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L100-L100) |
| `lead` | `context.lead` | Metadata fields | [apps/web/lib/partners/evaluate-reward-conditions.ts:88-88](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L88-L88) |
| `sale` | `context.sale` | Product ID, amount, type, metadata | [apps/web/lib/partners/evaluate-reward-conditions.ts:89-89](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L89-L89) |

Sources: [apps/web/lib/partners/evaluate-reward-conditions.ts:78-106](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L78-L106)

### Boolean Operators and Condition Evaluation

Once field values are resolved, individual condition rules are assessed using comparison operators. Condition groups combine multiple individual rules with either `AND` or `OR` logical operators. If all evaluations within an `AND` group or any evaluation within an `OR` group pass, the condition group is marked as met.

```typescript
const evaluateCondition = ({
  condition,
  fieldValue,
}: {
  condition: RewardCondition;
  fieldValue: string | number | string[] | number[];
}) => {
  // Equals
  if (condition.operator === "equals_to") {
    return fieldValue === condition.value;
  }

  // Not equals
  if (condition.operator === "not_equals") {
    return fieldValue !== condition.value;
  }

  // Starts with
  if (condition.operator === "starts_with") {
    if (
      typeof fieldValue !== "string" ||
      typeof condition.value !== "string" ||
      condition.value === ""
    ) {
      return false;
    }

    return fieldValue.startsWith(condition.value);
  }

  // Ends with
  if (condition.operator === "ends_with") {
    if (
      typeof fieldValue !== "string" ||
      typeof condition.value !== "string" ||
      condition.value === ""
    ) {
      return false;
    }

    return fieldValue.endsWith(condition.value);
  }

  // Contains
  if (condition.operator === "contains") {
    if (typeof fieldValue !== "string" || typeof condition.value !== "string") {
      return false;
    }

    const trimmedValue = condition.value.trim();

    if (trimmedValue === "") {
      return false;
    }

    return String(fieldValue).includes(trimmedValue);
  }

  // Not contains
  if (condition.operator === "not_contains") {
    if (typeof fieldValue !== "string" || typeof condition.value !== "string") {
      return false;
    }

    const trimmedValue = condition.value.trim();

    if (trimmedValue === "") {
      return false;
    }

    return !String(fieldValue).includes(trimmedValue);
  }

  // In
  if (condition.operator === "in") {
    if (!Array.isArray(condition.value)) {
      return false;
    }

    return (condition.value as (string | number)[]).includes(
```

Sources: [apps/web/lib/partners/evaluate-reward-conditions.ts:108-224](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L108-L224)

| Operator ID | Evaluation Logic | Sources |
| :--- | :--- | :--- |
| `equals_to` | `fieldValue === condition.value` | [apps/web/lib/partners/evaluate-reward-conditions.ts:116-118](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L116-L118) |
| `not_equals` | `fieldValue !== condition.value` | [apps/web/lib/partners/evaluate-reward-conditions.ts:121-123](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L121-L123) |
| `starts_with` | `fieldValue.startsWith(condition.value)` | [apps/web/lib/partners/evaluate-reward-conditions.ts:126-136](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L126-L136) |
| `ends_with` | `fieldValue.endsWith(condition.value)` | [apps/web/lib/partners/evaluate-reward-conditions.ts:139-149](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L139-L149) |
| `contains` | `String(fieldValue).includes(trimmedValue)` | [apps/web/lib/partners/evaluate-reward-conditions.ts:152-164](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L152-L164) |
| `not_contains` | `!String(fieldValue).includes(trimmedValue)` | [apps/web/lib/partners/evaluate-reward-conditions.ts:167-179](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L167-L179) |
| `in` | `condition.value.includes(fieldValue)` | [apps/web/lib/partners/evaluate-reward-conditions.ts:182-190](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L182-L190) |
| `not_in` | `!condition.value.includes(fieldValue)` | [apps/web/lib/partners/evaluate-reward-conditions.ts:193-201](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L193-L201) |
| `greater_than` | `Number(fieldValue) > Number(condition.value)` | [apps/web/lib/partners/evaluate-reward-conditions.ts:204-206](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L204-L206) |
| `greater_than_or_equal` | `Number(fieldValue) >= Number(condition.value)` | [apps/web/lib/partners/evaluate-reward-conditions.ts:209-211](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L209-L211) |
| `less_than` | `Number(fieldValue) < Number(condition.value)` | [apps/web/lib/partners/evaluate-reward-conditions.ts:214-216](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L214-L216) |
| `less_than_or_equal` | `Number(fieldValue) <= Number(condition.value)` | [apps/web/lib/partners/evaluate-reward-conditions.ts:219-221](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L219-L221) |

Sources: [apps/web/lib/partners/evaluate-reward-conditions.ts:108-224](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L108-L224)

### Override Selection and Precedence

When multiple condition groups match the evaluation context, the pipeline selects the best matching condition by sorting the matching groups in descending order by their calculated reward amount and picking the first entry.

```typescript
  if (matchingConditions.length === 0) {
    return null;
  }

  // Find the best matching condition (highest amount)
  return matchingConditions.sort(
    (a, b) =>
      getRewardAmount({
        type: b.type!,
        amountInCents: b.amountInCents,
        amountInPercentage: b.amountInPercentage,
      }) -
      getRewardAmount({
        type: a.type!,
        amountInCents: a.amountInCents,
        amountInPercentage: a.amountInPercentage,
      }),
  )[0];
};
```

Sources: [apps/web/lib/partners/evaluate-reward-conditions.ts:51-69](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L51-L69)

> [!NOTE]
> If multiple condition groups evaluate to true against the provided context, `evaluateRewardConditions` automatically sorts candidates using `getRewardAmount` and prioritizes the group yielding the highest commission amount.

Sources: [apps/web/lib/partners/evaluate-reward-conditions.ts:55-68](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/evaluate-reward-conditions.ts#L55-L68)

When a matched condition overrides the base reward, properties such as `type`, `amountInCents`, `amountInPercentage`, and `maxDuration` are updated on the reward object. For sales featuring Stripe line items with `productId` modifiers, `determinePartnerRewards` iterates over each product in the sale context to calculate distinct line-item overrides.

```typescript
let matchedCondition: RewardConditions | null = null;

if (partnerReward.modifiers && context) {
  const modifiers = rewardConditionsArraySchema.safeParse(
    partnerReward.modifiers,
  );

  // Parse the conditions before evaluating them
  if (modifiers.success) {
    matchedCondition = evaluateRewardConditions({
      conditions: modifiers.data,
      context,
    });

    if (matchedCondition) {
      partnerReward = {
        ...partnerReward,
        // Override the reward amount, type and max duration with the matched condition
        type: matchedCondition.type || partnerReward.type,
        amountInCents:
          matchedCondition.amountInCents != null
            ? matchedCondition.amountInCents
            : null,
        amountInPercentage:
          matchedCondition.amountInPercentage != null
            ? new Prisma.Decimal(matchedCondition.amountInPercentage)
            : null,
        maxDuration:
          matchedCondition.maxDuration !== undefined
            ? matchedCondition.maxDuration
            : partnerReward.maxDuration,
      };
    }
  }
}
```

Sources: [apps/web/lib/partners/determine-partner-reward.ts:116-150](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/determine-partner-reward.ts#L116-L150), [apps/web/lib/partners/determine-partner-reward.ts:209-237](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/determine-partner-reward.ts#L209-L237)

## Payout Amount Calculations

### Earnings Calculation and Base Reward Retrieval

The commission earnings for individual sales are computed via `calculateSaleEarnings`, which evaluates the underlying reward structure against sale parameters such as quantity and total monetary amount. It first resolves the raw numeric amount using `getRewardAmount`, handling nullable configurations by defaulting unassigned reward values to zero.

```typescript
export const calculateSaleEarnings = ({
  reward,
  sale,
}: {
  reward: Pick<RewardProps, "type" | "amountInCents" | "amountInPercentage">;
  sale: Pick<Commission, "quantity" | "amount">;
}) => {
  if (!reward) {
    return 0;
  }

  const amount = getRewardAmount(reward);

  if (reward.type === "flat") {
    return sale.quantity * amount;
  } else if (reward.type === "percentage") {
    return Math.round((sale.amount * amount) / 100);
  }

  return 0;
};
```

Sources: [apps/web/lib/api/sales/calculate-sale-earnings.ts:8-28](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/sales/calculate-sale-earnings.ts#L8-L28), [apps/web/lib/partners/get-reward-amount.ts:3-11](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partners/get-reward-amount.ts#L3-L11)

> [!NOTE]
> `calculateSaleEarnings` branches explicitly on the reward type: flat rewards multiply the sale quantity by the resolved unit amount, whereas percentage rewards multiply the sale amount by the percentage rate and divide by 100, rounding the final integer result with `Math.round`.

Sources: [apps/web/lib/api/sales/calculate-sale-earnings.ts:21-25](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/sales/calculate-sale-earnings.ts#L21-L25)

### Modifier Validation and Range Formatting

When rewards contain modifier conditions, `constructRewardAmount` validates them using `rewardConditionsArraySchema`. Each modifier's `type` and `maxDuration` are evaluated against the primary reward parameters. If every modifier matches both the primary reward type and maxDuration, the engine calculates a boundary range across the primary reward and all valid modifiers.

```typescript
// If there are modifiers, we need to check if they match the primary reward
if (reward.modifiers) {
  const parsedModifiers = rewardConditionsArraySchema.safeParse(
    reward.modifiers,
  );

  if (parsedModifiers.success) {
    const modifiers = parsedModifiers.data;

    // if no type or maxDuration, it falls back to the primary reward type and maxDuration
    const matchPrimary = modifiers.every((m) => {
      const typeMatches =
        m.type === undefined ? true : m.type === reward.type;
      const durationMatches =
        m.maxDuration === undefined
          ? true
          : m.maxDuration === reward.maxDuration;
      return typeMatches && durationMatches;
    });

    // If the type AND maxDuration matches the primary, show a range
    if (matchPrimary) {
      const amount = getRewardAmount(reward);

      const min = Math.min(
        amount,
        ...modifiers.map((modifier) =>
          reward.type === "flat"
            ? modifier.amountInCents ?? Infinity
            : modifier.amountInPercentage ?? Infinity,
        ),
      );

      const max = Math.max(
        amount,
        ...modifiers.map((modifier) =>
          reward.type === "flat"
            ? modifier.amountInCents ?? 0
            : modifier.amountInPercentage ?? 0,
        ),
      );

      if (min !== max) {
        return `Up to ${
          reward.type === "percentage"
            ? `${max}%`
            : currencyFormatter(max, {
                trailingZeroDisplay: "stripIfInteger",
              })
        }`;
      }
    }
```

Sources: [apps/web/lib/api/sales/construct-reward-amount.ts:16-67](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/sales/construct-reward-amount.ts#L16-L67)

| Reward Type | Minimum Value Fallback | Maximum Value Fallback | Formatting Logic |
| :--- | :--- | :--- | :--- |
| `flat` | `Infinity` (if null) | `0` (if null) | `currencyFormatter(max, { trailingZeroDisplay: "stripIfInteger" })` |
| `percentage` | `Infinity` (if null) | `0` (if null) | `${max}%` |

Sources: [apps/web/lib/api/sales/construct-reward-amount.ts:40-65](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/sales/construct-reward-amount.ts#L40-L65)

> [!WARNING]
> If modifiers omit either `type` or `maxDuration`, they default to matching the primary reward's respective properties. However, if any modifier fails to match both properties, range construction is bypassed entirely and the engine falls back to displaying the primary reward amount.

Sources: [apps/web/lib/api/sales/construct-reward-amount.ts:25-34](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/sales/construct-reward-amount.ts#L25-L34), [apps/web/lib/api/sales/construct-reward-amount.ts:71-78](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/api/sales/construct-reward-amount.ts#L71-L78)

## Group And Link Resolution

### Hierarchy Resolution and Display Filters

Workspace groups and partner links resolve active rewards through dedicated filtering utilities. The `getPartnerLinkDisplayRewards` function processes event reward parameters including `clickReward`, `leadReward`, `saleReward`, `discount`, and `enrollmentRewards`. It invokes `pickDisplayReward` on primary event rewards to filter out null instances or negative monetary values, while `getDisplayCustomRewards` extracts custom event rewards where the computed reward amount is greater than or equal to zero.

```typescript
function pickDisplayReward(reward: RewardProps | null | undefined) {
  if (!reward || getRewardAmount(reward) < 0) {
    return null;
  }

  return reward;
}

function getDisplayCustomRewards(enrollmentRewards: RewardProps[]) {
  return enrollmentRewards.filter(
    (reward) => reward.event === "custom" && getRewardAmount(reward) >= 0,
  );
}

export function getPartnerLinkDisplayRewards({
  clickReward,
  leadReward,
  saleReward,
  discount,
  enrollmentRewards = [],
}: {
  clickReward?: RewardProps | null;
  leadReward?: RewardProps | null;
  saleReward?: RewardProps | null;
  discount?: DiscountProps | null;
  enrollmentRewards?: RewardProps[];
}) {
  return {
    rewards: [
      pickDisplayReward(saleReward),
      pickDisplayReward(leadReward),
      pickDisplayReward(clickReward),
      ...getDisplayCustomRewards(enrollmentRewards),
    ].filter((reward): reward is RewardProps => reward != null),
    discount: discount ?? null,
  };
}
```

Sources: [apps/web/lib/rewards/resolve-partner-link-rewards.ts:4-39](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/rewards/resolve-partner-link-rewards.ts#L4-L39)

> [!NOTE]
> When prioritizing partner incentives via `getPartnerLinkPrimaryIncentive`, the engine evaluates primary events in the strict order of `sale`, `lead`, and `click`. If none of these primary event rewards exist, the primary incentive falls back to the associated discount.

Sources: [apps/web/lib/rewards/resolve-partner-link-rewards.ts:42-60](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/rewards/resolve-partner-link-rewards.ts#L42-L60)

### Spend Limits and Duration Enforcement

During commission creation workflows, the [Commission Engine](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/billing-and-payouts/commission-engine) evaluates whether active rewards have breached duration limits or cumulative spend thresholds. For sale events carrying a `maxDuration` property, one-time sales with a `maxDuration` of `0` immediately skip commission generation for recurring events. For recurring subscriptions where `maxDuration` exceeds zero, the workflow computes the month difference via `differenceInMonths` against the first commission creation date. If this duration meets or exceeds the configured `maxDuration`, commission processing is halted.

Sources: [apps/web/app/ee/api/workflows/create-partner-commission/route.ts:366-389](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/workflows/create-partner-commission/route.ts#L366-L389)

Furthermore, when a reward defines a `spendLimitAmount` and `spendLimitInterval`, earnings are evaluated through `clampEarningsToSpendLimit`. If the clamped earnings drop to zero, commission creation is skipped entirely. Custom reward commissions additionally verify that the target partner remains approved and actively bound to the specific `customRewardId` at write time.

```typescript
if (
  customerId &&
  event !== "custom" &&
  reward &&
  reward.spendLimitAmount &&
  reward.spendLimitInterval
) {
  cappedEarnings = await clampEarningsToSpendLimit({
    reward,
    earnings,
    programId,
    partnerId,
    customerId,
    referenceDate: createdAt ?? new Date(),
  });

  // If spend limit clamped earnings to 0, skip commission creation
  if (cappedEarnings === 0) {
    return logAndReturn({
      commission: null,
      outputLog: `Partner ${partnerId} has reached spend limit (${currencyFormatter(reward.spendLimitAmount)} ${reward.spendLimitInterval === "allTime" ? "" : `per ${reward.spendLimitInterval}`}) for ${event} event, skipping commission creation...`,
    });
  }
}

// Custom reward jobs are queued from a snapshot of eligible enrollments.
// Re-check at write time so we don't pay a partner who was banned, deactivated,
// or moved off this reward before the workflow ran.
// Only jobs that pass rewardId (create-custom-commission) should be gated —
// manual commissions and bounty payouts also use event "custom" but have no rewardId.
const isCustomRewardCommission = event === "custom" && Boolean(rewardId);
const stillOnCustomReward = programEnrollment.customRewardId === rewardId;

if (
  isCustomRewardCommission &&
  (programEnrollment.status !== "approved" || !stillOnCustomReward)
) {
  return logAndReturn({
    commission: null,
    outputLog: `Partner ${partnerId} is no longer eligible for custom reward ${rewardId} (status: ${programEnrollment.status}), skipping commission creation...`,
  });
}
```

Sources: [apps/web/app/ee/api/workflows/create-partner-commission/route.ts:421-462](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/workflows/create-partner-commission/route.ts#L421-L462)

## Referral And Bounty Rewards

### Partner Referral Compensation Rules

The partner referral rule engine processes two-tier affiliate compensation by evaluating whether a commission originates from a source commission or a direct partner approval and commission threshold event. When a partner refers another affiliate, the system prevents self-referrals by checking whether the active partner identifier matches the `referredByPartnerId` context, immediately skipping commission generation if both values are identical.

```typescript
type CreateReferralCommissionProps =
  | { sourceCommissionId: string; partnerId?: never; programId?: never } // Based on a source commission
  | { sourceCommissionId?: never; partnerId: string; programId: string }; // Based on partner approval and commission threshold

export const createReferralCommission = async (
  props: CreateReferralCommissionProps,
) => {
  const context = await resolveReferralContext(props);

  if (!context) {
    return null;
  }

  const { sourceCommission, programId, partnerId, referredByPartnerId } =
    context;

  if (partnerId && referredByPartnerId && partnerId === referredByPartnerId) {
    console.log(
      `Skipping referral commission creation for self-referral (partner ${partnerId}).`,
    );
    return null;
  }
```

Sources: [apps/web/lib/partner-referrals/create-referral-commission.ts:14-35](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/partner-referrals/create-referral-commission.ts#L14-L35)

Reward structures for referrals are split into percentage and flat triggers. Percentage triggers evaluate commission basis rules using predefined percentage events, whereas flat triggers operate on specific partner milestones such as partner approval or commission thresholds.

| Trigger Category | Trigger Identifier | Description & Behaviour |
| :--- | :--- | :--- |
| Percentage | `saleRecorded` | Evaluates percentage reward based on recorded sales. |
| Percentage | `commissionEarned` | Evaluates percentage reward based on earned partner commissions. |
| Flat | `partnerApproved` | Triggers flat reward immediately upon partner approval (clears `maxDuration`). |
| Flat | `commissionThreshold` | Triggers flat reward when accumulated commissions hit a defined threshold in cents. |

Sources: [apps/web/ui/partners/rewards/partner-referral-reward-builder.tsx:6-11](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/rewards/partner-referral-reward-builder.tsx#L6-L11), [apps/web/ui/partners/rewards/partner-referral-reward-builder.tsx:56-101](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/rewards/partner-referral-reward-builder.tsx#L56-L101), [apps/web/ui/partners/rewards/partner-referral-reward-builder.tsx:207-214](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/rewards/partner-referral-reward-builder.tsx#L207-L214)

> [!CAUTION]
> Flat referral triggers such as `partnerApproved` and `commissionThreshold` do not support duration limits. Whenever a flat trigger becomes active, the builder automatically forces `maxDuration` to `null`. Switching back to a percentage trigger automatically restores `maxDuration` to `Infinity` if it was previously cleared.

Sources: [apps/web/ui/partners/rewards/partner-referral-reward-builder.tsx:77-101](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/rewards/partner-referral-reward-builder.tsx#L77-L101)

### Manual Bounty Submission Criteria

Bounty programs manage time-bound content challenges through submission requirements that dictate whether creators must attach proof URLs or image files. The manual submission criteria engine reads submission options from the bounty form context, tracking requirement flags and configuration limits for images and URLs.

```tsx
export function BountyCriteriaManualSubmission() {
  const { watch, setValue } = useBountyFormContext();

  const [submissionRequirements, rewardType = "flat"] = watch([
    "submissionRequirements",
    "rewardType",
  ]);

  const requireImage = !!submissionRequirements?.image;
  const requireUrl = !!submissionRequirements?.url;
  const imageMax = submissionRequirements?.image?.max;
  const urlMax = submissionRequirements?.url?.max;
  const urlDomains = submissionRequirements?.url?.domains ?? [];
```

Sources: [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/add-edit-bounty/bounty-criteria-manual-submission.tsx:48-61](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/add-edit-bounty/bounty-criteria-manual-submission.tsx#L48-L61)

Bounty payouts support two reward structure types managed via interactive badges: flat rate payouts and custom descriptions. Flat rate bounties validate numeric currency amounts greater than or equal to zero, while custom bounties require a text description limited to 100 characters.

| Reward Type | Value Identifier | Validation Rule | Display Formatting |
| :--- | :--- | :--- | :--- |
| Flat Rate | `flat` | `rewardAmount != null && !isNaN && rewardAmount >= 0` | Formatted via `currencyFormatter` in cents. |
| Custom Amount | `custom` | `!rewardDescription?.trim()` (max 100 characters) | Displays trimmed description text or fallback placeholder. |

Sources: [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/add-edit-bounty/bounty-criteria-manual-submission.tsx:22-31](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/add-edit-bounty/bounty-criteria-manual-submission.tsx#L22-L31), [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/add-edit-bounty/bounty-criteria-manual-submission.tsx:384-436](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/add-edit-bounty/bounty-criteria-manual-submission.tsx#L384-L436)

> [!NOTE]
> When configuring custom bounty rewards, input values matching an empty string are explicitly converted to `null` before updating form state, and the controller enforces a strict maximum length rule of 100 characters.

Sources: [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/add-edit-bounty/bounty-criteria-manual-submission.tsx:418-435](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/add-edit-bounty/bounty-criteria-manual-submission.tsx#L418-L435)

## Form Construction And Tooltips

Visual program builders construct and edit reward structures through interactive form contexts, field arrays, and natural-language description engines. The reward form interface manages conditional rule groups, payout rates, and cadence configurations via react-hook-form state management. When configuring custom interval rewards, builders parse custom cadence configurations using `customConfigSchema` and format schedule descriptions and duration labels dynamically.

Sources: [apps/web/ui/partners/rewards/rewards-logic.tsx:55-102](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/rewards/rewards-logic.tsx#L55-L102), [apps/web/ui/partners/rewards/custom-reward-builder.tsx:40-51](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/rewards/custom-reward-builder.tsx#L40-L51), [apps/web/ui/partners/custom-reward-description.tsx:12-38](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/custom-reward-description.tsx#L12-L38)

Natural-language formatting functions convert raw reward objects and condition clauses into human-readable text strings for tooltips, embeds, and summary rows. The `formatRewardDescription` utility processes event types, flat or percentage amounts, duration constraints, and spend limit intervals into polished payout descriptions.

| Event Type | Condition Clause Formatter | Duration Suffix Logic | Spend Limit Integration |
| :--- | :--- | :--- | :--- |
| `sale` | `formatRewardConditionClause` | `for the customer's lifetime` or `for N months` / `for N years` | Appends `, up to [amount] [interval] per customer` |
| `referral` | `formatReferralRewardDescription` | `for the referred partner's lifetime` or `one time` | None |
| `custom` | `formatCustomRewardDescription` | `for [durationLabel]` | None |
| `lead` / `click` | Default event clause | None | None |

Sources: [apps/web/lib/rewards/format-reward-condition.ts:47-70](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/rewards/format-reward-condition.ts#L47-L70), [apps/web/ui/partners/format-reward-description.ts:14-78](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/format-reward-description.ts#L14-L78)

Reward quality grading evaluates configured amounts and durations against predefined competitiveness tiers, surfacing visual indicators with descriptive tooltips. The quality engine categorizes rewards into four discrete quality levels based on event type, payout structure, and duration thresholds.

| Reward Quality | Target Event & Criteria | Visual Badge Styling | Tooltip Summary |
| :--- | :--- | :--- | :--- |
| `low` | Clicks `< $0.03`, leads `< $1`, or percentage sales `< 10%` | Orange background (`bg-orange-100`), orange text | May struggle to attract partners |
| `limited` | One-time flat sales or short duration caps (`maxDuration === 1`) | Orange background (`bg-orange-100`), orange text | No long term earning potential |
| `good` | Clicks `0.03–0.05`, leads `1–10`, percentage sales `10–20%` | Cyan background (`bg-cyan-200`), cyan text | Solid reward with room to grow |
| `strong` | Clicks `> 0.05`, leads `> 10`, percentage sales `> 20%` with lifetime/12m+ duration | Green background (`bg-green-200`), green text | High earning potential for partners |

Sources: [apps/web/ui/partners/rewards/reward-quality.tsx:10-125](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/rewards/reward-quality.tsx#L10-L125), [apps/web/ui/partners/rewards/reward-quality.tsx:127-165](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/rewards/reward-quality.tsx#L127-L165)

> [!NOTE]
> AI consistency auditing uses Anthropic Claude Sonnet via streaming server actions (`generateReward` and `reviewRewardTooltip`) to check whether partner-facing copy contradicts structured reward conditions, automatically enforcing confidence floors of `0.5` and filtering out uninstalled customer source integrations.

Sources: [apps/web/lib/ai/generate-reward.ts:192-202](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/ai/generate-reward.ts#L192-L202), [apps/web/lib/ai/review-reward-tooltip.ts:221-238](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/ai/review-reward-tooltip.ts#L221-L238), [apps/web/lib/rewards/validate-tooltip-suggestion.ts:1-8](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/rewards/validate-tooltip-suggestion.ts#L1-L8), [apps/web/lib/rewards/validate-tooltip-suggestion.ts:194-204](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/rewards/validate-tooltip-suggestion.ts#L194-L204)

## Related

- [Commission Engine](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/billing-and-payouts/commission-engine)
- [Partner Programs](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/partner-programs/partner-programs)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/llms.txt) for all pages in this wiki.
