Skip to main content

SPEC 015 — Referrals

FieldValue
StatusDRAFT
PriorityP2 — Growth
Backendequa-server/modules/referral/
Frontendequa-web/src/modules/referrals/
Endpointsequa-server/modules/api/src/endpoints/referral-endpoints.ts

1. Feature Purpose

The referral system incentivizes user growth through a multi-layered reward mechanism. Users generate unique referral links, invite friends via email, and earn EquaCash (platform virtual currency) through scratch card rewards. The system supports both personal and organization-level referrals, invitation tracking, EquaCash transfers between users/organizations, and transaction history. Google Contacts integration for bulk invites was previously supported but has been deprecated.

2. Current State (Verified)

DetailValue
Fileequa-server/modules/referral/src/referral.ts
FunctionrandomString()uniqueReferral()
Algorithmcrypto.randomBytes(6).toString('hex').slice(0, 6) — 6-character hex string
UniquenessRecursive retry if link already exists in DB
Auto-creationReferral record created at registration and on first access to referral page

2.2 Reward Generation

DetailValue
Fileequa-server/modules/referral/src/referral.ts
FunctiongenerateReward()
DistributionRandom 1–100 → $10 (50%), $25 (25%), $50 (20%), $100 (5%)
Reward typesRewardType.signup, RewardType.referral, RewardType.organizationInfo, RewardType.obcl
Scratch mechanicReward amount hidden until user “scratches” the card

2.3 Referral Registration Flow

DetailValue
FunctionhandleReferralRequest()
Steps1. Generate referral link for new user 2. Insert signup reward 3. Add to waitlist 4. Look up referrer by link 5. Mark invitation as joined 6. Insert referral reward for referrer 7. Increment referrer’s count
IP limitingREGISTRATION_IP_LIMIT env var (default 20 registrations per IP)
BlacklistsEmail blacklist (getEmailBlacklist) and domain blacklist (getDomainBlacklist)

2.4 Scratch Card Reveal

DetailValue
FunctionrevealCard()
BehaviorAccepts array of reward IDs; for each unscratched card owned by user, marks as scratched and adds reward to EquaCash balance
Organization rewardsIf reward has an org association, cash goes to org’s balance
IdempotencyAlready-scratched cards are returned unchanged

2.5 EquaCash Transfer

DetailValue
FunctiontransferEquaCashFromRequest()
ValidationSender must have sufficient balance (availableCash >= request.value)
AtomicityDeducts from sender, adds to recipient, inserts transaction record
Request classNewEquaCashRequest with from, to, fromType, toType, value, currency, currencyType, optional memo

2.6 Transaction History

DetailValue
FunctiontransferHistoryFromRequest()
Fields per recordcreated, type, amount, totalAmount (running balance), earned (boolean), memo, source
Transfer typesEquaCashTransferType.earned, transferProfile, transferOrganization, spend
Source resolutionEarned from referral link, email, OBCL, profile, org, or annual subscription
Running balanceComputed in reverse chronological order

2.7 Invitation System

DetailValue
FunctionsendInvitationEmailFromRequest()
InputArray of emails, referral link, recipient name
TemplateauthNotificationTemplates.userInvitation
Invitation URL{appUrl}/invite/r?user={referralLink} (personal) or {appUrl}/invite/r?organization={orgLink}&user={userLink} (org)
StatusesInviteStatus.invited, bounced, joined, registered
UpsertRe-inviting same email updates existing invitation record

2.8 Google Contacts (Deprecated)

DetailValue
FunctiongoogleContacts()
StatusThrows BadRequest — “The legacy google contacts API is being removed by Google and no longer supported.”

2.9 Company Info Collection

DetailValue
FunctionaddUserCompanyInfo()
PurposeCollects company details during waitlist phase
Fieldsname, email, phoneNumber, organizationType (public/private), scheduleTool (zoom/skype/meet), numberSecurityHolder
RewardRewardType.organizationInfo scratch card on first submission
EmailSends company info to info@equastart.io

3. Data Model

Referrals

ColumnTypeConstraints
iduuidPK
useruuidFK to Users
referralLinktextUNIQUE, 6-char hex
equaCashnumericDEFAULT 0, current balance
noOfReferralsnumberDEFAULT 0, count of successful referrals
statusUserStatusEnum
ipAddresstextRegistration IP

