---
title: "Bounties & Submissions"
description: "The bounties and submissions subsystem in Dub powers partner incentive programs by supporting both performance-based tracking and social content verification apps/web/prisma/schema/bounty.prisma:1-..."
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/bounties-submissions"
---

<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/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/bounties/%5BbountyId%5D/sync-social-metrics/route.ts)
- [apps/web/app/ee/api/cron/bounties/sync-social-metrics/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/cron/bounties/sync-social-metrics/route.ts)
- [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/bountyId/bounty-submission-details-sheet.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/%5BbountyId%5D/bounty-submission-details-sheet.tsx)
- [apps/web/lib/bounty/api/create-bounty-submission.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/create-bounty-submission.ts)
- [apps/web/app/ee/api/bounties/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/bounties/route.ts)
- [apps/web/app/ee/api/cron/bounties/upsert-draft-submissions/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/cron/bounties/upsert-draft-submissions/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/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/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/partner-profile/programs/programId/bounties/bountyId/social-content-stats/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/bounties/%5BbountyId%5D/social-content-stats/route.ts)
- [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/bountyId/bounty-submissions-table.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/%5BbountyId%5D/bounty-submissions-table.tsx)
- [apps/web/lib/bounty/api/get-social-metrics-updates.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/get-social-metrics-updates.ts)
- [apps/web/app/ee/api/embed/referrals/bounties/bountyId/social-content-stats/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/embed/referrals/bounties/%5BbountyId%5D/social-content-stats/route.ts)
- [apps/web/app/ee/api/cron/bounties/queue-sync-social-metrics/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/cron/bounties/queue-sync-social-metrics/route.ts)
- [apps/web/app/ee/app.dub.co/embed/referrals/bounties/submission-form.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/app.dub.co/embed/referrals/bounties/submission-form.tsx)
- [apps/web/lib/zod/schemas/bounties.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/zod/schemas/bounties.ts)
- [apps/web/lib/bounty/api/approve-bounty-submission.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/approve-bounty-submission.ts)
- [apps/web/ui/partners/bounties/bounty-submission-details-sheet.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/bounty-submission-details-sheet.tsx)
- [apps/web/lib/bounty/social-metrics-milestones.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/social-metrics-milestones.ts)
- [apps/web/ui/partners/bounties/bounty-social-content.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/bounty-social-content.tsx)
- [apps/web/app/ee/api/embed/referrals/bounties/bountyId/submissions/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/embed/referrals/bounties/%5BbountyId%5D/submissions/route.ts)
- [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/add-edit-bounty/bounty-criteria.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.tsx)
- [apps/web/app/ee/app.dub.co/embed/referrals/bounties/detail.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/app.dub.co/embed/referrals/bounties/detail.tsx)
- [apps/web/ui/partners/bounties/claim-bounty-sheet.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/claim-bounty-sheet.tsx)
- [apps/web/app/ee/app.dub.co/embed/referrals/bounties/submission-detail.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/app.dub.co/embed/referrals/bounties/submission-detail.tsx)
- [apps/web/ui/partners/bounties/use-social-metrics-milestones.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/use-social-metrics-milestones.ts)
- [apps/web/ui/partners/bounties/bounty-submission-requirements.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/bounty-submission-requirements.tsx)
- [apps/web/lib/bounty/api/trigger-draft-bounty-submissions.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/trigger-draft-bounty-submissions.ts)
- [apps/web/app/ee/app.dub.co/embed/referrals/bounties/index.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/app.dub.co/embed/referrals/bounties/index.tsx)
- [apps/web/prisma/schema/bounty.prisma](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma)
- [apps/web/ui/partners/bounties/evaluate-social-content-requirements.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/evaluate-social-content-requirements.ts)
- [apps/web/ui/partners/bounties/bounty-social-content-preview.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/bounty-social-content-preview.tsx)
</details>

## Overview

