Getting Started
Core Architecture
Link Engine
Analytics & Attribution
Partners & Affiliates
Third-Party Integrations
Identity & Security
Automation & Messaging
Developer Tools
The following files were used as context for generating this wiki page:
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, apps/web/app/ee/api/cron/bounties/sync-social-metrics/route.ts:21-224, apps/web/lib/bounty/social-metrics-milestones.ts:33-126, apps/web/lib/bounty/api/create-bounty-submission.ts:48-562
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, apps/web/app/ee/api/bounties/route.ts:163-166, apps/web/lib/zod/schemas/bounties.ts:241-261
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, apps/web/lib/zod/schemas/bounties.ts:269-277
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.
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, apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/add-edit-bounty/bounty-criteria-social-metrics.tsx:91-101, apps/web/app/app.dub.co/dashboard/slug/ee/program/bounties/add-edit-bounty/bounty-criteria.tsx:18-32
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, apps/web/app/ee/api/embed/referrals/bounties/bountyId/social-content-stats/route.ts:16-80
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, apps/web/app/ee/api/embed/referrals/bounties/bountyId/social-content-stats/route.ts:26-29
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, apps/web/app/ee/app.dub.co/embed/referrals/bounties/use-embed-social-content.ts:13-47
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.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
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
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, apps/web/lib/bounty/api/create-bounty-submission.ts:48-111
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
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
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
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
validateUrlDomains strips www prefixes and ensures submitted URLs match or are subdomains of the allowed domains list configured on the bounty requirements.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}/.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, apps/web/ui/partners/bounties/claim-bounty-sheet.tsx:364-581
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
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, apps/web/app/ee/api/cron/bounties/sync-social-metrics/route.ts:1-258
When a synchronization request is processed for a specific submission, the API traverses a strict evaluation and validation chain.
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-36hasReachedSocialMetricsEarningCap checks if the submission's current metric count meets or exceeds the campaign cap.
Sources: apps/web/lib/bounty/social-metrics-milestones.ts:95-109getSocialMetricsEarningCap retrieves the highest valid threshold from the milestone list.
Sources: apps/web/lib/bounty/social-metrics-milestones.ts:84-92getSocialMetricsMilestones computes all payable milestones including base rewards and incremental bonus tiers in ascending order.
Sources: apps/web/lib/bounty/social-metrics-milestones.ts:33-81Tip
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
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
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, apps/web/app/ee/api/cron/bounties/sync-social-metrics/route.ts:231-246
Sources: apps/web/app/ee/api/cron/bounties/queue-sync-social-metrics/route.ts:13-44, apps/web/app/ee/api/cron/bounties/sync-social-metrics/route.ts:29-255
Sources: apps/web/app/ee/api/bounties/bountyId/sync-social-metrics/route.ts:87-104, apps/web/app/ee/api/cron/bounties/sync-social-metrics/route.ts:113-200, apps/web/lib/bounty/api/get-social-metrics-updates.ts:51-92
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
The milestone engine provides utility functions to query earning limits, sync states, and commission descriptions.
Milestone state computation follows a strict execution flow from raw inputs to UI state properties via useSocialMetricsMilestones:
useSocialMetricsMilestones() extracts the social metric name using resolveBountyDetails(bounty)?.socialMetrics?.metric.getSocialMetricsMilestones() evaluates the base tier and loops through incrementalBonus tiers if incrementCount > 0.getPendingSocialMetricsMilestones() filters the resulting milestones where threshold <= socialMetricCount && threshold > approvedSocialMetricThreshold.getSocialMetricsEarningCap() retrieves the final threshold from the array as the campaign earning ceiling.hasReachedSocialMetricsEarningCap() checks if submission.socialMetricCount >= earningCap to halt further sync tasks.Sources: apps/web/lib/bounty/social-metrics-milestones.ts:33-109, apps/web/ui/partners/bounties/use-social-metrics-milestones.ts:11-58
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
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
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, apps/web/lib/api/workflows/award-bounty/execute.ts:212-254, apps/web/lib/bounty/api/approve-bounty-submission.ts:184-371
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.
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, apps/web/ui/partners/bounties/bounty-submission-details-sheet.tsx:47-118
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.
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);
}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
The reward approval sequence follows a deterministic flow from administrative trigger to audit logging and partner email dispatch:
approveSocialMetricsMilestones() or standard approval handlers evaluate incoming submission payloads against active Prisma records.getPendingSocialMetricsMilestones() identifies unapproved performance tiers or social engagement thresholds.prisma.bountySubmission.update() writes the new approvedSocialMetricThreshold and adjusts the submission status (submitted or approved).queuePartnerCommissionCreation() logs the commission entry with source CommissionSource.user and assigns the associated bountySubmissionId.runApprovalSideEffects() uses waitUntil() to wrap concurrent execution of recordAuditLog() and optional partner notification emails via sendEmail().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
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.
Sources: apps/web/lib/api/workflows/award-bounty/execute.ts:23-30, apps/web/lib/api/workflows/award-bounty/execute.ts:212-254, apps/web/lib/bounty/api/approve-bounty-submission.ts:318-371