Skip to main content

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/launcher
  • cello-attribution.js - Loaded in public layouts to capture referral codes from URL parameters
Both scripts are independent and serve different purposes in the referral flow.

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

  1. Create an account at cello.so
  2. Create a product and note the Product ID and Product Secret
  3. Generate API access keys for server-side calls
  4. Configure your reward structure and campaigns

Step 1: Server-Side Helpers

1.1 Documentation

1.2 Prerequisites

  • CELLO_PRODUCT_ID and CELLO_PRODUCT_SECRET environment variables set
  • A JWT signing library (e.g., jsonwebtoken for 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.
Common Mistakes to Avoid:
  • ❌ Using iss, sub, exp (standard JWT claims)
  • ❌ Including name, email in the JWT
  • ✅ Use exactly: productId, productUserId, iat

B. Script URL Resolver

CRITICAL URLs:
  • ✅ 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 with productId, productUserId, iat claims only
  • getCelloScriptUrl() returns correct URL based on environment
  • buildCelloBootConfig() returns config with productUserDetails including firstName, 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 with cello-attribution.js (used in Step 3 for public pages). In your authenticated layout, add the Cello queue snippet and script:
CRITICAL Script Attributes:
  • type="module" - Required
  • async - Required
  • crossOrigin="anonymous" - Required
  • Do NOT include data-product-id on 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.
Do not import .d.ts files. TypeScript declaration files (.d.ts) are included automatically by the compiler. If you add import ... from '@/types/cello' or similar, Vite (and many bundlers) will fail because they resolve modules by file and cannot load .d.ts as a module. Use a .d.ts file and let TypeScript pick it up via your tsconfig (no import), or paste the declarations directly in your component.
Then create the bootstrap component:

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)
Use Cello’s default floating action button.

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

CRITICAL URLs:
  • ✅ 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 with cello.js (used in Step 2 for authenticated users).