The bounties and submissions subsystem in Dub powers partner incentive programs by supporting both performance-based tracking and social content verification [apps/web/prisma/schema/bounty.prisma:1-4](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L1-L4), [apps/web/lib/bounty/api/create-bounty-submission.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/create-bounty-submission.ts). It enables program operators to define flexible reward structures, configure automated submissions [apps/web/app/ee/api/cron/bounties/upsert-draft-submissions/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/cron/bounties/upsert-draft-submissions/route.ts), and synchronize external metrics via scheduled cron jobs [apps/web/app/ee/api/cron/bounties/sync-social-metrics/route.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/cron/bounties/sync-social-metrics/route.ts). Partners can submit proof through file uploads, custom URLs, or social media links tied to verified accounts, while program managers inspect submissions [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/[bountyId]/bounty-submission-details-sheet.tsx](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/%5BbountyId%5D/bounty-submission-details-sheet.tsx), manage milestone approvals [apps/web/lib/bounty/api/approve-bounty-submission.ts](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/approve-bounty-submission.ts), and trigger commission payouts.

## Bounty Data Models and Periodicity

### Bounty Types and Performance Scopes

The `Bounty` data model supports two primary variants defined by `BountyType`: `performance` tracking and manual `submission` verification [apps/web/prisma/schema/bounty.prisma:1-4](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L1-L4). Performance bounties evaluate partner metrics using a `BountyPerformanceScope` which can be set to either `new` or `lifetime` activity [apps/web/prisma/schema/bounty.prisma:6-9](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L6-L9). Lifecycle and availability are further controlled by `BountyStartMode`, supporting `absolute` calendar dates (`startsAt` and `endsAt`) or `relative` durations calculated via `endsAfterDays` [apps/web/prisma/schema/bounty.prisma:32-35](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L32-L35), [apps/web/prisma/schema/bounty.prisma:44-47](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L44-L47).

| Enum / Type | Values | Description |
| :--- | :--- | :--- |
| `BountyType` | `performance`, `submission` | Distinguishes automated performance tracking from manual proof submissions [apps/web/prisma/schema/bounty.prisma:1-4](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L1-L4). |
| `BountyPerformanceScope` | `new`, `lifetime` | Determines whether performance metrics evaluate new actions or cumulative history [apps/web/prisma/schema/bounty.prisma:6-9](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L6-L9). |
| `BountyStartMode` | `absolute`, `relative` | Controls schedule boundaries using fixed timestamps or relative day offsets [apps/web/prisma/schema/bounty.prisma:32-35](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L32-L35). |

Sources: [apps/web/prisma/schema/bounty.prisma:1-35](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L1-L35)

