Referral Management
A project's Referral Program as a connector's creator surface runs it — the options a setup needs, the programme in the core's words with its figures, the save from a draft, the pause, the resume and the delete, affiliates listed and read with their balances and payout details, approved, suspended and enrolled from a member search, and payouts recorded against what is payable.
Subscriby\Connector\Core\ReferralManagement. Where Referrals is the programme as a member meets it, this is the programme as the creator runs it, for a platform that has a conversation with creators. Every call takes the CreatorRef of the account that is talking and the ProjectRef of the project, and runs as that creator: the owner, or a teammate under the same team permissions, with the same entitlement gate and the same settings rules the dashboard applies. A bot never re-implements a rule, and never learns about a project the creator could not open. Reads answer in details whose sentences the core already phrased (what referrers earn, what friends get, the terms as affiliates hear them), and every refusal reaches you as ReferralRefused with a stable reason.
options()
$options = $this->referrals->options($creator, $project);
$options->referralsAvailable; // false when the creator's plan lacks the programme
$options->couponsAvailable; // whether one of the project's coupons can be the friend's reward
$options->coupons; // list<ReferralOption>: id and code
$options->plans; // list<ReferralOption>: id and name
$options->currencies; // list<ReferralOption>: id and ISO code, for a fixed commission
$options->windowDays; // how long a referred friend stays attributed
$options->defaultHoldDays; // the hold the core suggests
$options->maxHoldDays; // the longest hold the core accepts
$options->maxCommissionMonths; // the longest commission period
$options->maxRewardDays; // the most membership days one conversion may grantWhat a setup conversation needs before it asks its first question: the entitlement, the rows a creator picks from and the limits the core enforces, so a wizard refuses an answer in place instead of after the save.
programOf()
$program = $this->referrals->programOf($creator, $project); // null before the first save
$program->active; // the creator's switch
$program->live; // on, and the plan still includes the programme
$program->rewardKind; // ReferralRewardKind::FreeDays | Cash
$program->rewardLabel; // "20% of each payment", in the creator's language
$program->periodLabel; // "for 6 months", on a cash programme
$program->friendRewardLabel; // "3 free days on their first purchase"
$program->termsLines; // the lines an affiliate is handed
$program->terms; // the creator's own terms, or null
$program->payoutDetailsLabel; // the payout question, or null
$program->planIds; // the plans it is restricted to; empty for every plan
$program->portalUrl; // the portal's Refer & Earn, or null without a handle
$program->stats->affiliates(); // approved + pending + suspended
$program->stats->conversionRate(); // converted over referred, in percent
$program->stats->payable; // ["$120.00 USD"], one line per currency owedReferralProgramDetails carries every field the settings dialog shows twice over: as the stored value (the kind, the figure, the currency id, the coupon id), which a settings question edits, and as the sentence the core phrases from it, which a screen prints. stats are the overview's figures, computed when you ask.
saveProgram()
use Subscriby\Connector\Data\ReferralProgramDraft;
$draft = ReferralProgramDraft::empty()
->with(ReferralProgramDraft::REWARD_KIND, ReferralRewardKind::Cash->value)
->with(ReferralProgramDraft::COMMISSION_TYPE, ReferralCommissionType::Percentage->value)
->with(ReferralProgramDraft::COMMISSION_VALUE, '20')
->with(ReferralProgramDraft::COMMISSION_PERIOD_MONTHS, 6) // null for life
->with(ReferralProgramDraft::HOLD_DAYS, 14)
->with(ReferralProgramDraft::FRIEND_REWARD_KIND, ReferralFriendRewardKind::Coupon->value)
->with(ReferralProgramDraft::FRIEND_COUPON_ID, $couponId)
->with(ReferralProgramDraft::CUSTOMERS_ONLY, true)
->with(ReferralProgramDraft::APPROVAL_REQUIRED, true)
->with(ReferralProgramDraft::PAYOUT_DETAILS_LABEL, 'PayPal email')
->with(ReferralProgramDraft::TERMS, 'One referral per household.');
try {
$program = $this->referrals->saveProgram($creator, $project, $draft);
} catch (ReferralRefused $refused) {
// not_entitled, forbidden, invalid_settings
}A draft names only the fields you answered. The first save lets the core fill the rest with its defaults and creates the programme, live at once; a later save that names one field changes that field alone, and null clears a text. The core checks that the settings add up (a free-days reward needs its days, a cash commission its type and a rate above zero, a fixed one its currency, a coupon reward one of the project's coupons on a plan that includes coupons) and answers invalid_settings with the sentence to relay. Every affiliate is told when a term they were promised changes; that is the core's doing, not yours.
setProgramActive() and deleteProgram()
$paused = $this->referrals->setProgramActive($creator, $project, false); // links stop counting, balances stay
$resumed = $this->referrals->setProgramActive($creator, $project, true);
try {
$this->referrals->deleteProgram($creator, $project);
} catch (ReferralRefused $refused) {
// programme_owes_balances: record the payouts first, or pause instead
}Both need a programme (program_missing otherwise) and the creator's permission to change it (forbidden). A pause tells every affiliate and keeps every balance; a delete is refused while any commission is still owed.
affiliates() and affiliate()
$page = $this->referrals->affiliates($creator, $project, AffiliateStatus::Pending, page: 1, perPage: 5);
$page->items; // list<AffiliateRecord>
$page->page; $page->lastPage; $page->total;
$affiliate = $this->referrals->affiliate($creator, $project, $affiliateId); // null when the id names none of this project's
$affiliate->memberName; // the member as the dashboard names them
$affiliate->code; // ADA2026
$affiliate->status; // AffiliateStatus::Pending | Approved | Suspended
$affiliate->joinedAt; $affiliate->approvedAt;
$affiliate->referred; $affiliate->converted; $affiliate->daysEarned;
$affiliate->paysCash; // whether their programme pays money
foreach ($affiliate->balances as $line) { // one AffiliateBalanceLine per currency
$line->currency; // USD
$line->pending; $line->payable; $line->paidOut; // formatted
$line->payableAmount; // the raw decimal, for a "pay all of it" button
}
$affiliate->payableBalances(); // the lines with something payable
$affiliate->payoutDetails; // what they answered the payout question, or nullPass null as the status for every affiliate. payoutDetails is the one field a creator surface carries that the member surface, the REST API and the webhooks never do: it is the creator's to read, so they can pay.
approveAffiliate(), suspendAffiliate(), searchMembers() and enrolMember()
$approved = $this->referrals->approveAffiliate($creator, $project, $affiliateId); // handed their link and the terms
$suspended = $this->referrals->suspendAffiliate($creator, $project, $affiliateId); // their code stops counting; earnings stay
$matches = $this->referrals->searchMembers($creator, $project, $typed, limit: 5); // list<ReferralOption>: id and name; [] under two characters
$enrolled = $this->referrals->enrolMember($creator, $project, $matches[0]->id); // approved whatever the switches sayaffiliate_unknown and member_unknown when an id names none of this project's rows, already_joined when the member is an affiliate already, forbidden for a teammate without the affiliates permission. An approval sends the core's own notice with the terms, the code and the links, in the member's chat and by mail.
recordPayout()
use Subscriby\Connector\Data\ReferralPayoutDraft;
try {
$payout = $this->referrals->recordPayout($creator, $project, $affiliateId, new ReferralPayoutDraft('20', $currencyId, 'TX-100', 'Paid by PayPal.'));
} catch (ReferralRefused $refused) {
// payout_exceeds_balance: the sentence names the payable balance
}
$payout->amount; $payout->currency; // "$20.00", USD
$payout->rewardsCovered; // how many commissions it marked paid
$payout->remainingPayable; // still owed in that currency, formattedMoney is paid outside Subscriby; this records it, marks the affiliate's oldest payable commissions in that currency as paid up to the amount, tells the affiliate and fires the webhook, exactly as the dashboard's Record a Payout does. The reference and the note are optional and the creator's own.
Refusals
Every method throws Subscriby\Connector\Exceptions\ReferralRefused. getMessage() is the core's sentence in the creator's language; reason is stable and yours to branch on: project_unknown (the creator cannot open the project, or no such project or creator exists), not_entitled (the plan lacks the programme), forbidden (a teammate without the permission), program_missing, affiliate_unknown, member_unknown, invalid_settings, already_joined, programme_owes_balances and payout_exceeds_balance. A surface shows not_entitled as its "not on your plan" notice with a way to the billing page, treats project_unknown as missing information, and relays the rest.
What is not here
The reward ledger and the list of referrals stay the dashboard's and the REST API's; a conversation has no screen for them. The minimum payout is not on the draft. Nothing here reads as nobody: a creator surface reads what the creator may read, and a member surface uses Referrals.
Read it fresh
Details, records and pages are computed when you ask; park an id across two taps, never a record. The dashboard, the REST API and another connector may change the programme between them.
How is this guide?
Last updated on
Referrals
A project's Referral Program as a connector's member surface reads it and acts on it — the live programme in the core's own words, a member's standing with their code, links and tallies, the join, and the two doors a referral comes through, a deep link and a code typed at checkout, and the payout details an affiliate leaves for the creator.
Conformance Kit
The suite every connector passes before review — what "passes the kit" means, how it runs, how a rule fails, the report it returns, and the twenty-three rules grouped by what they check.