Referrals
The referrals namespace is the merchant-side surface for Ripllo's referral program: one toggle for the whole workspace, per-customer link issuance, and a four-step attribution lifecycle (signup → checkout-start → fulfill → void) that mints reward discount codes when a referred purchase clears. This page covers ripllo.referrals on the Node SDK. For the data model and the HTTP surface, see API → Referrals.
Namespace
ripllo.referrals.getProgram()
ripllo.referrals.putProgram(input)
ripllo.referrals.stats()
ripllo.referrals.issueLink(input)
ripllo.referrals.resolveLink(accountId, code)
ripllo.referrals.recordClick(input)
ripllo.referrals.attributeOnSignup(input)
ripllo.referrals.attributeCheckoutStart(input)
ripllo.referrals.fulfillRewardOnPayment(input)
ripllo.referrals.voidAttributionOnRefund(checkoutSessionId)
ripllo.referrals.listMyRewards(accountId, customerId)
ripllo.referrals.expirePending()
Three sub-surfaces: program config, links for sharing, attributions for the buyer lifecycle. The reward currency is a Ripllo-issued discount code — under the hood, fulfillment mints real DiscountCode rows (REF-… for the referrer, WELCOME-… for the referee).
Program
getProgram
const program = await ripllo.referrals.getProgram(); // null if never configured
Returns the merchant's ReferralProgram or null if it's never been set up. There is exactly one program per workspace.
putProgram
const program = await ripllo.referrals.putProgram({
enabled: true,
rewardType: 'percent',
referrerValue: 15, // 15%
refereeValue: 10, // 10%
currency: 'IDR',
minPurchaseAmount: 100_000,
rewardExpiryDays: 90,
attributionWindowDays: 30,
maxRewardsPerReferrer: 50,
programTerms: 'Codes redeemable on full-price items only.',
});
Upsert — same shape on every call. attributionWindowDays is the maximum gap between a click and a qualifying signup, and rewardExpiryDays becomes the expiresAt on each minted reward code.
Reward values are in the reward type's own units, and the program row does not validate them. For
percent/shipping_percent,referrerValueandrefereeValuemust be 1–100 — a percent program saved as1500accepts the save and then throwsINVALID_VALUE("Percent discount value must be 1-100") on every reward issuance, so the program looks configured and silently never pays out. Forfixed/shipping_fixedthe values are minor units ofcurrency.
stats
const s = await ripllo.referrals.stats();
console.log(s.totalLinks, s.totalClicks, s.totalSignups, s.totalRewards);
console.log(s.attributedRevenue, s.conversionRate);
Lifetime counters for the program, aggregated over every ReferralLink in the workspace: totalLinks, totalClicks, totalSignups, totalRewards, attributedRevenue (minor units, summed at fulfillment) and conversionRate (totalRewards / totalClicks, 0 when there are no clicks). Use this for the dashboard hero strip.
Links
issueLink
const { link } = await ripllo.referrals.issueLink({
accountId: 'acc_<merchant>',
customerId: 'cus_<referrer>',
});
// link.code → 'r-7H3K2N' (the shareable suffix)
Mints (or returns the existing) referral link for a customer. Idempotent — calling it twice for the same (accountId, customerId) returns the same record.
resolveLink
const { link } = await ripllo.referrals.resolveLink('acc_<merchant>', 'r-7H3K2N');
Looks up a link by its code. Throws not_found if the code doesn't exist or doesn't belong to the named merchant.
recordClick
await ripllo.referrals.recordClick({ accountId: 'acc_<merchant>', code: 'r-7H3K2N' });
Stamps a click for analytics. Call this from your storefront's /r/:code redirect handler before bouncing the visitor to the shop. The response carries the resolved linkId and programId so you can drop a click cookie if you want fingerprintless attribution.
Attribution lifecycle
The four-step lifecycle is the heart of the referral product. Call each step from the corresponding hook in your stack:
attributeOnSignup
const { attribution } = await ripllo.referrals.attributeOnSignup({
accountId: 'acc_<merchant>',
refereeCustomerId: 'cus_<new>',
refereeEmail: 'new@example.com',
linkCode: 'r-7H3K2N',
externalSource: 'storlaunch',
externalRef: 'cus_<storlaunchCustomerId>',
});
Called when a new customer registers via a referral link. Creates a ReferralAttribution in pending state. Returns null if the link is invalid or the referee's email matches the referrer's (self-referral guard).
attributeCheckoutStart
await ripllo.referrals.attributeCheckoutStart({
accountId,
customerId,
checkoutSessionId,
});
Stamps the attribution onto a checkout session at cart-start. Idempotent — safe to call from your checkout-initiated webhook regardless of whether referrer attribution exists.
fulfillRewardOnPayment
const result = await ripllo.referrals.fulfillRewardOnPayment({
checkoutSessionId,
status: 'completed', // anything else is a no-op — see below
currency: 'IDR',
amount: 250_000,
});
if (result.issued) {
console.log('Rewards:', result.referrerCodeId, result.refereeCodeId);
} else {
console.warn('No reward issued:', result.reason);
}
The reward-minting step — called from your payment-success webhook. When the referred customer's purchase clears, this mints two discount codes (one for the referrer, one for the referee) and flips the attribution to rewarded. Idempotent on checkoutSessionId (matched against the attribution's qualifyingCheckoutSessionId).
status must be exactly 'completed'. Any other value — 'paid' included — returns { issued: false, reason: 'session_not_completed' } and throws nothing, so both parties silently never get their codes. Always branch on result.issued and log result.reason:
reason |
Meaning |
|---|---|
session_not_completed |
status wasn't 'completed'. |
no_attribution |
No attribution is stamped on that checkout session. |
already_fulfilled |
The attribution has already left pending. |
expired |
Past the attribution's expiresAt; the attribution is voided as expired. |
program_disabled |
The program was switched off between signup and payment; attribution voided. |
currency_mismatch |
currency differs from the program's; attribution voided. |
min_purchase |
amount is under minPurchaseAmount. The attribution stays pending. |
max_rewards_per_referrer |
The referrer hit their cap; attribution voided. |
voidAttributionOnRefund
await ripllo.referrals.voidAttributionOnRefund('cs_<plugipaySession>');
Reverses the attribution and claws back the issued reward codes when the underlying payment is refunded. The codes are archived rather than deleted — this leaves an audit trail.
Rewards
const { items } = await ripllo.referrals.listMyRewards('acc_<merchant>', 'cus_<self>');
for (const r of items) {
console.log(r.role, r.code, r.value, r.expiresAt, r.redeemed, r.active);
}
Returns the calling customer's earned reward codes (for a "my rewards" dashboard page on the storefront) — only attributions that reached rewarded, so there is nothing pending in here. Each item is { role: 'referrer' | 'referee', attributionId, code, discountType, value, currency, expiresAt, redeemed, active, earnedAt }. Use redeemed / active / expiresAt to render state; there is no status field on a reward.
The attribution itself carries the status: pending → rewarded → (voided | expired). Note it is rewarded, not fulfilled.
Sweeps
const { expired } = await ripllo.referrals.expirePending();
Idempotent sweep that flips any attribution older than attributionWindowDays to expired. Requires a platform-admin key (ripllo:platform:admin); an ordinary merchant key gets 403. Wire it to a daily cron — the Ripllo platform calls it for you on prod, but partners running their own deployments need to schedule it.
Errors
| Code | Status | Cause |
|---|---|---|
program_disabled |
422 | Calling an attribution method while program.enabled = false. |
self_referral |
422 | Referee's email matches the referrer's. |
not_found |
404 | Unknown link code or attribution. |
forbidden |
403 | Key lacks ripllo:referral:write. |
See Errors for handling.
Next
- Discount codes — the underlying reward primitive.
- API → Referrals — full HTTP reference.