Cello + Lovable Detailed Integration Guide
A step-by-step guide to integrating Cello’s core referral system with the default launcher UI. When complete, you’ll have a fully functional referral system with Cello’s built-in floating action button. Estimated Time: 1 hour📋 What This Guide Accomplishes
By the end of this guide, you will have: ✅ Working Referral Widget - Users can access their referral link via Cello’s default floating button✅ Attribution Tracking - Referral codes captured from URL parameters
✅ Signup Event Tracking - New signups attributed to referrers
✅ Stripe Integration - Purchases attributed for commission tracking
✅ End-to-End Flow - Complete referral journey from link click to conversion Optional enhancements (not covered here): Custom launcher UI, signup personalization banner, referral code validation, and enhanced error handling. See the Referral Component and Custom Launcher docs for customization.
Cello Scripts Overview
Cello provides two separate JavaScript SDKs for different purposes:
Key Distinction:
cello.js- Loaded in authenticated layouts to show the referral widget/launchercello-attribution.js- Loaded in public layouts to capture referral codes from URL parameters
General Prerequisites
Before starting the Cello integration, ensure your application meets these requirements:Application Requirements
Environment Variables
Create these environment variables in your.env file:
Cello Portal Setup
- Create an account at cello.so
- Create a product and note the
Product IDandProduct Secret - Generate API access keys for server-side calls
- Configure your reward structure and campaigns
Step 1: Server-Side Helpers
1.1 Documentation
- User Authentication - JWT structure and signing
- Referral Component Quickstart - Boot configuration
1.2 Prerequisites
CELLO_PRODUCT_IDandCELLO_PRODUCT_SECRETenvironment variables set- A JWT signing library (e.g.,
jsonwebtokenfor Node.js)
1.3 Integration Description
Create a server-side helper module with three functions:A. JWT Generator
Generate HS512-signed JWTs for widget authentication. CRITICAL: Use Cello’s custom claim names, not standard JWT claims.- ❌ Using
iss,sub,exp(standard JWT claims) - ❌ Including
name,emailin the JWT - ✅ Use exactly:
productId,productUserId,iat
B. Script URL Resolver
- ✅ Sandbox:
https://assets.sandbox.cello.so/app/latest/cello.js - ✅ Production:
https://assets.cello.so/app/latest/cello.js - ❌ Wrong:
https://sandbox.cello.so/widget/cello.js
C. Boot Configuration Builder
1.4 Acceptance Criteria
-
createCelloJwt()generates a valid HS512 JWT withproductId,productUserId,iatclaims only -
getCelloScriptUrl()returns correct URL based on environment -
buildCelloBootConfig()returns config withproductUserDetailsincludingfirstName,lastName,fullName,email - No sensitive data (product secret) is exposed to the client
Step 2: Referral Widget with Default Launcher
📦 Required Script:cello.js (Referral Widget)
This step integrates the Cello referral widget for authenticated users only. This guide uses Cello’s default floating action button (FAB) - no custom launcher implementation needed. For a custom launcher, see Custom Launcher.
2.1 Documentation
2.2 Prerequisites
- Server-side helpers from Step 1 implemented
- Authenticated user session available
- Authenticated layout/pages where widget will appear
2.3 Integration Description
A. Create Token API Endpoint
Create an API route that returns boot configuration for authenticated users:B. Add Scripts to Authenticated Layout (cello.js)
CRITICAL: Use cello.js for authenticated users This script enables the referral widget functionality. Do not confuse withcello-attribution.js (used in Step 3 for public pages).
In your authenticated layout, add the Cello queue snippet and script:
type="module"- Requiredasync- RequiredcrossOrigin="anonymous"- Required- Do NOT include
data-product-idon the widget script
C. Create Bootstrap Component
First, add TypeScript declarations for the Cello SDK. Put them in a declaration file (e.g.types/cello.d.ts) or at the top of your component file.
2.4 Acceptance Criteria
Basic Widget Functionality (Mandatory):- Token endpoint returns
{ enabled: true, bootConfig: {...} }for authenticated users - Token endpoint returns
{ enabled: false }for unauthenticated users - Cello script loads without CORS errors in browser Network tab
- Console shows no “User is not authorized” errors
- Widget opens when clicking the default floating launcher button
- Notification badges appear on the launcher (test with your own referral link)
Step 3: Public Attribution
📦 Required Script:cello-attribution.js (Attribution Tracking)
This step integrates attribution tracking for public visitors. This script captures referral codes (UCC) from URL parameters when users visit via referral links, before they authenticate.
3.1 Documentation
3.2 Prerequisites
- Public layout/pages (landing page, signup page)
3.3 Integration Description
A. Attribution Script URL Helper
- ✅ Sandbox:
https://assets.sandbox.cello.so/attribution/latest/cello-attribution.js - ✅ Production:
https://assets.cello.so/attribution/latest/cello-attribution.js - ❌ Wrong:
https://sandbox.cello.so/attribution/cello-attribution.js
B. Add Scripts to Public Layout (cello-attribution.js)
CRITICAL: Use cello-attribution.js for public pages This script captures referral codes from URL parameters. Do not confuse withcello.js (used in Step 2 for authenticated users).
- Attribution script does NOT require
data-product-idattribute (product context comes from URL parameters) - Use Cello’s official queue function (not the simple queue snippet) - this handles async loading and allows immediate calls
- Queue snippet MUST load before the attribution script
C. Create Attribution Hook
First, add TypeScript declarations for the Attribution SDK. Use a declaration file (e.g.types/cello-attribution.d.ts) or the top of your hook file. Do not import the .d.ts file - TypeScript includes it automatically; importing it will break Vite and other bundlers.
- ✅ No polling needed - Cello’s queue function handles async loading automatically
- ✅ Direct calls work immediately - queue buffers commands until script loads
- ✅ Simpler and more reliable - uses Cello’s official recommended pattern
- ❌ Don’t use simple queue snippet - use the full queue function that returns promises
referrerName and campaignConfig for completeness; you can use them for a signup banner (see Personalizing Referrals).
3.4 Acceptance Criteria
Core Attribution (Mandatory):- Attribution script loads on public pages (check Network tab)
-
window.CelloAttributionfunction is available after script loads - Visiting
/?ucc=TEST123stores the referral code -
useCelloAttribution()hook returns the stored UCC
Step 4: Signup Event Tracking
4.1 Documentation
4.2 Prerequisites
CELLO_ACCESS_KEY_IDandCELLO_SECRET_ACCESS_KEYenvironment variables set- Attribution from Step 3 working
- Signup API endpoint
4.3 Integration Description
A. API Authentication Helper
- ✅ Production:
https://api.cello.so - ✅ Sandbox:
https://api.sandbox.cello.so - ❌ Wrong:
https://sandbox.api.cello.so(subdomain order reversed!)
- ✅ Token:
POST /token - ✅ Events:
POST /events - ❌ Wrong:
/v1/token,/v1/events,/v1/authentication/token
- The access token is in
data.accessToken, NOTdata.token - Expiration is in
data.expiresIn(seconds)
B. Event Emission Helper
- ❌ Putting
newUserIdincontextinstead ofpayload - ❌ Using flat fields like
newUserEmailinstead ofnewUser.email - ❌ Using
fullNameinstead ofname
C. Extend User Model
Add referral fields to your user model:D. Update Signup Handler
E. Pass Referral Code from Signup Form
4.4 Acceptance Criteria
Core Event Tracking (Mandatory):-
fetchCelloAccessToken()successfully retrieves and caches access token - Signup with referral code triggers
emitReferralUpdatedEvent() - Server logs show
POST /eventsreturning status 200 - Cello dashboard shows the new signup event
- User model stores
referralCodefield - Signup without referral code works normally (no errors)
Step 5: Stripe Integration
5.1 Documentation
5.2 Prerequisites
- Stripe integration in your application
- Cello API credentials (for storing referral data on Stripe customer)
5.3 Integration Description
A. UCC Data Flow (Critical)
Understanding how referral codes flow through your system is critical for proper attribution. The Complete Flow:- Capture (Public Pages):
cello-attribution.jscaptures UCC from URL parameter (?ucc=CODE) - Save (Signup): Your signup handler saves UCC to database field
user.referralCode - Read (Checkout): Your checkout function reads UCC from database to add to Stripe metadata
- Attribution cookies have limited lifetime (typically 30-90 days)
- Users may checkout days or weeks after signup
- Cookies may be cleared by the user
- Database is the source of truth for stored referral codes
- Signup: Save UCC to database (✓ covered in Step 4)
- Checkout: Read UCC from database (not from cookies/frontend)
- Database is your persistent source of truth for referral attribution
B. Create or Update Stripe Customer with Metadata
When creating or updating Stripe customers, include referral metadata:cello_ucc- The referral code (UCC)new_user_id- Your internal user ID
C. Stripe Checkout Sessions (Subscription Mode)
If using Stripe Checkout Sessions withmode: "subscription", you must create the customer first with metadata, then pass the customer ID to the session.
⚠️ Important Limitations:
customer_creation: "always"only works inpaymentmode, NOTsubscriptionmodesubscription_data.metadatasets metadata on the subscription, not the customer- Cello’s webhook reads from customer metadata, not subscription metadata
- Always create/update the customer before creating the checkout session
- Pass
customer: customer.idto the session, notcustomer_email - Cello metadata MUST be on the customer object, not the subscription
D. Edge Function Database Access
Issue: When reading user data (like referral_code) from edge functions, the Supabase client created with SUPABASE_ANON_KEY cannot bypass RLS policies. Even after verifying a user’s JWT with auth.getUser(token), subsequent queries run with auth.uid() = null, causing RLS to block the read. Solution: Use the SUPABASE_SERVICE_ROLE_KEY for server-side reads that need to bypass RLS:Only use the service role key for trusted server-side operations, never expose it client-side.
E. Configure Stripe Webhook in Cello Portal
- Go to Cello Portal → Integrations → Stripe
- Connect your Stripe account
- Cello will automatically create webhooks for:
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedinvoice.paidcharge.refunded
5.4 Acceptance Criteria
- Stripe customers have
cello_uccandnew_user_idin metadata - Stripe webhook configured in Cello portal
- Test purchase shows in Cello dashboard with correct attribution
- Reading user data like
referral_codefrom edge functions is working correctly
Troubleshooting
Widget Script Fails to Load (ERR_CONNECTION_REFUSED)
Symptoms:- Browser console shows
GET https://sandbox.cello.so/widget/cello.js net::ERR_CONNECTION_REFUSED - Widget never boots
- ✅ Sandbox:
https://assets.sandbox.cello.so/app/latest/cello.js - ✅ Production:
https://assets.cello.so/app/latest/cello.js - ❌ Wrong:
https://sandbox.cello.so/widget/cello.js
Widget Script Loads but Doesn’t Boot
Symptoms:- Script loads successfully (200 response)
- No boot success message
- Widget never appears
- Missing script attributes - Must have
type="module",async,crossOrigin="anonymous" - Wrong queue pattern - Use
window.cello = { cmd: [] }(object with array) - Wrong boot pattern - Use
window.cello.cmd.push(async function(cello) { await cello.boot(config) })
User is Not Authorized to Load the Widget
Symptoms:- Script loads successfully
- Console error: “User is not authorized to load the widget”
- Use
productId, NOTiss - Use
productUserId, NOTsub - Do NOT include
name,email,expin JWT - User details go in
productUserDetailsduring boot, not JWT - Algorithm must be
HS512
Attribution Script Fails to Load
Symptoms:window.CelloAttributionnever becomes available- Network tab shows 404 for attribution script
- ✅ Sandbox:
https://assets.sandbox.cello.so/attribution/latest/cello-attribution.js - ✅ Production:
https://assets.cello.so/attribution/latest/cello-attribution.js - ❌ Wrong:
https://sandbox.cello.so/attribution/cello-attribution.js
- Script must have
type="module"andasyncattributes - Queue snippet MUST load before the attribution script
Cello API Returns 404
Symptoms:- Server logs show 404 for
/tokenor/events - No referral validation or event emission
- ✅ Production:
https://api.cello.so - ✅ Sandbox:
https://api.sandbox.cello.so - ❌ Wrong:
https://sandbox.api.cello.so(subdomain order reversed!)
- ✅
POST /token - ✅
POST /events - ❌ Wrong:
/v1/token,/v1/events
Access Token is Undefined
Symptoms:- Token request succeeds (200 status)
- But
cachedToken.tokenisundefined - All subsequent API calls fail with 401
accessToken, not token.
Event Emission Returns 400 (Validation Error)
Symptoms:- Server logs show
Response status: 400 - Error:
{"message":"Validation error: Missing newUserId"}
newUserIdmust be inpayload, NOTcontext- User details go in
context.newUseras nested object - Use
name, NOTfullName
You now have a fully functional referral system with Cello’s default UI. For customization (custom launcher, signup banner, referral code validation), see the Referral Component and Custom Launcher docs.