Rewards

ColumnTypeConstraints
iduuidPK
rewardnumberAmount (10,10, 25, 50,or50, or 100)
typeRewardTypesignup, referral, organizationInfo, obcl
scratchedbooleanDEFAULT false
scratchedDateDateSet when card is scratched
useruuidFK to Users (reward recipient)
organizationuuidFK to Organizations (nullable)
recipientByLinkcitextEmail of user who joined via link
recipientByEmailcitextEmail of user who joined via email invite

Invitations

ColumnTypeConstraints
iduuidPK
emailcitextInvitee email
useruuidFK to Users (inviter)
organizationuuidFK to Organizations (nullable)
statusInviteStatusinvited, bounced, joined, registered
createdtimestampAuto
modifiedtimestampAuto

CompaniesInfo

ColumnTypeConstraints
iduuidPK
emailcitextCompany contact email
nametextCompany name
phoneNumbertext
numberSecurityHoldernumberExpected member count
organizationTypeOrganizationTypeEnum (public/private)
scheduleToolToolTypeEnum (zoom/skype/meet)
useruuidFK to Users, UNIQUE

UserCoupons

ColumnTypeConstraints
useruuidFK to Users
codetextCoupon code

Transactions (EquaCash transfers)

ColumnTypeConstraints
iduuidPK
fromtextSender address (user ID, org ID, or chargify)
fromTypeAddressTypeEnum
totextRecipient address
toTypeAddressTypeEnum
valuenumberTransfer amount
currencytext
currencyTypeCurrencyTypeEnum
memotextOptional note
createdtimestampAuto

4. API Endpoints

MethodPathAuthDescription
GET/api/v1/referral/:entitySessionGet referral data (link, EquaCash balance, count, waitlist position)
GET/api/v1/referral/:entity/rewardsSessionGet scratch cards with daily/monthly stats
GET/api/v1/referral/:entity/invitationsSessionGet invitation list with stats
POST/api/v1/referral/:entity/inviteSessionSend email invitations (array of emails)
POST/api/v1/referral/:entity/revealSessionScratch cards (array of reward IDs)
GET/api/v1/referral/:entity/equacashSessionGet EquaCash balance for org context
POST/api/v1/referral/transferSessionTransfer EquaCash between users/orgs
GET/api/v1/referral/:entity/historySessionGet transaction history with running balance
POST/api/v1/referral/company-infoSessionSubmit company info during waitlist
GET/api/v1/referral/waitlist-statsPublicGet waitlist aggregate stats

5. Frontend Components

Module: equa-web/src/modules/referrals/

Pages:
ComponentFilePurpose
MyReferralspages/my-referrals.tsxPersonal referral dashboard with scratch cards
OrganizationReferralspages/organization-referrals.tsxOrganization-level referral management
TransferEquaCashpages/transfer-equa-cash.tsxPersonal EquaCash transfer form
TransferOrgEquaCashpages/transfer-org-equa-cash.tsxOrganization EquaCash transfer form
TransferHistoryPagepages/transfer-history-page.tsxPersonal transaction history
OrgTransferHistoryPagepages/org-transfer-history-page.tsxOrganization transaction history
Components:
ComponentFilePurpose
MyReferralsSharedcomponents/referral-component.tsxCore referral UI (shared between personal and org views)
ReferralStatisticscomponents/referral-statistics.tsxStats: total referrals, daily/monthly tickets, EquaCash earned
ReferralInvitePanelcomponents/referral-invite-panel.tsxInvite-a-friend panel with link sharing
ReferralsTablecomponents/referrals-table.tsxTable of sent invitations and their statuses
ScratchCardListcomponents/scratch-card-list/scratch-card-list.tsxGrid of scratch cards
ScratchCardcomponents/scratch-card/scratch-card.tsxIndividual scratch card with reveal animation
ScratchOffPanelscomponents/scratch-off-panels.tsxScratch card section container
TransferCashFormcomponents/transfer-cash-form.tsxEquaCash transfer form fields
TransferInfocomponents/transfer-info.tsxTransfer confirmation display
TransactionHistoryTablecomponents/transaction-history-table.tsxTable of EquaCash transactions
InviteFormcomponents/invite-form.tsxEmail invitation form
EquaTransferComponentcomponents/equa-transfer-component.tsxTransfer flow container
ClaimEquaCashcomponents/claim-equacash/claim-equacash.tsxEquaCash claim UI
SnackBarcomponents/snack-bar.tsxToast notification for referral actions
Modals:
ComponentFilePurpose
ConfirmTransferModalcomponents/modal/confirm-transfer-modal.tsxTransfer confirmation dialog
ScratchTicketModalcomponents/modal/scratch-ticket-modal/scratch-ticket-modal.tsxFull-screen scratch card reveal
LearnMoreModalcomponents/modal/learn-more-modal.tsxReferral program explainer
WaitlistModalcomponents/modal/waitlist-modal.tsxWaitlist position display
ConnectGmailcomponents/modal/connect-gmail.tsxGmail integration for contacts
DisconnectGmailcomponents/modal/disconnect-gmail.tsxGmail disconnection flow
SocialMediaLinkscomponents/modal/social-media-links.tsxSocial sharing options
Google Sign-In:
ComponentFilePurpose
GoogleSignIncomponents/google-sign-in/google-sign-in.tsxGoogle OAuth for contact import (deprecated backend)
ConnectToGooglecomponents/connect-to-google/connect-to-google.tsxGoogle connection UI

