---
title: "Bounties and Social Metrics"
description: "Bounties and Social Metrics power partner reward campaigns by integrating performance tracking with social media engagement data. This subsystem enables program administrators to establish targeted..."
last_updated: "2026-10-05T05:07:35.16912+00:00"
canonical_url: "https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/affiliate-platform/bounties-and-social-metrics"
---

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

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

- [apps/web/app/ee/api/bounties/bountyId/sync-social-metrics/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/bounties/%5BbountyId%5D/sync-social-metrics/route.ts)
- [apps/web/app/ee/api/partner-profile/programs/programId/bounties/bountyId/social-content-stats/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/bounties/%5BbountyId%5D/social-content-stats/route.ts)
- [apps/web/app/ee/api/bounties/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/bounties/route.ts)
- [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/add-edit-bounty/bounty-criteria-social-metrics.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/add-edit-bounty/bounty-criteria-social-metrics.tsx)
- [apps/web/app/ee/api/embed/referrals/bounties/bountyId/social-content-stats/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/embed/referrals/bounties/%5BbountyId%5D/social-content-stats/route.ts)
- [apps/web/app/ee/api/cron/bounties/sync-social-metrics/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/bounties/sync-social-metrics/route.ts)
- [apps/web/app/ee/partners.dub.co/dashboard/programs/programSlug/enrolled/bounties/bountyId/page.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/%5BprogramSlug%5D/(enrolled)/bounties/%5BbountyId%5D/page.tsx)
- [apps/web/lib/bounty/social-metrics-milestones.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/social-metrics-milestones.ts)
- [apps/web/ui/partners/bounties/bounty-social-content.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/bounty-social-content.tsx)
- [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/bountyId/bounty-submission-details-sheet.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/%5BbountyId%5D/bounty-submission-details-sheet.tsx)
- [apps/web/lib/api/workflows/award-bounty/execute.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/workflows/award-bounty/execute.ts)
- [apps/web/ui/partners/bounties/use-social-metrics-milestones.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/use-social-metrics-milestones.ts)
- [apps/web/lib/bounty/api/approve-bounty-submission.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/approve-bounty-submission.ts)
- [apps/web/app/ee/api/embed/referrals/bounties/bountyId/submissions/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/embed/referrals/bounties/%5BbountyId%5D/submissions/route.ts)
- [apps/web/app/ee/api/cron/bounties/queue-sync-social-metrics/route.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/bounties/queue-sync-social-metrics/route.ts)
- [apps/web/lib/zod/schemas/bounties.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/zod/schemas/bounties.ts)
- [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/add-edit-bounty/bounty-criteria.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/add-edit-bounty/bounty-criteria.tsx)
- [apps/web/ui/partners/bounties/bounty-submission-details-sheet.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/bounty-submission-details-sheet.tsx)
- [apps/web/app/ee/app.dub.co/embed/referrals/bounties/detail.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/bounties/detail.tsx)
- [apps/web/ui/partners/bounties/use-social-content.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/use-social-content.ts)
- [apps/web/ui/partners/bounties/bounty-submission-requirements.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/bounty-submission-requirements.tsx)
- [apps/web/ui/partners/bounties/evaluate-social-content-requirements.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/evaluate-social-content-requirements.ts)
- [apps/web/app/ee/app.dub.co/embed/referrals/bounties/index.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/bounties/index.tsx)
- [apps/web/app/ee/app.dub.co/embed/referrals/bounties/submission-detail.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/bounties/submission-detail.tsx)
- [apps/web/ui/partners/bounties/bounty-social-content-preview.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/bounty-social-content-preview.tsx)
- [apps/web/lib/bounty/api/get-social-metrics-updates.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/get-social-metrics-updates.ts)
- [apps/web/ui/partners/bounties/claim-bounty-sheet.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/claim-bounty-sheet.tsx)
- [apps/web/lib/bounty/api/create-bounty-submission.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/create-bounty-submission.ts)
- [apps/web/ui/partners/bounties/bounty-reward-criteria.tsx](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/bounty-reward-criteria.tsx)
- [apps/web/app/ee/app.dub.co/embed/referrals/bounties/use-embed-social-content.ts](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/bounties/use-embed-social-content.ts)
</details>

## Overview