CRITICAL:
  • Attribution script does NOT require data-product-id attribute (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.
Then create the attribution hook:
Key Implementation Notes:
  • 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
The hook fetches 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.CelloAttribution function is available after script loads
  • Visiting /?ucc=TEST123 stores the referral code
  • useCelloAttribution() hook returns the stored UCC
Displaying referrer name and campaign discount on the signup page is optional; see Personalizing Referrals.

Step 4: Signup Event Tracking

4.1 Documentation

4.2 Prerequisites

  • CELLO_ACCESS_KEY_ID and CELLO_SECRET_ACCESS_KEY environment variables set
  • Attribution from Step 3 working
  • Signup API endpoint

4.3 Integration Description

A. API Authentication Helper

CRITICAL API Base URLs:
  • ✅ Production: https://api.cello.so
  • ✅ Sandbox: https://api.sandbox.cello.so
  • ❌ Wrong: https://sandbox.api.cello.so (subdomain order reversed!)
CRITICAL Endpoints (no /v1/ prefix):
  • ✅ Token: POST /token
  • ✅ Events: POST /events
  • ❌ Wrong: /v1/token, /v1/events, /v1/authentication/token
CRITICAL Token Response:
  • The access token is in data.accessToken, NOT data.token
  • Expiration is in data.expiresIn (seconds)

B. Event Emission Helper

CRITICAL Event Payload Structure:
Common Mistakes:
  • ❌ Putting newUserId in context instead of payload
  • ❌ Using flat fields like newUserEmail instead of newUser.email
  • ❌ Using fullName instead of name

C. Extend User Model

Add referral fields to your user model:

D. Update Signup Handler

This guide stores referral codes without validation. For validation before storing, see Fetch Referral Code Info.

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 /events returning status 200
  • Cello dashboard shows the new signup event
  • User model stores referralCode field
  • Signup without referral code works normally (no errors)
Referral code validation before storing is optional; see the API reference above.

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:
  1. Capture (Public Pages): cello-attribution.js captures UCC from URL parameter (?ucc=CODE)
  2. Save (Signup): Your signup handler saves UCC to database field user.referralCode
  3. Read (Checkout): Your checkout function reads UCC from database to add to Stripe metadata
⚠️ Critical: Read from Database at Checkout At checkout time, ALWAYS read the referral code from your database, NOT from cookies or frontend. Why?
  • 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
✅ CORRECT Pattern:
❌ WRONG Pattern:
Key Takeaway:
  • 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:
Required Metadata Keys:
  • cello_ucc - The referral code (UCC)
  • new_user_id - Your internal user ID
Important: Always update metadata if the customer already exists. This ensures referral attribution is preserved even if users had a Stripe customer record before signing up with a referral code.

C. Stripe Checkout Sessions (Subscription Mode)

If using Stripe Checkout Sessions with mode: "subscription", you must create the customer first with metadata, then pass the customer ID to the session. ⚠️ Important Limitations:
  • customer_creation: "always" only works in payment mode, NOT subscription mode
  • subscription_data.metadata sets metadata on the subscription, not the customer
  • Cello’s webhook reads from customer metadata, not subscription metadata
✅ Correct Pattern for Checkout Sessions:
❌ Common Mistakes:
Key Points:
  • Always create/update the customer before creating the checkout session
  • Pass customer: customer.id to the session, not customer_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

Once the integration is complete, have the user complete these steps:
  1. Go to Cello Portal → Integrations → Stripe
  2. Connect your Stripe account
  3. Cello will automatically create webhooks for:
    • checkout.session.completed
    • customer.subscription.created
    • customer.subscription.updated
    • invoice.paid
    • charge.refunded

5.4 Acceptance Criteria

  • Stripe customers have cello_ucc and new_user_id in metadata
  • Stripe webhook configured in Cello portal
  • Test purchase shows in Cello dashboard with correct attribution
  • Reading user data like referral_code from 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
Root Cause: Script URL format is incorrect. Solution:
  • ✅ 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
Common Causes:
  1. Missing script attributes - Must have type="module", async, crossOrigin="anonymous"
  2. Wrong queue pattern - Use window.cello = { cmd: [] } (object with array)
  3. 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”
Root Cause: JWT payload structure is incorrect. Solution - Use Cello’s custom claim names: WRONG (Standard JWT claims):
CORRECT (Cello’s custom claims):
Key Points:
  • Use productId, NOT iss
  • Use productUserId, NOT sub
  • Do NOT include name, email, exp in JWT
  • User details go in productUserDetails during boot, not JWT
  • Algorithm must be HS512

Attribution Script Fails to Load

Symptoms:
  • window.CelloAttribution never becomes available
  • Network tab shows 404 for attribution script
Root Cause: Attribution script URL is incorrect. Solution:
  • ✅ 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
Also check:
  • Script must have type="module" and async attributes
  • Queue snippet MUST load before the attribution script

Cello API Returns 404

Symptoms:
  • Server logs show 404 for /token or /events
  • No referral validation or event emission
Root Cause: API base URL or endpoint paths are incorrect. Solution: Correct Base URLs:
  • ✅ Production: https://api.cello.so
  • ✅ Sandbox: https://api.sandbox.cello.so
  • ❌ Wrong: https://sandbox.api.cello.so (subdomain order reversed!)
Correct Endpoints (no /v1/ prefix):
  • POST /token
  • POST /events
  • ❌ Wrong: /v1/token, /v1/events

Access Token is Undefined

Symptoms:
  • Token request succeeds (200 status)
  • But cachedToken.token is undefined
  • All subsequent API calls fail with 401
Root Cause: Reading the wrong field from the token response. Solution: WRONG:
CORRECT:
The Cello API returns accessToken, not token.

Event Emission Returns 400 (Validation Error)

Symptoms:
  • Server logs show Response status: 400
  • Error: {"message":"Validation error: Missing newUserId"}
Root Cause: Event payload structure doesn’t match Cello’s API spec. Solution: WRONG (newUserId in wrong location):
WRONG (flat fields instead of nested object):
CORRECT:
Key Points:
  • newUserId must be in payload, NOT context
  • User details go in context.newUser as nested object
  • Use name, NOT fullName

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.