6. Business Rules

  1. Referral link is a 6-character hex string, cryptographically random, guaranteed unique via recursive retry.
  2. Reward distribution uses weighted random: $10 (50%), $25 (25%), $50 (20%), $100 (5%).
  3. Signup reward: Every new user gets one scratch card at registration.
  4. Referral reward: The referrer gets one scratch card when their invitee registers.
  5. Organization info reward: User gets one scratch card for submitting company information.
  6. Scratch cards are one-time reveal — once scratched, the EquaCash is added to the user’s or org’s balance.
  7. IP rate limiting: Max REGISTRATION_IP_LIMIT (default 20) referral registrations per IP address.
  8. Email and domain blacklists prevent abusive registrations.
  9. EquaCash transfers require the sender to have a balance >= the transfer amount. Transfer is atomic: deduct, credit, record.
  10. Transaction history shows a running balance computed in reverse chronological order.
  11. Invitation upsert: Re-inviting the same email updates the existing record rather than creating a duplicate.
  12. Invitation statuses progress: invitedregistered or joined; bounced emails are tracked separately.
  13. Google Contacts import is deprecated — backend throws BadRequest if called.
  14. Dual referral paths: Personal referral link (?user={link}) and organization referral link (?organization={orgLink}&user={userLink}).

7. Acceptance Criteria

  • New user receives a unique 6-character referral link at registration
  • User can share referral link and track how many people registered through it
  • Signup generates one scratch card for the new user
  • Successful referral generates one scratch card for the referrer
  • Scratch card reveal animation works and adds correct EquaCash to balance
  • Reward amounts follow the weighted distribution (10/10/25/50/50/100)
  • User can send email invitations to multiple addresses
  • Invitation table shows status (invited, registered, joined, bounced) with daily/monthly stats
  • EquaCash transfers work between users and between user/org
  • Transfer fails gracefully when balance is insufficient
  • Transaction history shows all transfers, earned rewards, and subscription spends with running balance
  • IP rate limiting blocks excessive registrations from a single IP
  • Email and domain blacklists prevent blocked addresses from registering
  • Organization-level referral page shows org-specific rewards and invite tracking

8. Risks

RiskImpactMitigation
EquaCash balance stored as numeric on referral record, updated non-atomically in some pathsRace condition on concurrent scratch/transferWrap balance updates in database transactions with row-level locking
IP limit is per-IP, not per-sessionShared IPs (corporate, VPN) may hit limit prematurelyConsider adding session-based rate limiting alongside IP
Google Contacts integration deprecated but UI components remainConfusing UI if buttons are still visibleRemove or hide Google Contacts UI components
Scratch card reward amounts are deterministic given the random seedNo guaranteed prize pool budget controlImplement budget caps or reward rate adjustment based on total EquaCash issued
Transaction history computed in application codePerformance degradation with many transactionsAdd database-level running balance or pagination
updateCash uses getUserByRewardId with user param that may actually be a reward IDPotential data corruptionRefactor to use explicit parameter naming (noted as TODO in source)