> [!NOTE]
> During updates via `updateBountySchema`, the bounty `type` and `performanceScope` are strictly omitted and cannot be modified after initial creation [apps/web/lib/zod/schemas/bounties.ts:221-226](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/zod/schemas/bounties.ts#L221-L226).

Sources: [apps/web/lib/zod/schemas/bounties.ts:221-226](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/zod/schemas/bounties.ts#L221-L226)

### Submission Lifecycles and Frequencies

Bounty submissions progress through specific lifecycle states defined by `BountySubmissionStatus`, accompanied by categorized failure logging via `BountySubmissionRejectionReason` [apps/web/prisma/schema/bounty.prisma:11-24](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L11-L24). Periodicity and recurrence are structured using `BountySubmissionFrequency` alongside a 1-indexed `periodNumber` [apps/web/prisma/schema/bounty.prisma:26-30](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L26-L30), [apps/web/prisma/schema/bounty.prisma:110](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L110). Uniqueness constraints on submissions enforce a strict one-submission-per-period rule per partner and bounty [apps/web/prisma/schema/bounty.prisma:124](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L124).

| Status / Reason Enum | Values | Purpose |
| :--- | :--- | :--- |
| `BountySubmissionStatus` | `draft`, `submitted`, `approved`, `rejected` | Represents the execution and review state of a partner's submission [apps/web/prisma/schema/bounty.prisma:11-16](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L11-L16). |
| `BountySubmissionRejectionReason` | `invalidProof`, `duplicateSubmission`, `outOfTimeWindow`, `didNotMeetCriteria`, `other` | Categorizes why a submission failed operator review [apps/web/prisma/schema/bounty.prisma:18-24](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L18-L24). |
| `BountySubmissionFrequency` | `day`, `week`, `month` | Defines recurring temporal intervals for periodic bounty submissions [apps/web/prisma/schema/bounty.prisma:26-30](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L26-L30). |

Sources: [apps/web/prisma/schema/bounty.prisma:11-30](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L11-L30)

> [!CAUTION]
> The database schema enforces a unique composite index `@@unique([bountyId, partnerId, periodNumber])` on `BountySubmission`, preventing duplicate active or draft submissions within the exact same periodic window [apps/web/prisma/schema/bounty.prisma:124](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L124).

Sources: [apps/web/prisma/schema/bounty.prisma:124](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/prisma/schema/bounty.prisma#L124)

## Partner Submission Ingestion and Validation

### Submission Pipeline Execution Flow

The `BountySubmissionHandler` coordinates submission ingestion and validation [apps/web/lib/bounty/api/create-bounty-submission.ts:48-111](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/create-bounty-submission.ts#L48-L111). The `submit` method executes a sequential series of validation checks and state operations before persisting the record:

```mermaid
flowchart TD
  A["fetchBountyAndEnrollment()"] --> B["resolvePeriodNumber()"]
  B --> C["validateEligibility()"]
  C --> D["validateRequirements()"]
  D --> E["validateFiles()"]
  E --> F["validateSocialContent()"]
  F --> G["mergeSubmissionData()"]
  G --> H["persist()"]
  H --> I["sendNotifications(submission)"]
```
Sources: [apps/web/lib/bounty/api/create-bounty-submission.ts:91-111](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/create-bounty-submission.ts#L91-L111)

> [!WARNING]
> If a submission exists for the `finalPeriodNumber`, `persist()` performs an update on the existing record's ID instead of creating a duplicate row [apps/web/lib/bounty/api/create-bounty-submission.ts:587-607](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/create-bounty-submission.ts#L587-L607).

Sources: [apps/web/lib/bounty/api/create-bounty-submission.ts:587-607](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/create-bounty-submission.ts#L587-L607)

### Requirement Rules and UI Integration

Partners interact with the submission pipeline via EmbedBountySubmissionForm or the claim bounty sheet UI [apps/web/app/ee/app.dub.co/embed/referrals/bounties/submission-form.tsx:68-88](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/ee/app.dub.co/embed/referrals/bounties/submission-form.tsx#L68-L88), [apps/web/ui/partners/bounties/claim-bounty-sheet.tsx:364-369](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/claim-bounty-sheet.tsx#L364-L369). Requirements are parsed dynamically to enforce file uploads and URL constraints [apps/web/ui/partners/bounties/bounty-submission-requirements.tsx:5-38](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/bounty-submission-requirements.tsx#L5-L38).

| Requirement Type | Schema Field | Validation Rule |
| :--- | :--- | :--- |
| Image | `submissionRequirements.image` | Optional max limit; validates attached file array size when required [apps/web/ui/partners/bounties/bounty-submission-requirements.tsx:11-17](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/bounty-submission-requirements.tsx#L11-L17). |
| URL | `submissionRequirements.url` | Optional max limit and required allowed domain whitelist [apps/web/ui/partners/bounties/bounty-submission-requirements.tsx:19-28](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/bounty-submission-requirements.tsx#L19-L28). |
| Social Content | `bountyInfo.hasSocialMetrics` | Requires a connected partner account platform link posted after the bounty start date [apps/web/ui/partners/bounties/bounty-submission-requirements.tsx:30-35](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/bounty-submission-requirements.tsx#L30-L35). |

Sources: [apps/web/ui/partners/bounties/bounty-submission-requirements.tsx:5-38](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/bounty-submission-requirements.tsx#L5-L38)

> [!NOTE]
> Social metrics bounties automatically bypass manual file requirements and restrict URL inputs to a single content link, syncing external metrics directly during validation [apps/web/lib/bounty/api/create-bounty-submission.ts:543-561](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/create-bounty-submission.ts#L543-L561), [apps/web/lib/bounty/api/create-bounty-submission.ts:578-581](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/create-bounty-submission.ts#L578-L581).

Sources: [apps/web/lib/bounty/api/create-bounty-submission.ts:543-561](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/create-bounty-submission.ts#L543-L561), [apps/web/lib/bounty/api/create-bounty-submission.ts:578-581](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/create-bounty-submission.ts#L578-L581)

### Social Content Verification

Social content requirements evaluate whether a post belongs to the partner's verified platform handle and meets temporal boundaries [apps/web/ui/partners/bounties/evaluate-social-content-requirements.ts:8-32](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/evaluate-social-content-requirements.ts#L8-L32).

```typescript
export function evaluateSocialContentRequirements({
  content,
  bounty,
  partnerPlatform,
}: {
  content?: SocialContent | null;
  bounty: Pick<PartnerBountyProps, "startsAt">;
  partnerPlatform?: Pick<PartnerPlatformProps, "identifier" | "verifiedAt">;
}) {
  const isPostedFromYourAccount =
    !!content &&
    !!partnerPlatform &&
    !!partnerPlatform.verifiedAt &&
    partnerPlatform.identifier.toLowerCase() === content.handle?.toLowerCase();

  const isAfterStartDate =
    !!content?.publishedAt &&
    !!bounty.startsAt &&
    !isBefore(content.publishedAt, bounty.startsAt);

  return {
    isPostedFromYourAccount,
    isAfterStartDate,
  };
}
```
Sources: [apps/web/ui/partners/bounties/evaluate-social-content-requirements.ts:8-32](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/evaluate-social-content-requirements.ts#L8-L32)

## Social Content Metrics and Synchronization

### Stats Scraping and API Endpoints

Social content metrics require periodic scraping of external platform stats. Endpoints such as `GET /api/partner-profile/programs/[programId]/bounties/[bountyId]/social-content-stats` and `GET /api/embed/referrals/bounties/[bountyId]/social-content-stats` validate partner credentials, enforce rate limits via `RATELIMIT_POLICIES.socialContentStats`, verify bounty eligibility, and query external platform metrics using `getSocialContent`.

Sources: [apps/web/app/ee/api/partner-profile/programs/programId/bounties/bountyId/social-content-stats/route.ts:21-91](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/partner-profile/programs/%5BprogramId%5D/bounties/%5BbountyId%5D/social-content-stats/route.ts#L21-L91), [apps/web/app/ee/api/embed/referrals/bounties/bountyId/social-content-stats/route.ts:21-80](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/embed/referrals/bounties/%5BbountyId%5D/social-content-stats/route.ts#L21-L80)

> [!WARNING]
> Live metric scraping requests are rate-limited per partner identifier to prevent abuse of external scrapers when partners test or preview submission links.

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/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/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/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/embed/referrals/bounties/%5BbountyId%5D/social-content-stats/route.ts#L26-L29)

### Cron Scheduling and Queueing

The synchronization of social metrics runs periodically through background jobs managed via QStash. The `GET /api/cron/bounties/queue-sync-social-metrics` route queries active submission bounties containing social metrics requirements and chunks them into batches of 100 to enqueue jobs to `/api/cron/bounties/sync-social-metrics`.

Sources: [apps/web/app/ee/api/cron/bounties/queue-sync-social-metrics/route.ts:12-45](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/cron/bounties/queue-sync-social-metrics/route.ts#L12-L45)

```typescript
export const GET = withCron(async () => {
  const bounties = await prisma.bounty.findMany({
    where: {
      type: "submission",
      submissionRequirements: {
        path: "$.socialMetrics",
        not: Prisma.JsonNull,
      },
      ...buildBountyActivePeriodWhere(),
    },
    select: {
      id: true,
    },
  });

  if (bounties.length === 0) {
    return logAndRespond("No bounties to sync social metrics for.");
  }

  const chunks = chunk(bounties, 100);

  for (const chunk of chunks) {
    await enqueueBatchJobs(
      chunk.map((bounty) => ({
        queueName: "sync-bounty-social-metrics",
        url: `${APP_DOMAIN_WITH_NGROK}/api/cron/bounties/sync-social-metrics`,
        deduplicationId: bounty.id,
        body: {
          bountyId: bounty.id,
        },
      })),
    );
  }

  return logAndRespond(
    `Queued ${bounties.length} bounties to sync social metrics.`,
  );
});
```
Sources: [apps/web/app/ee/api/cron/bounties/queue-sync-social-metrics/route.ts:12-48](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/cron/bounties/queue-sync-social-metrics/route.ts#L12-L48)

### Synchronization Call-Chain Execution

When synchronizing metrics for a bounty or individual submission, the system evaluates earning caps and milestones.

1. `POST` request or cron handler invokes `hasReachedSocialMetricsEarningCap` with the bounty and submission.
2. `hasReachedSocialMetricsEarningCap` calls `getSocialMetricsEarningCap`.
3. `getSocialMetricsEarningCap` invokes `getSocialMetricsMilestones` to retrieve all payable milestones.
4. `getSocialMetricsMilestones` resolves bounty details and extracts configured thresholds and bonuses.

Sources: [apps/web/app/ee/api/bounties/bountyId/sync-social-metrics/route.ts:123-129](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/bounties/%5BbountyId%5D/sync-social-metrics/route.ts#L123-L129), [apps/web/lib/bounty/social-metrics-milestones.ts:84-109](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/social-metrics-milestones.ts#L84-L109)

```mermaid
sequenceDiagram
    participant Route as POST /api/bounties/[bountyId]/sync-social-metrics
    participant Milestones as hasReachedSocialMetricsEarningCap
    participant Cap as getSocialMetricsEarningCap
    participant List as getSocialMetricsMilestones

    Route->>Milestones: Check if submission reached earning cap
    Milestones->>Cap: Get maximum milestone threshold
    Cap->>List: Calculate all payable milestones
    List-->>Cap: Return milestone array
    Cap-->>Milestones: Return earning cap threshold
    Milestones-->>Route: Return boolean status
```
Sources: [apps/web/app/ee/api/bounties/bountyId/sync-social-metrics/route.ts:123-129](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/bounties/%5BbountyId%5D/sync-social-metrics/route.ts#L123-L129), [apps/web/lib/bounty/social-metrics-milestones.ts:33-92](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/social-metrics-milestones.ts#L33-L92)

## Milestone Calculation and Submission Approval

### Milestone Generation and Structure

The system calculates payable milestones for social metrics bounties by establishing an initial base tier followed by incremental bonus tiers. The function `getSocialMetricsMilestones` extracts the configured `minCount` and `rewardAmount` from bounty details to form the primary base milestone where `fromThreshold` is `0`. If an `incrementalBonus` object is defined with valid parameters including `incrementCount`, `bonusPerIncrement`, and `maxCount`, additional milestone tiers are generated sequentially starting from `minCount + incrementCount` up to `maxCount`. Each intermediate milestone records its preceding boundary in `fromThreshold`, the target metric count in `threshold`, and the payout in `rewardAmount`.

Sources: [apps/web/lib/bounty/social-metrics-milestones.ts:33-81](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/social-metrics-milestones.ts#L33-L81)

```typescript
export function getSocialMetricsMilestones(
  bounty: BountyInfoInput | undefined | null,
): SocialMetricsMilestone[] {
  const bountyInfo = resolveBountyDetails(bounty);
  const socialMetrics = bountyInfo?.socialMetrics;
  const rewardAmount = bounty?.rewardAmount ?? 0;

  if (!socialMetrics?.metric || !socialMetrics.minCount || rewardAmount <= 0) {
    return [];
  }

  const { minCount, incrementalBonus } = socialMetrics;

  const milestones: SocialMetricsMilestone[] = [
    {
      fromThreshold: 0,
      threshold: minCount,
      rewardAmount,
    },
  ];

  if (incrementalBonus) {
    const { incrementCount, bonusPerIncrement, maxCount } = incrementalBonus;

    const hasValidIncrementalBonus =
      incrementCount != null &&
      bonusPerIncrement != null &&
      maxCount != null &&
      incrementCount > 0;

    if (hasValidIncrementalBonus) {
      for (
        let t = minCount + incrementCount;
        t <= maxCount;
        t += incrementCount
      ) {
        milestones.push({
          fromThreshold: t - incrementCount,
          threshold: t,
          rewardAmount: bonusPerIncrement,
        });
      }
    }
  }

  return milestones;
}
```
Sources: [apps/web/lib/bounty/social-metrics-milestones.ts:33-81](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/social-metrics-milestones.ts#L33-L81)

> [!NOTE]
> The earning cap for a social metrics bounty is determined by `getSocialMetricsEarningCap`, which evaluates the `threshold` value of the final element in the generated milestones array. When a partner submission reaches or exceeds this metric count, `hasReachedSocialMetricsEarningCap` returns true and disables further synchronization needs.

Sources: [apps/web/lib/bounty/social-metrics-milestones.ts:84-109](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/social-metrics-milestones.ts#L84-L109)

### Submission Approval and Commission Queueing

When a program operator approves milestones for a submission through `approveSocialMetricsMilestones`, the system identifies pending tiers via `getPendingSocialMetricsMilestones` where the milestone threshold falls between the submission's current `socialMetricCount` and its previously `approvedSocialMetricThreshold`. The approval transaction updates the submission record, advancing the `approvedSocialMetricThreshold` to the highest pending milestone's threshold and setting the submission status to `approved` if `completesEarningCap` is met, or keeping it `submitted` for intermediate progress.

Sources: [apps/web/lib/bounty/api/approve-bounty-submission.ts:204-274](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/approve-bounty-submission.ts#L204-L274)

Concurrently, a descriptive commission entry is generated using `buildMilestonesCommissionDescription` and enqueued via `queuePartnerCommissionCreation`. The total reward is calculated by summing the `rewardAmount` across all pending milestones in the batch.

Sources: [apps/web/lib/bounty/api/approve-bounty-submission.ts:271-304](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/approve-bounty-submission.ts#L271-L304)

```typescript
  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,
      },
    },
  });
```
Sources: [apps/web/lib/bounty/api/approve-bounty-submission.ts:271-304](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/approve-bounty-submission.ts#L271-L304)

## Performance Submissions and Automated Upserts

### QStash Trigger Pipeline for Draft Submissions

The system automates performance-based referral tracking by triggering draft bounty submissions through QStash. When `triggerDraftBountySubmissionCreation` is invoked, it evaluates program enrollments and finds matching performance bounties that use lifetime statistics (`BountyPerformanceScope.lifetime`). Eligible partner identifiers are collected, and individual JSON publish requests are dispatched to the cron route `/api/cron/bounties/upsert-draft-submissions`.

```typescript
  const results = await Promise.allSettled(
    bountySubmissionCreationData.map(({ bountyId, partnerIds }) =>
      qstash.publishJSON({
        url: `${APP_DOMAIN_WITH_NGROK}/api/cron/bounties/upsert-draft-submissions`,
        body: {
          bountyId,
          partnerIds,
        },
        ...(idempotencyKey && {
          deduplicationId: `${idempotencyKey}-${bountyId}`,
        }),
      }),
    ),
  );
```
Sources: [apps/web/lib/bounty/api/trigger-draft-bounty-submissions.ts:131-144](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/trigger-draft-bounty-submissions.ts#L131-L144)

> [!NOTE]
> If an `idempotencyKey` is provided when triggering draft submissions, QStash uses a composite deduplication identifier (`${idempotencyKey}-${bountyId}`) to prevent duplicate execution of the same bounty batch.

Sources: [apps/web/lib/bounty/api/trigger-draft-bounty-submissions.ts:15-20](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/trigger-draft-bounty-submissions.ts#L15-L20), [apps/web/lib/bounty/api/trigger-draft-bounty-submissions.ts:139-141](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/lib/bounty/api/trigger-draft-bounty-submissions.ts#L139-L141)

### Cron Upsert Route Processing

The POST endpoint at `/api/cron/bounties/upsert-draft-submissions` validates incoming QStash signatures, parses request bodies with Zod, and queries the target bounty along with its workflow trigger conditions and program settings.

```typescript
const schema = z.object({
  bountyId: z.string(),
  partnerIds: z.array(z.string()).optional(),
  page: z.number().optional().default(0),
});

const MAX_PAGE_SIZE = 100;
```
Sources: [apps/web/app/ee/api/cron/bounties/upsert-draft-submissions/route.ts:20-26](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/cron/bounties/upsert-draft-submissions/route.ts#L20-L26)

The route enforces several guard clauses before planning submission upserts:
- Bounties starting 10 or more minutes in the future are deferred.
- Non-performance bounties or bounties restricted to new stats (`performanceScope === "new"`) skip submission creation.
- Bounties missing associated workflow triggers are aborted.

Sources: [apps/web/app/ee/api/cron/bounties/upsert-draft-submissions/route.ts:68-90](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/cron/bounties/upsert-draft-submissions/route.ts#L68-L90)

Program enrollments are fetched matching commission-eligible statuses (`COMMISSION_ELIGIBLE_ENROLLMENT_STATUSES`), grouped by group IDs and partner tag filters, and paginated up to `MAX_PAGE_SIZE` (100). Link metrics—including clicks, sales, leads, conversions, and sale amounts—are aggregated via `aggregatePartnerLinksStats` and planned into create or update actions through `planDraftBountySubmissionUpserts`.

```typescript
    const createdBountySubmissions =
      toCreate.length > 0
        ? await prisma.bountySubmission.createMany({
            data: toCreate,
            skipDuplicates: true,
          })
        : { count: 0 };

    if (toUpdate.length > 0) {
      await Promise.allSettled(
        toUpdate.map((update) =>
          prisma.bountySubmission.update({
            where: {
              id: update.id,
              status: "draft", // in case of race condition, we don't want to update an already submitted entry
            },
            data: {
              performanceCount: update.performanceCount,
              ...(update.promoteToSubmitted && {
                status: "submitted",
                completedAt: new Date(),
              }),
            },
          }),
        ),
      );
    }
```
Sources: [apps/web/app/ee/api/cron/bounties/upsert-draft-submissions/route.ts:203-228](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/cron/bounties/upsert-draft-submissions/route.ts#L203-L228)

> [!CAUTION]
> When updating existing draft submissions, the update query explicitly checks `status: "draft"` to prevent race conditions from overwriting submissions that partners have already promoted to submitted status.

Sources: [apps/web/app/ee/api/cron/bounties/upsert-draft-submissions/route.ts:214-218](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/api/cron/bounties/upsert-draft-submissions/route.ts#L214-L218)

## Program Operator Review and UI

### Submission Inspection and Rich Previews

Program operators inspect partner bounty submissions through dedicated management views, such as `BountySubmissionDetailsSheet` and `BountySubmissionsTable`. For bounties with social metrics requirements, the inspection interface automatically renders rich platform embeds via `BountySocialContentPreview`. This component parses submission URLs to generate embedded iframes for supported social platforms, applying platform-specific aspect ratios.

```typescript
function getSocialContentEmbedAspectRatio({
  platform,
  url,
}: GetSocialContentEmbedAspectRatioParams) {
  try {
    const parsed = new URL(url);
    const pathname = parsed.pathname;

    if (platform === "youtube") {
      return pathname.includes("/shorts/") ? "aspect-[9/16]" : "aspect-video";
    }

    if (platform === "tiktok") {
      return "aspect-[9/16]";
    }

    if (platform === "instagram") {
      return pathname.includes("/reel/") ? "aspect-[9/16]" : "aspect-square";
    }

    if (platform === "twitter") {
      return "aspect-square";
    }

    if (platform === "linkedin") {
      return "aspect-video";
    }

    return "aspect-video";
  } catch {
    return "aspect-video";
  }
}
```
Sources: [apps/web/ui/partners/bounties/bounty-social-content-preview.tsx:121-153](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/bounty-social-content-preview.tsx#L121-L153)

> [!NOTE]
> The embed generator supports exact hostnames and URL path patterns for `youtube`, `instagram`, `tiktok`, `twitter`, and `linkedin`, extracting unique post or video identifiers to construct clean iframe source URLs.

Sources: [apps/web/ui/partners/bounties/bounty-social-content-preview.tsx:31-118](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/ui/partners/bounties/bounty-social-content-preview.tsx#L31-L118)

### Status Badges and Filtering

Operator dashboards categorize submissions using `BountySubmissionStatusBadges`, providing quick visibility into submission lifecycles. Table filters built via `BountySubmissionFilters` allow operators to isolate submissions awaiting review and refresh social metrics synchronization status on demand.

| Status / Filter Element | Target State / Component | Purpose |
| :--- | :--- | :--- |
| `submitted` | Pending review | Isolates submissions that require program manager moderation |
| `approved` | Approved | Indicates submissions that have successfully passed inspection |
| `rejected` | Rejected | Denotes submissions declined by operators |
| Awaiting review button | `BountySubmissionFilters` | Quick-action button displaying the live count of pending submissions |

Sources: [apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/bountyId/bounty-submissions-table.tsx:508-544](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/app.dub.co/(dashboard)/%5Bslug%5D/(ee)/program/bounties/%5BbountyId%5D/bounty-submissions-table.tsx#L508-L544), [apps/web/app/ee/app.dub.co/embed/referrals/bounties/submission-detail.tsx:214-217](https://github.com/blade47/dub/blob/7c93d97abf0ecd37fb669c6a68a1f7ef9109daea/apps/web/app/(ee)/app.dub.co/embed/referrals/bounties/submission-detail.tsx#L214-L217)

## Related

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


## Sitemap

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