Version

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.

Subscriby\Connector\Core\Referrals. The programme is the core's: the creator sets what referrers earn, what their friends get and who may join, the ledger quotes every reward, and the dashboard pays out. A connector is one more place a member meets it. The contract takes the InstallationRef the conversation runs on and the IdentityRecord of the account that is talking, finds the project behind the one and the member behind the other, and answers in summaries whose every sentence the core already phrased in the member's language. A bot never composes "7 free days" from a kind and a count; it prints what it was handed, and the portal and the dashboard spell the same programme the same way.

programFor()

$program = $this->referrals->programFor($bot->installationRef());

if ($program === null) {
    return;                                   // no live programme: no button, no screen
}

$program->projectName;                        // for a share message
$program->approvalRequired;                   // the creator approves each affiliate first
$program->customersOnly;                      // only members with an active membership may join
$program->windowDays;                         // how long a referred friend stays attributed
$program->pitch;                              // "Share your link and earn 7 free days of membership."
$program->termsLines;                         // the terms, one sentence a line
$program->welcomeSentence;                    // what a friend is told on arrival, or null
$program->terms;                              // the creator's own terms, or null
$program->payoutDetailsLabel;                 // the payout question on a cash programme, or null

ReferralProgramSummary is null while the project runs no programme, the programme is paused, or the creator's plan no longer includes it, so a connector shows its Refer & Earn entry only when a member can act on it. termsLines are the lines the core's own approval and change notices carry: what the referrer earns with the commission period, what their friends get, how long a friend stays theirs and, on a cash programme, when a commission becomes payable.

affiliateFor()

$standing = $this->referrals->affiliateFor($installation, $chat->identityRecord());

$standing?->code;                             // ADA2026
$standing?->status;                           // AffiliateStatus::Pending | Approved | Suspended
$standing?->status->isEarning();              // approved
$standing?->programLive;                      // false while the programme is paused
$standing?->referred;                         // friends who arrived
$standing?->converted;                        // friends who paid
$standing?->daysEarned;                       // membership days earned, on a days programme
foreach ($standing?->earnings ?? [] as $earnings) {
    $earnings->currency;                      // USD
    $earnings->payable;                       // "$12.00", past the hold and not yet paid out
    $earnings->pending;                       // on hold
    $earnings->paidOut;
}
$standing?->payoutDetails;                    // what they answered the payout question, or null

Null when the member never joined, or the project runs no programme at all. A paused programme still answers: the member's standing is theirs whatever the switch says, because a paused programme still owes its affiliates.

join()

try {
    $standing = $this->referrals->join($installation, $chat->identityRecord($displayName));
} catch (ReferralRefused $refused) {
    $this->send($chat, $refused->getMessage());   // the core's sentence, in the member's language
}

Runs the same action the portal runs, so the programme's switches apply once: a customers-only programme refuses a member without a live membership (customers_only), a second join is refused (already_joined), and a programme that requires approval answers Pending and tells the creator. An account the core has never met is created as a lead first. A member approved on the spot is mailed the programme's terms with their code and links; the chat copy of that notice is skipped, because the conversation that asked is their chat and the standing you show is the answer.

foreach ($this->referrals->links($installation, $chat->identityRecord()) as $link) {
    $link->label;                             // "Telegram", "Web"
    $link->url;                               // https://t.me/YourBot?start=ref_ADA2026
}

One ReferralLink per live installation that answers with a deep link, in installation order, then the portal link labelled "Web" when the project has a handle. Empty until the member is an approved affiliate on a live programme, so a connector never hands out a link that would not count.

updatePayoutDetails()

try {
    $standing = $this->referrals->updatePayoutDetails($installation, $chat->identityRecord(), $text);   // null clears
} catch (ReferralRefused $refused) {
    // not_joined, invalid_details
}

$standing->payoutDetails;                     // "ada@paypal.com"

A cash programme may ask its affiliates one question in the creator's words, so the creator knows where to send the money: programFor()->payoutDetailsLabel carries it while the programme pays cash and the creator set one, and a connector asks it under that label. The answer is written through the same action the portal uses, encrypted at rest, trimmed, and read back on the member's own standing; null or a blank clears it. A member who never joined is refused with not_joined, and an answer over 500 characters with invalid_details. The creator reads it in the dashboard and on a creator surface through ReferralManagement; it never travels over the REST API or the webhooks.

capture()

try {
    $capture = $this->referrals->capture($installation, $chat->identityRecord($displayName), $code, ReferralSource::ConnectorLink);
} catch (ReferralRefused $refused) {
    // code_not_valid, self_referral, not_eligible, programme_inactive
}

$capture->expiresAt;                          // when the attribution lapses unless the friend pays
$capture->welcomeSentence;                    // "You get 3 free days on your first purchase.", or null

The two doors a referral comes through: ReferralSource::ConnectorLink for a code carried by a deep link (?start=ref_<code> on Telegram) and ReferralSource::CodeAtCheckout for a code the friend typed where the checkout asked for one. The core applies the capture rules the portal applies: the programme must be live, the code must belong to an approved affiliate, the friend may not be the affiliate, and the friend must be new, with no earlier referral and no successful payment. A capture is a touch; the affiliate earns when the friend's first payment settles.

Refusals

join(), capture() and updatePayoutDetails() throw Subscriby\Connector\Exceptions\ReferralRefused. getMessage() is the core's translated sentence, which is what you relay to the member; reason is stable and yours to branch on (programme_inactive, customers_only, already_joined, code_not_valid, self_referral, not_eligible, not_joined and invalid_details on the payout details, and installation_unknown when the ref names no project). A connector that takes a referral code at its coupon prompt treats code_not_valid, programme_inactive and installation_unknown as "not a referral code at all" and lets the coupon refusal stand.

What is not here

Rewards, holds, payouts and the creator's settings are the core's whole subsystem and never written by a connector; a connector only shows a member where they stand and files the two touches above. Approving, suspending and paying an affiliate are the dashboard's, the REST API's and, for a connector's creator surface, ReferralManagement's.

Read it fresh

A programme summary and a member's standing are computed when you ask; do not cache them across updates. The creator may change the terms or pause the programme between two taps, and the core tells every affiliate when they do.

How is this guide?

Last updated on

Version

On this page

Subscriby is a product
designed by you — for you.
No boardroom full of executives deciding what we ships next. Our roadmap always shaped by you with your feedback.

Share feedback or a request

For AI agents: llms.txt · llms-full.txt