Bounties and Social Metrics power partner reward campaigns by integrating performance tracking with social media engagement data. This subsystem enables program administrators to establish targeted promotional campaigns that dynamically evaluate partner-submitted content, such as posts across supported platforms, against specific viewership and interaction milestones. By automating background metric synchronization, validating submission criteria, and calculating incremental bonus caps, the system ensures accurate performance evaluation while reducing manual review overhead. Creators and partners can seamlessly track progress, submit campaign content via dashboard interfaces or embedded views, and receive automated payouts as milestones are achieved.
Sources: [apps/web/app/ee/api/bounties/bountyId/sync-social-metrics/route.ts:20-192](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/bounties/%5BbountyId%5D/sync-social-metrics/route.ts#L20-L192), [apps/web/app/ee/api/cron/bounties/sync-social-metrics/route.ts:21-224](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/bounties/sync-social-metrics/route.ts#L21-L224), [apps/web/lib/bounty/social-metrics-milestones.ts:33-126](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/social-metrics-milestones.ts#L33-L126), [apps/web/lib/bounty/api/create-bounty-submission.ts:48-562](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/create-bounty-submission.ts#L48-L562)

## Bounty Configuration and Criteria Models

### Overview

Program bounties are configured using Zod validation schemas that define persistence structures, API payloads, and creator dashboard criteria models. The primary validation routines and data schemas govern how bounties are created, retrieved, and listed under workspace contexts, requiring specific plan capabilities such as business, advanced, or enterprise tiers.
Sources: [apps/web/app/ee/api/bounties/route.ts:27-31](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/bounties/route.ts#L27-L31), [apps/web/app/ee/api/bounties/route.ts:163-166](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/bounties/route.ts#L163-L166), [apps/web/lib/zod/schemas/bounties.ts:241-261](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/zod/schemas/bounties.ts#L241-L261)

### Bounty Schema Definitions

The bounty data model supports various configuration attributes, including start modes, submission frequencies, performance scopes, and reward calculations. The `BountySchema` definition outlines fields such as `id`, `name`, `type`, `startsAt`, `endsAt`, `startMode`, `maxSubmissions`, `rewardAmount`, and `submissionRequirements`. Related list schemas like `BountyListSchema` extend these definitions with aggregated submission counts.
Sources: [apps/web/lib/zod/schemas/bounties.ts:241-261](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/zod/schemas/bounties.ts#L241-L261), [apps/web/lib/zod/schemas/bounties.ts:269-277](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/zod/schemas/bounties.ts#L269-L277)

| Schema Field | Type / Enum | Default Value | Description |
| :--- | :--- | :--- | :--- |
| `id` | `string` | *None* | Unique identifier for the bounty. |
| `name` | `string \| null` | `null` | Display name of the bounty. |
| `type` | `BountyType` | *None* | Classification type of the bounty. |
| `startMode` | `BountyStartMode` | *None* | Execution mode governing how the bounty period begins. |
| `maxSubmissions` | `number` | *None* | Maximum allowable submissions for the bounty. |
| `rewardAmount` | `number \| null` | `null` | Fixed monetary reward amount. |
| `performanceCondition` | `awardBountyConditionSchema \| null` | `null` | Evaluated conditions required to trigger the reward. |
| `submissionRequirements` | `submissionRequirementsSchema \| null` | `null` | Criteria requirements including manual inputs or social metrics. |

Sources: [apps/web/lib/zod/schemas/bounties.ts:241-261](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/zod/schemas/bounties.ts#L241-L261)

### Social Criteria Rules and Dashboard Configuration

Within the creator dashboard, the `BountyCriteria` component renders distinct configuration views depending on the active bounty type interface (`performance`, `submission`, or `socialMetrics`). The `BountyCriteriaSocialMetrics` component evaluates social platform constraints by inspecting criteria rules. It tracks whether a target platform, minimum metric count, and specific metric type are configured.

```typescript
export function BountyCriteriaSocialMetrics() {
  const { watch, setValue } = useBountyFormContext();

  const [submissionRequirements, rewardAmount] = watch([
    "submissionRequirements",
    "rewardAmount",
  ]);

  const socialMetrics = submissionRequirements?.socialMetrics;
  const hasChannel = socialMetrics?.platform != null;
  const hasMinCount =
    socialMetrics?.minCount != null && socialMetrics.minCount > 0;
  const hasMetric = socialMetrics?.metric != null;
  // ...
}
```

If any required field is missing, inline popover validation elements flag the configuration as invalid using distinct visual states.
Sources: [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/add-edit-bounty/bounty-criteria-social-metrics.tsx:41-55](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/add-edit-bounty/bounty-criteria-social-metrics.tsx#L41-L55), [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/add-edit-bounty/bounty-criteria-social-metrics.tsx:91-101](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/add-edit-bounty/bounty-criteria-social-metrics.tsx#L91-L101), [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/add-edit-bounty/bounty-criteria.tsx:18-32](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/add-edit-bounty/bounty-criteria.tsx#L18-L32)

## Social Content Inspection and Embeds

### Overview

Partner interfaces inspect social media content by parsing post URLs, validating platform identifiers, and fetching engagement statistics. The application provides two distinct API endpoints for retrieving social content statistics: one for authenticated partner profiles (`/api/partner-profile/programs/[programId]/bounties/[bountyId]/social-content-stats`) and another for referral embed tokens (`/api/embed/referrals/bounties/[bountyId]/social-content-stats`). Both routes validate search parameters via Zod (`searchParamsSchema` with `z.httpUrl`), assert rate limits using `RATELIMIT_POLICIES.socialContentStats`, verify bounty requirements through `resolveBountyDetails`, and invoke `getSocialContent` to query platform metrics.
Sources: [apps/web/app/ee/api/partner-profile/programs/programId/bounties/bountyId/social-content-stats/route.ts:16-91](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/bounties/%5BbountyId%5D/social-content-stats/route.ts#L16-L91), [apps/web/app/ee/api/embed/referrals/bounties/bountyId/social-content-stats/route.ts:16-80](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/embed/referrals/bounties/%5BbountyId%5D/social-content-stats/route.ts#L16-L80)

> [!NOTE]
> Rate limiting for social content inspection is strictly enforced per partner identifier using predefined Upstash rate-limit policies to prevent abuse of platform scraping routines.
> Sources: [apps/web/app/ee/api/partner-profile/programs/programId/bounties/bountyId/social-content-stats/route.ts:27-30](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/bounties/%5BbountyId%5D/social-content-stats/route.ts#L27-L30), [apps/web/app/ee/api/embed/referrals/bounties/bountyId/social-content-stats/route.ts:26-29](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/embed/referrals/bounties/%5BbountyId%5D/social-content-stats/route.ts#L26-L29)

### Client Hooks and Requirement Evaluation

Frontend components interact with these endpoints through dedicated hooks: `useSocialContent` for standard partner profile dashboards and `useEmbedSocialContent` for embedded referral views. These hooks format search parameters and leverage SWR with disabled focus revalidation to query content statistics dynamically.
Sources: [apps/web/ui/partners/bounties/use-social-content.ts:11-32](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/use-social-content.ts#L11-L32), [apps/web/app/ee/app.dub.co/embed/referrals/bounties/use-embed-social-content.ts:13-47](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/app.dub.co/embed/referrals/bounties/use-embed-social-content.ts#L13-L47)

Once content metadata is retrieved, `evaluateSocialContentRequirements` evaluates whether a post complies with campaign rules by checking two primary conditions:
- `isPostedFromYourAccount`: Validates that the partner platform identifier matches the fetched content handle case-insensitively, and that the platform is verified.
- `isAfterStartDate`: Confirms that the content's `publishedAt` timestamp is not before the bounty's `startsAt` date.

Sources: [apps/web/ui/partners/bounties/evaluate-social-content-requirements.ts:8-32](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/evaluate-social-content-requirements.ts#L8-L32)

### Platform URL Validation and Embedding Previews

The `BountySocialContentPreview` component renders native iframe embeds across supported social platforms by parsing submission URLs and transforming them into platform-specific embed URLs and aspect ratios via helper functions.
Sources: [apps/web/ui/partners/bounties/bounty-social-content-preview.tsx:27-153](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/bounty-social-content-preview.tsx#L27-L153)

| Social Platform | Target Hostnames | Embed URL Generation Pattern | Aspect Ratio |
| :--- | :--- | :--- | :--- |
| `youtube` | `youtu.be`, `youtube.com`, `m.youtube.com` | `https://www.youtube.com/embed/{id}` (supports standard watch URLs and shorts) | `aspect-video` or `aspect-[9/16]` |
| `instagram` | `instagram.com`, `m.instagram.com` | `https://www.instagram.com/p/{code}/embed/` or `/reel/{code}/embed/` | `aspect-square` or `aspect-[9/16]` |
| `tiktok` | `tiktok.com`, `m.tiktok.com`, `vm.tiktok.com` | `https://www.tiktok.com/embed/v2/{videoId}` | `aspect-[9/16]` |
| `twitter` | `twitter.com`, `x.com` | `https://platform.twitter.com/embed/Tweet.html?id={tweetId}` | `aspect-square` |
| `linkedin` | `linkedin.com` | `https://www.linkedin.com/embed/feed/update/urn:li:activity:{activityId}` | `aspect-video` |

Sources: [apps/web/ui/partners/bounties/bounty-social-content-preview.tsx:35-147](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/bounty-social-content-preview.tsx#L35-L147)

> [!CAUTION]
> If a submission URL does not match the expected platform hostnames or lacks required path identifiers like video IDs or shortcodes, `getSocialContentEmbedUrl` returns `null`, causing the preview component to render nothing.
> Sources: [apps/web/ui/partners/bounties/bounty-social-content-preview.tsx:31-118](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/bounty-social-content-preview.tsx#L31-L118)

## Partner Submission and Validation Flow

### Overview

The partner bounty submission flow governs how creators submit evidence of completion for manual and social bounties through interactive claim sheets and API endpoints. The submission lifecycle is managed by the `BountySubmissionHandler` class, which handles requests sent to the embed route and executes sequential validation, persistence, and notification routines.
Sources: [apps/web/app/ee/api/embed/referrals/bounties/bountyId/submissions/route.ts:9-39](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/embed/referrals/bounties/%5BbountyId%5D/submissions/route.ts#L9-L39), [apps/web/lib/bounty/api/create-bounty-submission.ts:48-111](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/create-bounty-submission.ts#L48-L111)

The submission execution pipeline flows sequentially through several distinct phases:
`fetchBountyAndEnrollment()` → `resolvePeriodNumber()` → `validateEligibility()` → `validateRequirements()` → `validateFiles()` → `validateSocialContent()` → `mergeSubmissionData()` → `persist()` → `sendNotifications()`
Sources: [apps/web/lib/bounty/api/create-bounty-submission.ts:91-111](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/create-bounty-submission.ts#L91-L111)

### Enrollment and Eligibility Checks

Before persisting any entry, `BountySubmissionHandler` checks partner enrollment state and campaign parameters. Performance bounties are blocked at the API level since they track automatically rather than via partner submissions. Furthermore, social metrics bounties reject draft saves entirely, requiring direct final submissions.
Sources: [apps/web/lib/bounty/api/create-bounty-submission.ts:245-310](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/create-bounty-submission.ts#L245-L310)

> [!WARNING]
> If a partner attempts to save a draft for a bounty that has social metrics enabled, `validateEligibility()` throws a bad request error preventing draft persistence.
> Sources: [apps/web/lib/bounty/api/create-bounty-submission.ts:303-310](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/create-bounty-submission.ts#L303-L310)

### URL Domain Enforcement and File Security

When requirements specify URL submissions, the handler enforces strict domain filtering and file storage boundaries to prevent malicious payloads. 
Sources: [apps/web/lib/bounty/api/create-bounty-submission.ts:367-435](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/create-bounty-submission.ts#L367-L435)

- **Domain Filtering**: `validateUrlDomains` strips `www` prefixes and ensures submitted URLs match or are subdomains of the allowed domains list configured on the bounty requirements.
- **File Validation**: `validateFiles` parses uploaded file URLs against the Cloudflare R2 storage origin and verifies that pathnames strictly start with the expected prefix `/programs/{programId}/bounties/{bountyId}/submissions/{partnerId}/`.

Sources: [apps/web/lib/bounty/api/create-bounty-submission.ts:368-435](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/create-bounty-submission.ts#L368-L435)

| Validation Phase | Target Field / Input | Enforcement Rule | Error Code |
| :--- | :--- | :--- | :--- |
| Eligibility | `bounty.type` | Must not be `performance` | `forbidden` |
| Period Check | `periodNumber` | Must fall within active campaign window and `maxSubmissions` | `bad_request` |
| Image Requirement | `files` | Must include at least one file if `submissionRequirements.image` is set | `unprocessable_entity` |
| URL Requirement | `urls` | Must include at least one URL if `submissionRequirements.url` is set | `unprocessable_entity` |
| Domain Enforcement | `urls` | Host must match `submissionRequirements.url.domains` whitelist | `unprocessable_entity` |
| File Storage | `files[].url` | Origin must match `R2_URL` and pathname must start with expected partner submission path | `unprocessable_entity` |

Sources: [apps/web/lib/bounty/api/create-bounty-submission.ts:245-435](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/create-bounty-submission.ts#L245-L435)

### Handling Claim Forms and State Management

The frontend claim sheet component (`ClaimBountySheetContent`) synchronizes form state using React Hook Form and manages asynchronous submission actions via `useAction`. It evaluates real-time requirements such as verifying connected social accounts and confirming post dates using `SocialContentUrlField` and `SocialContentRequirementChecks`.
Sources: [apps/web/ui/partners/bounties/bounty-social-content.tsx:16-186](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/bounty-social-content.tsx#L16-L186), [apps/web/ui/partners/bounties/claim-bounty-sheet.tsx:364-581](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/claim-bounty-sheet.tsx#L364-L581)

> [!NOTE]
> The claim sheet disables submission controls whenever file uploads are active, social content is currently verifying, or social requirements remain unmet.
> Sources: [apps/web/ui/partners/bounties/claim-bounty-sheet.tsx:573-580](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/claim-bounty-sheet.tsx#L573-L580)

## Metric Synchronization and Cron Jobs

### Overview

The metric synchronization and cron subsystem keeps campaign engagement counts updated across active submissions. It operates via individual synchronization endpoints and scheduled background cron jobs that fetch platform metrics, evaluate earning caps, update submission records, and notify partners upon milestone completion.
Sources: [apps/web/app/ee/api/bounties/bountyId/sync-social-metrics/route.ts:1-224](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/bounties/%5BbountyId%5D/sync-social-metrics/route.ts#L1-L224), [apps/web/app/ee/api/cron/bounties/sync-social-metrics/route.ts:1-258](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/bounties/sync-social-metrics/route.ts#L1-L258)

### Synchronization Call-Chain Execution

When a synchronization request is processed for a specific submission, the API traverses a strict evaluation and validation chain. 

```mermaid
sequenceDiagram
  autonumber
  participant POST as POST Route
  participant HRSEC as hasReachedSocialMetricsEarningCap
  participant GSMEC as getSocialMetricsEarningCap
  participant GSMM as getSocialMetricsMilestones

  POST->>HRSEC: { bounty, submission }
  HRSEC->>GSMEC: getSocialMetricsEarningCap(bounty)
  GSMEC->>GSMM: getSocialMetricsMilestones(bounty)
  GSMM-->>GSMEC: milestone list
  GSMEC-->>HRSEC: earningCap threshold
  HRSEC-->>POST: boolean result
```

1. `POST` receives the incoming request to sync social metrics and extracts the target submission.
Sources: [apps/web/app/ee/api/bounties/bountyId/sync-social-metrics/route.ts:30-36](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/bounties/%5BbountyId%5D/sync-social-metrics/route.ts#L30-L36)
2. `hasReachedSocialMetricsEarningCap` checks if the submission's current metric count meets or exceeds the campaign cap.
Sources: [apps/web/lib/bounty/social-metrics-milestones.ts:95-109](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/social-metrics-milestones.ts#L95-L109)
3. `getSocialMetricsEarningCap` retrieves the highest valid threshold from the milestone list.
Sources: [apps/web/lib/bounty/social-metrics-milestones.ts:84-92](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/social-metrics-milestones.ts#L84-L92)
4. `getSocialMetricsMilestones` computes all payable milestones including base rewards and incremental bonus tiers in ascending order.
Sources: [apps/web/lib/bounty/social-metrics-milestones.ts:33-81](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/social-metrics-milestones.ts#L33-L81)

> [!TIP]
> During bounty-wide synchronization where no `submissionId` is supplied, the `POST` route offloads processing entirely to QStash by publishing a background job to `/api/cron/bounties/sync-social-metrics`.
> Sources: [apps/web/app/ee/api/bounties/bountyId/sync-social-metrics/route.ts:87-95](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/bounties/%5BbountyId%5D/sync-social-metrics/route.ts#L87-L95)

### Cron Job Queueing and Batching

Scheduled cron routes automate the periodic sweep of active bounties and submissions. The queueing endpoint (`GET`) identifies active submission bounties containing social metrics requirements and chunks them into batches of 100 before dispatching them to QStash.
Sources: [apps/web/app/ee/api/cron/bounties/queue-sync-social-metrics/route.ts:11-45](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/bounties/queue-sync-social-metrics/route.ts#L11-L45)

The worker cron route (`POST`) processes submissions in increments defined by `SUBMISSION_BATCH_SIZE`. It queries up to 50 non-approved and non-rejected submissions ordered ascending by identifier, applies cursor pagination via `startingAfter`, and queues subsequent batches automatically if the batch limit is reached.
Sources: [apps/web/app/ee/api/cron/bounties/sync-social-metrics/route.ts:29-120](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/bounties/sync-social-metrics/route.ts#L29-L120), [apps/web/app/ee/api/cron/bounties/sync-social-metrics/route.ts:231-246](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/bounties/sync-social-metrics/route.ts#L231-L246)

| Cron Route Path | HTTP Method | Batch Size / Limit | Trigger / Queue Name | Primary Action |
| :--- | :--- | :--- | :--- | :--- |
| `/api/cron/bounties/queue-sync-social-metrics` | `GET` | 100 bounties per chunk | `sync-bounty-social-metrics` | Queries active social metrics bounties and enqueues batch background jobs |
| `/api/cron/bounties/sync-social-metrics` | `POST` | 50 submissions per batch (`SUBMISSION_BATCH_SIZE`) | QStash publishing (`startingAfter` cursor) | Fetches social metrics updates, updates submission records in transactions, and sends batch completion emails |

Sources: [apps/web/app/ee/api/cron/bounties/queue-sync-social-metrics/route.ts:13-44](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/bounties/queue-sync-social-metrics/route.ts#L13-L44), [apps/web/app/ee/api/cron/bounties/sync-social-metrics/route.ts:29-255](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/bounties/sync-social-metrics/route.ts#L29-L255)

### Design Trade-Offs

| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| **Cursor pagination with fixed batch sizes (`SUBMISSION_BATCH_SIZE = 50`)** | Prevents memory exhaustion and database query timeouts on bounties with thousands of partner submissions. | Requires chained asynchronous QStash job triggers to complete synchronization across all pages. |
| **Asynchronous background queuing via QStash** | Offloads heavy multi-submission scraping and database transactions away from client-facing API response cycles. | Introduces eventual consistency in social metric counters visible to partners and creators. |
| **`Promise.allSettled` for social content fetching** | Ensures individual scraping failures or platform timeouts do not abort synchronization for other partner submissions. | Requires robust post-processing filters to validate settled fulfillment states and integer metric types. |

Sources: [apps/web/app/ee/api/bounties/bountyId/sync-social-metrics/route.ts:87-104](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/bounties/%5BbountyId%5D/sync-social-metrics/route.ts#L87-L104), [apps/web/app/ee/api/cron/bounties/sync-social-metrics/route.ts:113-200](https://github.com/blade47/dub/blob/HEAD/apps/web/app/(ee)/api/cron/bounties/sync-social-metrics/route.ts#L113-L200), [apps/web/lib/bounty/api/get-social-metrics-updates.ts:51-92](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/get-social-metrics-updates.ts#L51-L92)

## Milestone Evaluation and Cap Calculation

### Overview

The milestone evaluation and cap calculation engine processes bounty details to construct performance tiers, calculate earning limits, determine pending rewards, and format commission strings. Functions such as `getSocialMetricsMilestones` resolve bounty particulars using `resolveBountyDetails`, extracting `minCount` and `incrementalBonus` properties. It initializes a base tier milestone starting at threshold `0` up to `minCount` with the full `rewardAmount`. When a valid incremental bonus structure exists containing `incrementCount`, `bonusPerIncrement`, and `maxCount`, a `for` loop iterates from `minCount + incrementCount` up to `maxCount` in steps of `incrementCount`, pushing subsequent milestones with a `fromThreshold` of `t - incrementCount` and `rewardAmount` set to `bonusPerIncrement`.
Sources: [apps/web/lib/bounty/social-metrics-milestones.ts:33-81](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/social-metrics-milestones.ts#L33-L81)

### Milestone and Cap Functions

The milestone engine provides utility functions to query earning limits, sync states, and commission descriptions. 

| Function Name | Parameters | Return Type | Description |
| :--- | :--- | :--- | :--- |
| `getSocialMetricsMilestones` | `bounty: BountyInfoInput \| undefined \| null` | `SocialMetricsMilestone[]` | Assembles all payable milestones (base reward + bonus increments) in ascending order. |
| `getSocialMetricsEarningCap` | `bounty: BountyInfoInput \| undefined \| null` | `number \| null` | Returns the highest metric count earning a reward (the last milestone threshold), or `null`. |
| `hasReachedSocialMetricsEarningCap` | `{ bounty, submission }` | `boolean` | Checks if a submission's live metric count meets or exceeds the earning cap. |
| `getPendingSocialMetricsMilestones` | `{ bounty, submission }` | `SocialMetricsMilestone[]` | Filters milestones where threshold is less than or equal to `socialMetricCount` and greater than `approvedSocialMetricThreshold`. |
| `isSocialMetricsMilestoneApproved` | `{ milestone, submission }` | `boolean` | Verifies if a milestone has been approved or paid based on thresholds or legacy status. |
| `buildMilestonesCommissionDescription` | `{ bountyName, metric, milestone }` | `string` | Formats a commission description string using `nFormatter`. |
| `groupSocialMetricsMilestones` | `milestones: SocialMetricsMilestone[]` | Grouped milestone array | Merges consecutive milestones sharing identical rewards into ranges while keeping the base tier isolated. |
| `getSocialMetricsMilestoneStatus` | `{ milestone, submission }` | `SocialMetricsMilestoneStatus` | Resolves the display status (`"approved"`, `"pending"`, `"inProgress"`, `"rejected"`). |

Sources: [apps/web/lib/bounty/social-metrics-milestones.ts:33-221](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/social-metrics-milestones.ts#L33-L221)

### Call-Chain Execution Walkthrough

Milestone state computation follows a strict execution flow from raw inputs to UI state properties via `useSocialMetricsMilestones`:

1. `useSocialMetricsMilestones()` extracts the social metric name using `resolveBountyDetails(bounty)?.socialMetrics?.metric`.
2. `getSocialMetricsMilestones()` evaluates the base tier and loops through `incrementalBonus` tiers if `incrementCount > 0`.
3. `getPendingSocialMetricsMilestones()` filters the resulting milestones where `threshold <= socialMetricCount && threshold > approvedSocialMetricThreshold`.
4. `getSocialMetricsEarningCap()` retrieves the final threshold from the array as the campaign earning ceiling.
5. `hasReachedSocialMetricsEarningCap()` checks if `submission.socialMetricCount >= earningCap` to halt further sync tasks.

Sources: [apps/web/lib/bounty/social-metrics-milestones.ts:33-109](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/social-metrics-milestones.ts#L33-L109), [apps/web/ui/partners/bounties/use-social-metrics-milestones.ts:11-58](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/use-social-metrics-milestones.ts#L11-L58)

> [!NOTE]
> Legacy approved submissions containing a `null` approved threshold treat every reached milestone as paid by evaluating `submission.status === "approved" && milestone.threshold <= (submission.socialMetricCount ?? 0)`.
> Sources: [apps/web/lib/bounty/social-metrics-milestones.ts:128-144](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/social-metrics-milestones.ts#L128-L144)

> [!TIP]
> The `groupSocialMetricsMilestones` function leaves the base milestone at `fromThreshold: 0` on its own while grouping subsequent sequential tiers that share identical reward amounts and contiguous thresholds.
> Sources: [apps/web/lib/bounty/social-metrics-milestones.ts:167-198](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/social-metrics-milestones.ts#L167-L198)

## Submission Review and Reward Payouts

### Overview

The submission review and reward payout lifecycle transitions partner entries from pending administrative evaluation to approved commissions. Reviewers inspect claims, media attachments, and live social metrics using dedicated administration sheets or automated workflows. Upon approval, system routines queue partner commissions, write comprehensive audit logs, and dispatch notifications via email templates.

Sources: [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/bountyId/bounty-submission-details-sheet.tsx:421-520](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/%5BbountyId%5D/bounty-submission-details-sheet.tsx#L421-L520), [apps/web/lib/api/workflows/award-bounty/execute.ts:212-254](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/workflows/award-bounty/execute.ts#L212-L254), [apps/web/lib/bounty/api/approve-bounty-submission.ts:184-371](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/approve-bounty-submission.ts#L184-L371)

### Administrative Review Interface

Reviewers inspect individual submissions through `BountySubmissionDetailsSheet`, which surfaces live metric progress, attached files, uploaded URLs, and rejection metadata. When a campaign incorporates social metrics, reviewers can trigger manual sync actions via `refreshSubmissionSocialMetrics` or review pending engagement milestones.

```typescript
export function SocialContentPreview({
  bounty,
  submission,
}: {
  bounty: PartnerBountyProps;
  submission: PartnerBountySubmission;
}) {
  const bountyInfo = resolveBountyDetails(bounty);
  const { socialMetrics, socialPlatform } = bountyInfo ?? {};
  const url = submission.urls?.[0] ?? "";
  if (!socialMetrics || !socialPlatform || !url) {
    return null;
  }
  const socialMetricCount = submission.socialMetricCount ?? 0;
  const minCount = socialMetrics.minCount ?? 0;
  const percent =
    minCount > 0 ? Math.min((socialMetricCount / minCount) * 100, 100) : 100;
  const isComplete = percent >= 100;
  const PlatformIcon = PLATFORM_ICONS[socialPlatform.value];
  const lastSyncedAt = submission.socialMetricsLastSyncedAt;
  return (
    <div className="flex flex-col gap-2">
      <div className="flex items-center justify-between">
        <h2 className="text-content-emphasis text-base font-semibold">
          Submitted content
        </h2>
        {lastSyncedAt && (
          <span className="text-content-subtle text-xs font-medium">
            Last sync{" "}
            {formatDistanceToNow(new Date(lastSyncedAt), { addSuffix: true })}
          </span>
        )}
      </div>
      {/* Renders progress bar, platform icon, and social preview component */}
    </div>
  );
}
```

Sources: [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/bountyId/bounty-submission-details-sheet.tsx:421-488](https://github.com/blade47/dub/blob/HEAD/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/%5BbountyId%5D/bounty-submission-details-sheet.tsx#L421-L488), [apps/web/ui/partners/bounties/bounty-submission-details-sheet.tsx:47-118](https://github.com/blade47/dub/blob/HEAD/apps/web/ui/partners/bounties/bounty-submission-details-sheet.tsx#L47-L118)

### Approval Workflows and Side Effects

The approval engine processes standard submissions and social milestone tiers through targeted execution functions. When approving social metrics via `approveSocialMetricsMilestones`, the system validates pending milestones, calculates incremental reward amounts, updates database states using Prisma transactions, queues commissions, and executes side-effects asynchronously.

```typescript
async function approveSocialMetricsMilestones({
  submissionId,
  submission,
  metric,
  user,
}: {
  submissionId: string;
  submission: SubmissionToApprove;
  metric: string;
  user: Session["user"];
}) {
  const { bounty } = submission;
  const pendingMilestones = getPendingSocialMetricsMilestones({
    bounty,
    submission,
  });
  const earningCap = getSocialMetricsEarningCap(bounty);

  if (pendingMilestones.length === 0 || earningCap == null) {
    throw new DubApiError({
      code: "bad_request",
      message:
        "The partner hasn't reached a new milestone for this bounty yet, so there is nothing to approve.",
    });
  }

  const firstPendingMilestone = pendingMilestones[0];
  const approvedThreshold =
    pendingMilestones[pendingMilestones.length - 1].threshold;
  const completesEarningCap = approvedThreshold >= earningCap;

  const approvedSubmission = await prisma.bountySubmission.update({
    where: {
      id: submissionId,
      approvedSocialMetricThreshold: submission.approvedSocialMetricThreshold,
      status: {
        notIn: [
          BountySubmissionStatus.approved,
          BountySubmissionStatus.draft,
        ],
      },
    },
    data: {
      approvedSocialMetricThreshold: approvedThreshold,
      status: completesEarningCap ? "approved" : "submitted",
      reviewedAt: new Date(),
      userId: user.id,
      rejectionNote: null,
      rejectionReason: null,
    },
    include: submissionApprovalInclude,
  });

  const rewardAmount = pendingMilestones.reduce(
    (sum, { rewardAmount }) => sum + rewardAmount,
    0,
  );

  const description = buildMilestonesCommissionDescription({
    bountyName: bounty.name,
    metric,
    milestone: {
      fromThreshold: firstPendingMilestone.fromThreshold,
      threshold: approvedThreshold,
    },
  });

  await queuePartnerCommissionCreation({
    event: "custom",
    partnerId: submission.partnerId,
    programId: submission.programId,
    amount: rewardAmount,
    quantity: 1,
    userId: user.id,
    source: CommissionSource.user,
    description,
    bountySubmissionId: submissionId,
    metadata: {
      socialMetrics: {
        metric,
        fromThreshold: firstPendingMilestone.fromThreshold,
        threshold: approvedThreshold,
        milestones: pendingMilestones,
      },
    },
  });

  runApprovalSideEffects({
    approvedSubmission,
    bounty,
    user,
    description: completesEarningCap
      ? `Bounty submission approved for ${approvedSubmission.partner.id}`
      : `Bounty milestones approved up to ${nFormatter(approvedThreshold, { full: true })} ${metric} for ${approvedSubmission.partner.id}`,
    notifyPartner: completesEarningCap,
  });

  return BountySubmissionSchema.parse(approvedSubmission);
}
```

Sources: [apps/web/lib/bounty/api/approve-bounty-submission.ts:204-316](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/approve-bounty-submission.ts#L204-L316)

> [!WARNING]
> Database updates for social metric milestones explicitly guard against race conditions using P2025 error catching on threshold matches, throwing a `bad_request` API error if the submission has already been reviewed or processed concurrently.
> Sources: [apps/web/lib/bounty/api/approve-bounty-submission.ts:237-270](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/approve-bounty-submission.ts#L237-L270)

### Call-Chain Execution Walkthrough

The reward approval sequence follows a deterministic flow from administrative trigger to audit logging and partner email dispatch:

1. `approveSocialMetricsMilestones()` or standard approval handlers evaluate incoming submission payloads against active Prisma records.
2. `getPendingSocialMetricsMilestones()` identifies unapproved performance tiers or social engagement thresholds.
3. `prisma.bountySubmission.update()` writes the new `approvedSocialMetricThreshold` and adjusts the submission status (`submitted` or `approved`).
4. `queuePartnerCommissionCreation()` logs the commission entry with source `CommissionSource.user` and assigns the associated `bountySubmissionId`.
5. `runApprovalSideEffects()` uses `waitUntil()` to wrap concurrent execution of `recordAuditLog()` and optional partner notification emails via `sendEmail()`.

Sources: [apps/web/lib/bounty/api/approve-bounty-submission.ts:184-371](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/approve-bounty-submission.ts#L184-L371)

> [!TIP]
> The `runApprovalSideEffects` function delegates tasks to `waitUntil()`, ensuring audit logs and notification emails resolve asynchronously without delaying the synchronous API response returned to the client.
> Sources: [apps/web/lib/bounty/api/approve-bounty-submission.ts:318-370](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/approve-bounty-submission.ts#L318-L370)

### Terminal Submission States and Notifications

Performance-based bounties executed via automated workflows check terminal status reasons and partner eligibility before transitioning submission rows. Once conditions are satisfied, workflow actions transition the submission status to `submitted`, record the completion timestamp, and dispatch notification emails to both the partner and program owners.

| Terminal Status | Action Type | Reason Mapping | Notification Template |
| :--- | :--- | :--- | :--- |
| `submitted` | Workflow Execution | `"finished"` | `BountyCompleted`, `NewBountySubmission` |
| `approved` | Manual / Milestone Approval | `"been awarded"` | `BountyApproved` |
| `rejected` | Administrative Rejection | `"been rejected"` | None (Rejection Note/Reason recorded) |

Sources: [apps/web/lib/api/workflows/award-bounty/execute.ts:23-30](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/workflows/award-bounty/execute.ts#L23-L30), [apps/web/lib/api/workflows/award-bounty/execute.ts:212-254](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/api/workflows/award-bounty/execute.ts#L212-L254), [apps/web/lib/bounty/api/approve-bounty-submission.ts:318-371](https://github.com/blade47/dub/blob/HEAD/apps/web/lib/bounty/api/approve-bounty-submission.ts#L318-L371)

## Related

- [Partner Program Management](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/affiliate-platform/partner-program-management)
- [Commission Rules and Rewards](https://www.doc0.dev/docs/934e554a-e6a1-476f-bb2f-23e62d86c3fd/technical/affiliate-platform/commission-rules-and-rewards)


## Sitemap

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