> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cello.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Support FAQ

> Frequently asked questions about integrating, running, and troubleshooting Cello

## Partners & affiliate programs

<AccordionGroup>
  <Accordion title="How do I view affiliate stats in the Cello portal?">
    Switch to the **Partner Campaign** in the upper-right of the Cello portal. Your dashboard will filter to display the affiliate/partner program numbers. Note: ensure you have the toggle in the upper-left toggled to production. 
  </Accordion>

  <Accordion title="How can I provide marketing materials for the affiliates to use?">
    See [Partner resources](/guides/partners/partner-resources).
  </Accordion>

  <Accordion title="Can we automatically add partners/affiliates via API sync?">
    Partners and affiliates are added via the [Cello Portal](https://portal.cello.so/partners/management). There is no API endpoint for bulk partner creation today.

    For complex migrations or large partner onboarding, Cello works directly with customers to handle bulk imports. Contact your CSM to coordinate.
  </Accordion>

  <Accordion title="Is coupon-code-only tracking supported (without an affiliate link)?">
    Cello's attribution is link-based - referral links with a `ucc` parameter are the supported tracking method. Standalone coupon-code tracking (where a code is shared and attributed at checkout without a referral link) is not available today.

    If your referrers promote through channels where links aren't practical (podcasts, video, print), consider using a short, memorable referral URL instead. If coupon-code-only tracking is important for your program, share the use case with your CSM.
  </Accordion>

  <Accordion title="What is the Product User ID when adding a partner, and where do I find it?">
    The **Product User ID** is the unique identifier your product already assigns to that user - the same value you pass as `productUserId` when generating the JWT token and booting the Referral Component. It is not a Cello-generated ID.

    To look up an existing user's ID: go to the **Referrals** dashboard in the Cello Portal. The **Referrer Id** column shows the `productUserId` for each referrer.

    You should provide the Product User ID whenever the person you're inviting as a partner is already a user in your product. This links their new partner identity to their existing referrer identity, so their referral link (`ucc`) stays the same and their referral tracking remains unified. If you skip it, Cello creates a new identity and the user ends up with two separate referral links.
  </Accordion>

  <Accordion title="When I upgrade a user referrer to a partner, do they keep the same referral link?">
    In general, Cello is designed to be seamless across **user referrals** and **partner/affiliate referrals**:

    * Users access Cello via the in-product Referral Component.
    * Partners can access a separate Cello partner portal, and can also use the in-product Referral Component.
    * The same campaign code can be shared across your product and the partner portal. To do so: when adding the partner in the Cello admin portal, ensure the partner is mapped using their `productUserId` so they retain the same UCC/link across your product and the Cello partner portal.
  </Accordion>

  <Accordion title="When upgrading a referrer to a partner, does historical performance carry over?">
    If the partner is mapped correctly to the existing user via `productUserId`, the intent is for the partner to retain their current UCC and associated performance history across your product and the Cello partner portal.
  </Accordion>

  <Accordion title="When upgrading a referrer to a partner, do commissions change retroactively?">
    No. When a referrer is upgraded to a partner campaign, existing signups keep the reward terms from the campaign that was active when they signed up. Only new signups after the upgrade use the new campaign’s reward structure. This grandfathering is handled automatically.
  </Accordion>

  <Accordion title="Do we need to remove the referral widget when someone becomes a partner?">
    No. Partners can still use the Cello widget to access their referral link and rewards experience.
  </Accordion>

  <Accordion title="I don't see the Partner Users or Partner Resources pages in the Cello portal">
    The partner pages are only visible in **Production** mode. Check the **Sandbox / Production** toggle in the upper left of the portal and make sure it is set to **Production**. If the pages are still not visible after switching, contact Cello support to have the necessary permissions enabled on your account.
  </Accordion>
</AccordionGroup>

## Attribution & cookies

<AccordionGroup>
  <Accordion title="What is attribution?">
    Attribution is the process of tracking and linking new user signups and purchases back to the referrer who originally shared the referral link. It ensures referrers are correctly rewarded and that your referral analytics stay accurate.

    Cello's attribution works in four steps:

    1. **Referral link sharing** - referrers share links containing a unique referral code (`ucc` parameter)
    2. **Landing page capture** - your website or app captures and stores the `ucc` as a first-party cookie
    3. **Signup tracking** - new user registrations are linked to their referrer via the stored `ucc`
    4. **Purchase tracking** - revenue events are attributed back to the original referrer

    Attribution is available for both web and mobile signup flows. For mobile apps, the referral code is passed through deep-link attribution providers (e.g., Branch.io, AppsFlyer) so it persists through app store redirects and installation.

    **Related docs**

    * [Attribution introduction](/attribution/introduction)
    * [Web attribution](/attribution/for-web)
    * [Mobile attribution](/attribution/for-mobile)
  </Accordion>

  <Accordion title="Why is the Attribution Library required?">
    The Attribution Library ([Attribution JS](/sdk/client-side/attribution-js-introduction)) is a lightweight JavaScript library that captures the referral code (`ucc`) from referral links and stores it as a first-party cookie. This cookie persists for 3 months, which is critical because users often don’t sign up immediately - they may click a referral link, browse your site, leave, and return days later before converting.

    Without the library, the `ucc` parameter would be lost as soon as the user navigates away from the landing page URL, breaking attribution for these indirect signups.

    Beyond persistence, the library also provides APIs to:

    * **Retrieve the referral code** - `getUcc()` for passing into signup and purchase events
    * **Get the referrer’s name** - `getReferrerName()` for personalizing landing pages
    * **Access campaign config** - `getCampaignConfig()` for displaying referral discounts
    * **Manage cookie consent** - built-in methods for privacy compliance

    You can install it via an [embedded script tag](/sdk/client-side/embedded-script-tag) or [Google Tag Manager](/sdk/client-side/google-tag-manager).

    **Related docs**

    * [Attribution JS introduction](/sdk/client-side/attribution-js-introduction)
    * [Web attribution](/attribution/for-web)
  </Accordion>

  <Accordion title="Do I need the attribution library if I only want cookies stored?">
    You could technically store the `ucc` as a cookie yourself, but the Attribution Library ([Attribution JS](/sdk/client-side/attribution-js-introduction)) does significantly more than cookie storage:

    * **Automatic form injection** - the library auto-injects a hidden `ucc` field into signup forms, so attribution works without custom form logic
    * **API access** - provides `getUcc()`, `getReferrerName()`, and `getCampaignConfig()` methods for programmatic access to referral data, referrer personalization, and discount display
    * **Fraud detection signals** - the script collects telemetry that feeds into Cello's [fraud and self-referral detection](/guides/fraud-detection), which is not possible with a plain cookie
    * **Cross-session persistence** - stores the `ucc` as a first-party cookie with a 3-month lifetime, handling delayed and indirect signups automatically

    Skipping the library means you would need to manually parse the `ucc` from the URL, store it, inject it into forms, and lose fraud detection capabilities. For most integrations, using the library is the simpler and more reliable path.

    **Related docs**

    * [Attribution JS introduction](/sdk/client-side/attribution-js-introduction)
    * [Attribution JS usage & API](/sdk/client-side/attribution-js-usage)
  </Accordion>

  <Accordion title="Is the cookie with the Cello UCC available across pages/subdomains?">
    Yes. The Attribution Library stores the referral code in first-party cookies (`cello-referral` and `cello-productId`) that are scoped to your root domain. This means:

    * **Across pages** - the cookies are available on every page of your site, as long as the attribution script is installed on each page (recommended in the `<head>` tag site-wide)
    * **Across subdomains** - the cookies work across subdomains of the same root domain (e.g., `www.example.com` and `app.example.com`). Cross-subdomain attribution is supported natively

    The cookies persist for 3 months, so they survive across sessions and return visits.

    Note: cookies are **not** shared across different root domains (e.g., `example.net` and `example.app`). If your landing page and product use different root domains, see the FAQ below on cross-domain strategies.

    **Related docs**

    * [Manage cookie consent](/landing-pages/manage-cookies)
    * [Attribution JS introduction](/sdk/client-side/attribution-js-introduction)
  </Accordion>

  <Accordion title="We have different root domains for landing vs product. Will attribution work?">
    The Attribution Library stores the `ucc` in a first-party cookie scoped to the root domain where it was set. This means the cookie set on `example.net` is **not** automatically available on `example.app` - browsers enforce this as a security boundary.

    If your landing page and product use different root domains, here are your options (in order of preference):

    1. **Use the same root domain** (recommended) - host your landing page on a subdomain of your product domain (e.g., `www.example.app` for landing, `app.example.app` for the product). This makes attribution seamless with no extra work, since cookies are shared across subdomains of the same root domain.

    2. **Persist the `ucc` in URLs across domains** - install the Attribution Library on both domains and ensure all links between them append the `?ucc={ucc}` parameter. This way the `ucc` is recaptured by the Attribution Library when the user arrives on the second domain.

    3. **Build a custom cross-domain bridge** - implement server-side logic to store and forward the `ucc` when users transition between domains (e.g., pass it through your authentication or redirect flow so it’s available on the product domain at signup time).

    **Related docs**

    * [Web attribution setup](/attribution/for-web)
    * [Attribution JS introduction](/sdk/client-side/attribution-js-introduction)
  </Accordion>

  <Accordion title="Does Cello integrate with cookie consent management platforms (OneTrust, CookieBot, etc.)?">
    Yes. Cello's Attribution Library supports integration with cookie consent management platforms so that referral cookies are only stored after the user grants consent.

    **Supported platforms:**

    * **OneTrust** - fully supported. Cello enables the integration on their side; no setup required from you
    * **CookieFirst** - coming soon
    * **CookieBot** - coming soon
    * **Civic Cookie Control** - coming soon

    If your consent platform isn't listed above, you can use the **custom consent method** via the Attribution JS API:

    ```javascript theme={null}
    // After user grants consent
    window.CelloAttribution('allowCookies');

    // If user withdraws consent
    window.CelloAttribution('deleteCookies');
    ```

    This gives you full control to integrate Cello's cookie handling with any consent management platform or custom consent flow.

    **Related docs**

    * [Manage cookie consent](/landing-pages/manage-cookies)
    * [Attribution JS introduction](/sdk/client-side/attribution-js-introduction)
  </Accordion>

  <Accordion title="What is direct vs indirect signups?">
    * **Direct signup**: user clicks a referral link and signs up immediately.
    * **Indirect signup**: user clicks a referral link, navigates around, and signs up later from a different page.

    For both, ensure attribution rules are followed so the referrer’s UCC is tracked and rewards are correctly attributed.
  </Accordion>

  <Accordion title="Can we track purchases without requiring a prior signup event?">
    Yes, but Cello still needs to know *who* the referred user is before attributing a purchase. If you skip the standard signup flow, you'll need to send a `new-signup` or `sign_in` event via the [Cello API](/attribution/tracking-signups) before sending `invoice-paid` events - otherwise the purchase has no user to attribute to.

    A common use case is **existing-user attribution**, where a current user clicks a referral link and you want to credit the referrer for their next purchase. The recommended approach:

    1. **Check for a `ucc` on login** - not just on signup. Use `getUcc()` from the Attribution Library or read the `ucc` URL parameter directly.
    2. **Validate the `ucc`** - call Cello’s [Referral Codes API](/api-reference/referral-codes/fetch-referral-code-info) to confirm the code is valid and identify the referrer.
    3. **Prevent self-attribution** - compare the referrer’s `productUserId` with the logged-in user’s ID. If they match, ignore the `ucc`.
    4. **Store the `ucc`** - save it in your database linked to the user, so it’s available when sending events.
    5. **Send events to Cello** - send a `sign_in` event (to establish the attribution) followed by `invoice-paid` events as purchases occur.

    **Related docs**

    * [Track signups](/attribution/tracking-signups)
    * [Track purchases](/attribution/tracking-purchase)
    * [Fetch referral code info](/api-reference/referral-codes/fetch-referral-code-info)
  </Accordion>

  <Accordion title="How do I manually backfill attribution for a missed signup or purchase?">
    If a referral wasn’t automatically attributed - due to a technical issue, missing metadata, or a user dispute - you can manually backfill it. The exact steps depend on your integration type:

    **For Stripe or Chargebee webhook integrations:**

    1. Get the referrer’s `cello_ucc` from the [Cello Portal](https://portal.cello.so) and the new user’s `new_user_id` from your system.
    2. Add `cello_ucc` and `new_user_id` to the new user’s customer metadata in Stripe or Chargebee.
    3. Cello will receive a `customer.updated` (Stripe) or `customer_changed` (Chargebee) event automatically.
    4. If past invoices were already paid, resend the `invoice.paid` (Stripe) or `payment_succeeded` (Chargebee) events from within your payment platform’s dashboard.

    **For Cello API integrations:**

    1. Get the referrer’s `cello_ucc` and the new user’s ID.
    2. Resend the signup event via the [POST /events API](/api-reference/generic-events/send-event).
    3. Resend any past transaction events via the same API.

    If events cannot be resent via API, Cello Support can accept manual reports - contact [support@cello.so](mailto:support@cello.so) for report templates.

    **Related docs**

    * [Manual Attribution guide](/guides/attribution/manual-attribution)
    * [Track purchases](/attribution/tracking-purchase)
    * [Stripe webhook](/integrations/webhooks/stripe-webhook)
    * [Chargebee webhook](/integrations/webhooks/chargebee-webhook)
  </Accordion>

  <Accordion title="My flow is purchase first, signup later. Does that work?">
    Yes, this is a supported pattern. Cello can handle flows where the payment gateway customer is created at checkout (purchase) rather than at signup, as long as the referral metadata is attached within 7 days of the purchase.

    **How it works with Stripe:**

    1. User clicks a referral link and the `ucc` is captured by the Attribution Library.
    2. User completes a Stripe Checkout session, which creates a Stripe customer.
    3. After the checkout session completes, update the Stripe customer with `cello_ucc` and `new_user_id` metadata via `customer.update`.
    4. Cello receives the `customer.updated` event and uses it to establish the attribution.
    5. Even if the first `invoice.paid` event arrived before the metadata was added, Cello will retroactively attribute it once the customer is updated.

    **How it works with Chargebee:**
    Follow the same pattern - add `cello_ucc` and `new_user_id` to the Chargebee customer metadata after checkout. Cello will receive the `customer_changed` event.

    **How it works with Cello API:**
    If your payment gateway is Paddle, Recurly, or another provider, use the [POST /events API](/api-reference/generic-events/send-event) to send a `new-signup` event after the customer is created at purchase.

    **Related docs**

    * [Track signups - Option 3: customer created at purchase](/attribution/tracking-signups#option-3-using-cello-api-post-events-api-endpoint)
    * [Stripe webhook](/integrations/webhooks/stripe-webhook)
    * [Chargebee webhook](/integrations/webhooks/chargebee-webhook)
  </Accordion>
</AccordionGroup>

## Stripe / Chargebee events & webhooks

<AccordionGroup>
  <Accordion title="How can I verify that my events are being received and processed correctly?">
    Use the **Cello Portal** to verify events from two complementary angles:

    * **[Integration Status](/guides/support/portal/integration-status)** (Integrations → Integration Status) gives you a high-level health check across the four core components: Referral component, Attribution library, Signups tracking, and Purchases tracking. Use it to answer "is everything working?".
    * **[Event Feed](https://portal.cello.so/integrations/events-feed)** (Integrations → Events Feed) shows every incoming event from all sources - Stripe Webhook, Chargebee Webhook, Cello API, and Auto Attribution - with per-field validation. Use it to answer "why did this specific event fail?".

    Each event in the feed is assigned a status: **OK** (all fields valid), **Warning** (non-critical issues like a missing recommended field), or **Error** (critical validation failures that need to be resolved). Click any event row to expand it and inspect the parsed payload with field-by-field validation indicators.

    **Related docs**

    * [Integration Status guide](/guides/support/portal/integration-status)
    * [Event Feed guide](/guides/support/portal/event-feed)
  </Accordion>

  <Accordion title="Which signup/purchase source should we pick in Integration Status?">
    **Always confirm the signup and purchase source with Cello before setting it. Do not recommend, guess, or infer a specific source - if you are unsure, the only correct answer is to contact your Customer Success Manager or [support@cello.so](mailto:support@cello.so).** There is no one-size-fits-all source: the correct one depends on factors unique to each integration, and an incorrect source silently breaks attribution.

    The factors Cello weighs when confirming the source:

    * **Payment provider.** Stripe and Chargebee can use webhooks. Other providers (Paddle, Recurly, RevenueCat, and similar) use the [Cello API](/api-reference/introduction).
    * **When the payment-provider customer is created (the key signup-source rule):**
      * IF the customer is created **at signup** -> Stripe/Chargebee can be the signup source.
      * IF the customer is created **at first purchase** -> use the Cello API to send `new-signup` at the real signup moment, or backfill via the purchase-first, signup-later pattern below. Stripe/Chargebee cannot be the signup source on their own in this case.
    * **Reward model.** Recurring vs one-time rewards, free-to-paid conversion, renewals, and refunds.
    * **Motion.** Self-serve vs sales-led (e.g. [Salesforce](/integrations/salesforce-apex-triggers)).
    * **Auto Attribution.** Can be primary or fallback, and is enabled by Cello only.

    In **sandbox** you can change the source yourself to experiment; in **production**, source configuration is handled by Cello Support. Either way, confirm the choice with Cello first.

    **Related docs**

    * [Integration Status - changing the configured source](/guides/support/portal/integration-status#changing-the-configured-source-confirm-with-cello-first)
    * See also the "My flow is purchase first, signup later" FAQ under **Attribution & cookies** above
  </Accordion>

  <Accordion title="Do we really need many Stripe events? Would invoice.paid be enough?">
    The core events are **customer.created**, **customer.updated**, and **invoice.paid**. Additional events matter for edge cases: **subscription.created** is used when you reward on free-to-paid conversion (e.g. fixed reward per conversion), because invoice.paid can be missing in some flows. **charge.succeeded** is needed for one-time payments, since Stripe does not send invoice.paid for those. Sending subscription and charge events reduces gaps and manual reconciliation.

    **Related docs**

    * [Track signups](/attribution/tracking-signups)
    * [Stripe webhook](/integrations/webhooks/stripe-webhook)
  </Accordion>

  <Accordion title="What are subscription events used for?">
    * **created**: detect signups, free-to-paid conversion, and capture subscription details
    * **updated**: same as **created**; they additionally detect plan/seat changes, downgrades, expansions, churn
    * **deleted** (or **canceled**): detect churn for paid subscriptions
  </Accordion>

  <Accordion title="Why send subscription events if we already send invoice.paid?">
    Subscription events help when invoice flows and subscription state diverge (e.g. pricing changes, discounts, failed payments). They improve reconciliation and reward accuracy.
  </Accordion>

  <Accordion title="What is invoice.paid used for?">
    It is usually the main “payment received” signal: it drives recurring payouts, reward calculation, referral ARR calculation. If a payment fails, you may get subscription events without invoice.paid. For one-time or fixed-amount transactions, invoice.paid may still be the primary event depending on your setup.
  </Accordion>

  <Accordion title="What is customer.created / customer.updated used for?">
    * **customer.created**: typically used as the signup signal when the customer is created at signup
    * **customer.updated**: used when you add or change attribution metadata (e.g. `cello_ucc`, `new_user_id`) after the customer already exists (e.g. at first purchase)
  </Accordion>

  <Accordion title="What is charge.succeeded used for?">
    It indicates a payment. For subscriptions, invoice.paid is usually preferred for interval logic. For **one-time payments**, Stripe does not send invoice.paid, so charge.succeeded (or equivalent) is what Cello uses.
  </Accordion>

  <Accordion title="What is charge.refunded used for?">
    It signals refunds/cancellations so Cello can correct attribution and reward outcomes (e.g. cancel or adjust pending rewards).
  </Accordion>

  <Accordion title="What events do we need if we use Cello API only?">
    * Send signup events via API.
    * Send at least purchase events (e.g. invoice-paid equivalent) via the Cello API.
    * Include amount (in **cents**), currency, and payment frequency (monthly vs annual) where applicable.
    * Send renewal purchase events for continuous rewarding and referral ARR computation.

    **Related docs**

    * [API reference](/api-reference/introduction)
    * [Track signups](/attribution/tracking-signups)
    * [Track purchases](/attribution/tracking-purchase)
  </Accordion>

  <Accordion title="What events do we need if we use Stripe or Chargebee webhooks only?">
    Connect the Stripe or Chargebee webhooks, add `cello_ucc` and `new_user_id` to the **customer object** (on customer.created or customer.updated, depending on when you create the customer). That customer update is the signup signal; subscription and invoice events are then used automatically for rewards.

    **Related docs**

    * [Track signups](/attribution/tracking-signups)
    * [Stripe webhook](/integrations/webhooks/stripe-webhook)
    * [Chargebee webhook](/integrations/webhooks/chargebee-webhook)
  </Accordion>

  <Accordion title="What if we use a payment provider other than Stripe/Chargebee?">
    Use the **Cello API** from your backend to send conversion events (signups and purchases). You keep full control over which events are sent. First time purchases and renewals have to be shared as purchase events.

    **Related docs**

    * [Integration overview](/integration-overview)
    * [API reference](/api-reference/introduction)
  </Accordion>

  <Accordion title="Which user ID should we send as new_user_id / productUserId?">
    Send the **new user (referee)** ID. The referrer is identified via the UCC in the referral link; the referee is identified by this ID.
  </Accordion>

  <Accordion title="Will connecting Stripe webhooks affect our payment architecture?">
    No. Cello only **listens** to webhook events. Your payment flow and architecture stay unchanged.
  </Accordion>

  <Accordion title="Are accessKeyId and productId the same thing?">
    In the **Stripe webhook** context they are used interchangeably. Everywhere else (API, portal, docs) the product identifier is referred to as **productId**.
  </Accordion>

  <Accordion title="Reward amount is less than expected. What could be wrong?">
    Check that amounts are sent in **cents** (or smallest currency unit). For example, 100 USD should be **10000**. If you send 100, Cello treats it as 1.00 USD.
  </Accordion>

  <Accordion title="Stripe can’t send webhook events to our server. What should we check?">
    Verify that the **Webhooks** section of the Cello Portal is populated with the correct Stripe signing secret. Without it, events will fail to authenticate. Ensure the signing secret matches the environment (staging vs prod). In a next step verify in your **Stripe dashboard** if the webhook url is correct and active. In the webhook event feed in Stripe's dashboard you can check if an event resulted in an error. You can also check the [Event Feed](https://portal.cello.so/integrations/events-feed) in the Cello Portal to see if events are arriving.
  </Accordion>

  <Accordion title="What events are needed when using Cello API plus Stripe or Chargebee webhooks?">
    * Send `new-signup` events via the Cello API.
    * Connect Stripe or Chargebee webhooks.
    * Add `cello_ucc` and `new_user_id` to the **customer object** (on `customer.created` or `customer.updated`).
    * Subscription and invoice events are detected automatically once the above is triggered - no need to add `cello_ucc` and `new_user_id` to subscription or transaction events.
  </Accordion>

  <Accordion title="Is there deduplication for invoice-paid events (e.g. by invoiceId) when using Cello API?">
    There is no fully automated deduplication (similar to idempotency rules) yet. However, Cello has simple deduplication logic based on `new_user_id` + time difference + `invoiceId` (if provided).

    * The current time-difference threshold for detecting duplicate `invoice-paid` events is **one minute**.
    * Flagged events go through manual review, and the threshold is adjusted if there are too many false positives.
    * There are scenarios where a customer can buy multiple subscriptions within a minute, so the threshold is kept conservative.
    * If you can send an `invoiceId`, this significantly improves deduplication accuracy since it becomes the primary identifier for detecting duplicates.
  </Accordion>
</AccordionGroup>

## Rewards, payouts, taxes & billing

<AccordionGroup>
  <Accordion title="What payout methods are supported (besides PayPal/Venmo)?">
    Currently, payouts are primarily via **PayPal** (and **Venmo** for the US on request). Bank transfer/SEPA are not offered today.
  </Accordion>

  <Accordion title="How does Cello invoicing work?">
    Most customers receive two separate invoices: one for the Platform Fee and one for the Advertising Fee, issued as part of Cello's Advertising Services. Invoices are typically issued monthly, though frequency and timing may vary based on your specific arrangement. For questions about your invoicing setup, contact your Cello representative.
  </Accordion>

  <Accordion title="When will we receive invoices?">
    Invoices are routinely sent around the middle of the month. Timing may differ based on your agreement or customer setup; your Cello contact can confirm the schedule that applies to you.
  </Accordion>

  <Accordion title="How should we handle refunds and reward clawbacks?">
    Cello campaign rules are designed to mitigate refund risk. Rewards are spaced and delayed so that during the delay period no payment is made, and when a subscription is refunded or canceled, future payouts can be automatically stopped. Programs set up with the recommended delay and spacing significantly reduce the risk of incorrect payouts.

    If you believe rewards have been incorrectly issued, contact Cello support to discuss your options.
  </Accordion>

  <Accordion title="How do we apply the referee discount if we don’t use the Cello↔Stripe discount integration?">
    Referee discounts are usually implemented on your side. Many customers use Stripe discounts/coupons for this.

    **Related docs**

    * [New user discounts](/guides/user-experience/new-user-discounts)
  </Accordion>

  <Accordion title="If a referrer churns from our product, do their rewards stop?">
    Referral rewards are tied to the referred customer's activity, not the referrer's subscription status. If a referrer is no longer a user of your product, they can continue to access their referral dashboard and claim rewards through the Cello portal, provided they have access to the email address they used to register for rewards.
  </Accordion>

  <Accordion title="What happens if a referee cancels and later resubscribes? Does the referrer keep earning?">
    If the payment record is the same and the UCC metadata is preserved, rewarding can continue. One-time bonus rewards typically do not repeat.
  </Accordion>

  <Accordion title="What happens when a referred user cancels an annual subscription?">
    If rewards are recurring percentage-based, the monthly reward typically stops when the subscription stops.
  </Accordion>

  <Accordion title="If a customer prepays annually, is the reward paid on the full year or prorated?">
    By default, rewards are prorated with the referrer reward spread over the payment period rather than paid on the full year upfront. Spaced rewards reduce risk and the need to introduce long payout delays, while also driving better referrer engagement through regular, recurring payouts. Some customers opt for front-loading, but this is generally not recommended as it reduces these benefits.
  </Accordion>

  <Accordion title="Can we cap rewards by time instead of total reward amount?">
    Cello's campaign design favors capping based on reward amounts rather than time horizons. User research across referral programs has shown that time-based caps are less well received by referrers and can reduce sharing rates.

    If you have a specific requirement around time-based caps, discuss it with your CSM to explore what configuration options are available.
  </Accordion>

  <Accordion title="What currencies are supported for campaigns?">
    Cello supports multiple currencies for setting a campaign’s primary currency (configured in the portal).
  </Accordion>

  <Accordion title="What currencies are supported for reward payouts?">
    Rewards are typically paid in the local currency supported by the referrer’s PayPal/Venmo account. Additional payout methods may be added in the future.
  </Accordion>

  <Accordion title="What happens for users from unsupported payout countries? How should we handle them?">
    Users from countries not on the [supported payout list](/guides/user-experience/overview#reward-countries-and-payout-methods) (e.g. Mexico, Argentina) will not be able to set up a payout method - their country won't appear in the payout country dropdown. This is due to PayPal-side restrictions and compliance, tax, or regulatory rules. Cello cannot issue payouts to unsupported countries.

    **Two approaches:**

    1. **Hide the widget for unsupported countries** - pass `productUserCountryCode` in `cello.boot()`. If the country is unsupported, the component won't be shown to that user, preventing a confusing experience where they can share and earn but not receive payouts.

    2. **Show the widget to everyone** - simpler to implement. Users in unsupported countries can still share and earn, but won't be able to complete payout setup. This avoids incorrectly hiding the widget from users who might actually be eligible.

    **Why the hide approach is imperfect:** `productUserCountryCode` reflects the country in your system, which may differ from the user's actual PayPal payout country. Three scenarios:

    * Product country unsupported, payout country also unsupported → hiding works correctly.
    * Product country supported, payout country unsupported → user sees the widget and can share/earn, but can't receive payouts. No way to prevent this upfront.
    * Product country unsupported, payout country supported → worst case: user is incorrectly hidden even though they could use Cello and receive payouts.

    The right choice depends on how accurately your system reflects your users' actual locations.

    **Edge case - PayPal account country vs. physical location:** A user may live in an unsupported country (e.g. Argentina) but hold a PayPal account registered in a supported country (e.g. Brazil). They should enter the address matching their PayPal account's registered country in the widget. Cello cannot issue payouts to unsupported countries regardless of physical location.
  </Accordion>

  <Accordion title="Can we restrict payment countries shown to referrers (e.g. US only)?">
    The country list shown to referrers during payout setup is not restricted because referrers and partners can earn rewards from any country they participate in. Restricting the list could prevent eligible referrers from setting up their payout details and receiving rewards.

    If you need to limit who can participate in your referral program, you can control eligibility on your side by restricting which users see the Referral Component (e.g., only booting the Cello SDK for users in eligible regions).
  </Accordion>

  <Accordion title="Rewards earned shows $0 in the widget. When does that update?">
    The “Rewards earned” indicator reflects how much has been **credited to a PayPal account**. Until a referrer enters valid PayPal credentials, it will show \$0. Attributed rewards still accumulate in the rewards list below.
  </Accordion>

  <Accordion title="Can end users see a dashboard of their referrals and rewards?">
    Referrers can see link views, signup history, and reward history in the **rewards tab** inside the referral widget.

    For a more detailed dashboard, the Partner add-on provides a unified view of user referrals and affiliate programs on a single platform - with no extra technical setup required.
  </Accordion>

  <Accordion title="Successful referrals are missing from the customer view. What should I check?">
    Referral activity should be visible in the Cello Portal dashboards. If data is missing, check the following:

    * Open [Integration Status](/guides/support/portal/integration-status) and confirm the relevant tracking component (Signups or Purchases) is **Connected**. If it shows **Connected with warnings** or **Not connected**, that's where the problem is.
    * Check the [Event Feed](https://portal.cello.so/integrations/events-feed) to verify events are arriving with the correct field values and passing validation
    * Verify that signup and purchase events are being sent correctly and contain the expected `cello_ucc` and `new_user_id` values
    * Confirm that the `productUserId` is consistent - if the same user signs up via different channels with different IDs, attribution may not match
    * Check whether there have been any recent code changes that could affect how events are sent to Cello
  </Accordion>

  <Accordion title="Do referrers handle taxes themselves? Do we need to explain tax implications?">
    Referrers are responsible for determining and complying with their own tax obligations. Cello may provide general informational materials and, where applicable, payout statements, credit notes, or similar documentation relating to rewards paid through the platform. Such information and documentation are provided for informational and record-keeping purposes only, are not exhaustive, and do not constitute tax, legal, or financial advice.
  </Accordion>

  <Accordion title="When exactly are invoices generated, and how do cancellations affect them?">
    An invoice may be triggered within the first **3–5 days** after the close of the previous month. For cancellations, the corresponding credit-note rewards are triggered **14 days later**, which means they appear on the following month’s invoice.

    Note: this adjustment only applies to the invoice covering rewards that Cello pays to referrers on your behalf - not to the platform/subscription fee invoice.
  </Accordion>

  <Accordion title="When referrers add their payment details, when exactly is the reward applied?">
    Cello rewards users on a daily basis, barring any additional reward-delay policy you have configured for your program. Because rewarding runs daily, it typically takes only a few days for referrers to receive their reward (assuming they add their payment details promptly).
  </Accordion>

  <Accordion title="Is the payout always in USD, or can it be in EUR/GBP based on the referrer's preference?">
    * Cello allows you to select only **one default currency** for your rewards (configured in the Campaigns tab).
    * Cello covers transfer and transaction fees, including currency conversion from the subscription event currency back to your nominated default currency.
    * Referrers receive rewards in their PayPal account in your default currency. If they convert to a different currency (e.g. their local one), the exchange rate depends on PayPal's currency exchange and may include additional PayPal fees.
  </Accordion>

  <Accordion title="I want to move my referrers to a higher revshare or reward, or move a user-led referrer to a higher partner campaign plan">
    Grandfathering of the prior campaign when a referrer moves to a new one is handled automatically.

    When a referee signs up through a referral link, the campaign rate at the time of signup is locked in for that referral. If you later move the referrer to a new campaign:

    * **Existing signups** keep the reward structure from the campaign that was active when they signed up
    * **New signups** (after the move) use the new campaign's reward structure

    For major campaign updates, coordinate with the Cello team to ensure the transition is configured correctly for your program.
  </Accordion>

  <Accordion title="I want to move a referrer from one Partner Campaign to another">
    Moving a referrer between Partner Campaigns is handled automatically. The referrer keeps the same referral link, and:

    * **Existing signups** retain the reward terms from the original campaign
    * **New signups** (after the move) use the new campaign's reward structure

    No data migration or new referral links are required. For major campaign updates, coordinate with the Cello team to ensure the transition is configured correctly for your program.
  </Accordion>

  <Accordion title="Can we implement a reward cap based on a time threshold AND/OR an amount ceiling?">
    Cello's campaign design favors capping based on reward amounts rather than time horizons. User research across referral programs has shown that time-based caps are less well received by referrers and can reduce sharing rates.

    If you have a specific requirement around time-based caps, discuss it with your CSM to explore what configuration options are available.
  </Accordion>
</AccordionGroup>

## Notifications & emails

<AccordionGroup>
  <Accordion title="If a referrer earns a reward but hasn’t set up payout details, will they be prompted?">
    Yes. Referrers will see an in-product notification prompting them to add payout details. If emails are enabled, they may also receive an email.

    If a reward payment has not been received by one of your users, check that they have added payment details. Verify that account details, country, and other mandatory fields are filled out in the correct format.
  </Accordion>

  <Accordion title="Can Cello provide sample email notifications?">
    All notification templates can be reviewed in the [Cello Portal](https://portal.cello.so/setup/notifications).
  </Accordion>

  <Accordion title="Can email templates be customized?">
    Yes - you can customize email templates self-serve directly in the [Cello Portal](https://portal.cello.so/setup/notifications). Open any email notification row to bring up the preview panel. Editable elements are highlighted on hover - click to change them.

    The following can be customized:

    * **Logo** - upload your company logo
    * **Presentation image** - upload a hero image shown prominently in the email body
    * **CTA link** - set the URL for the main call-to-action button

    The **Welcome email** has one additional option: a second CTA link, letting you point new users to a specific page alongside the referral program link.

    Changes apply across all email types at once. See [Email template customization](/guides/user-experience/referral-notifications-and-emails#email-template-customization) for full details.

    For changes to email body copy or wording, contact your Customer Success Manager.
  </Accordion>

  <Accordion title="How do I change the logo or image in my emails?">
    Go to **Notifications** in the [Cello Portal](https://portal.cello.so/setup/notifications) and click any email row to open the preview panel. Hover over the logo or image - a prompt will appear to click and replace it. Your changes apply across all email types automatically.
  </Accordion>

  <Accordion title="When are welcome emails triggered?">
    Welcome emails are typically sent to existing users after the referral program goes live, as well as to any new user who signs up after the program has gone live. 

    Common behavior:

    * Sent on the 6th day after a new user first signs up to introduce the referral program and its details.
    * Includes the referral link.
    * Sent only once per user.\
      \
      Please review the [notifications tab](https://portal.cello.so/setup/notifications) in portal for copy and timing. 
  </Accordion>

  <Accordion title="If a user views the widget in another language (example: French or German), will their emails match that language?">
    Email language is generally based on the last language used in the widget. Supported languages include English, German, Spanish, French, Dutch, Italian, Portuguese, Japanese, Danish, Slovak, and Romanian (more may be added on request).\
    \
    Enabling of email text in different languages requires config customization on the Cello side. Please coordinate with your Customer Success Manager for the additional languages required.
  </Accordion>

  <Accordion title="Why can’t we keep the notification dot always visible?">
    The dot is designed as an alerting mechanism to bring users into the widget when there’s something new. By design, it disappears once the user has seen the update.\
    \
    What we have seen work well for other customers are small "NEW" stickers on the right side of the menu items.
  </Accordion>

  <Accordion title="How do announcements, dots, and welcome/view emails work (typical timing)?">
    Typical sequence (often with \~1 day delay):

    * `welcome-dot`: after first boot if user remains inactive
    * `welcome-announcement`: a few days after first activity (or after the welcome-dot is displayed)
    * `welcome-email`: about one week after first boot. All other emails are transactional, meaning they are only sent in order to update the referrer on their referral activity.

    Note: actual timing can vary with payout delays and fraud/self-referral review. Please review the [notifications tab](https://portal.cello.so/setup/notifications) in portal for copy and timing.
  </Accordion>

  <Accordion title="My announcement is not showing even though showAnnouncement returns a fulfilled promise. What's wrong?">
    Most common cause: your **Announcement Selector** matches more than one element, and Cello (which uses `document.querySelector`) anchored the announcement to a hidden or zero-sized one. A fulfilled `showAnnouncement` promise only means the SDK accepted the call - it does **not** mean the announcement is visible.

    Run this in the browser console on the page where the announcement should appear:

    ```javascript theme={null}
    document.querySelectorAll("<your-announcement-selector>")
    ```

    * If the result is **more than one element**: narrow the selector. Cello uses the **first match in DOM order**, which may be a wrapper `<li>` or `<span>` with no layout box. Use a parent prefix or an `id` to target exactly one visible element.
    * If the result is **one element**: check `document.querySelector("<your selector>").getBoundingClientRect()`. If `width` or `height` is `0`, the class is on a wrapper, not the visible element - move the class/attribute to the icon or button you actually want anchored.
    * If the result is **zero elements**: the element doesn't exist when the announcement renders. Confirm it exists before `cello.boot()` and check `#`, `.`, `[]` prefix correctness.

    Update **Announcement Selector** in [Referrer Experience](https://portal.cello.so/setup/referrer-experience) once you have a selector that returns exactly one visible element.

    See [Troubleshooting: announcement not showing](/referral-component/custom-launcher#troubleshooting-announcement-not-showing) for the full diagnostic checklist and selector specificity examples.
  </Accordion>

  <Accordion title="How do I hide the notification badge?">
    Add `data-cello-badge="false"` to your custom launcher HTML element:

    ```html theme={null}
    <button
      id="your-launcher-element"
      data-cello-badge="false"
    >
      Invite a friend
    </button>
    ```

    This hides the badge while keeping the element clickable. See [Configure notification badge](/referral-component/custom-launcher#configure-notification-badge) for all options.

    **Common confusion:** there is no `hideDefaultBadge` boot option - this does not exist in the Cello JS SDK. The `hideDefaultLauncher` boot option is different - it hides the **entire default Cello button**, not just the badge.
  </Accordion>

  <Accordion title="The notification badge position is incorrect. How do I fix it?">
    You can test the notification badge by opening your own referral link. Common causes of incorrect positioning:

    * The badge is added on the menu level instead of the menu-item level.
    * The badge is positioned `absolute` but lacks a `relative` parent. Set the menu-item container to `position: relative`.
  </Accordion>

  <Accordion title="How are notifications managed overall? What is the notification logic?">
    Cello notifications fall into two categories:

    1. **In-product**: announcement banners and badge dots displayed inside the widget.
    2. **External**: emails (welcome, reward earned, first-view, etc.).

    These notifications and their triggers are designed to work alongside your existing product engagement touchpoints in a seamless way.
  </Accordion>

  <Accordion title="How can we subscribe to Cello release notes and new features?">
    Cello does not currently send out emails for new features or release notes. Reach out to your Cello contact for the latest updates.\
    \
    If we have major updates, we often post about them directly in a shared slack channel. We also have this section in our docs for updates: [Changelog](https://docs.cello.so/changelog)
  </Accordion>
</AccordionGroup>

## Fraud, self-referrals & abuse

<AccordionGroup>
  <Accordion title="What is the self-referral policy?">
    Cello provides automated capabilities to detect and mitigate self-referrals and fraudulent behavior. You can review marked referrals in our Cello Portal.

    **Related docs**

    * [Fraud detection](/guides/fraud-detection)
  </Accordion>

  <Accordion title="Can a rejected referral be reversed?">
    Yes. Reversals are done manually; coordinate with Cello support.
  </Accordion>

  <Accordion title="How do you handle abuse/self-referrals?">
    Cello uses multiple signals (including data you provide, e.g. emails) and behavioral patterns to detect likely self-referrals. High-certainty cases can be auto-rejected; others are flagged for review in the portal, where you can accept or reject.

    **Related docs**

    * [Fraud detection](/guides/fraud-detection)
  </Accordion>

  <Accordion title="Do you use hashed IP/fingerprinting to detect abuse?">
    Cello uses anonymized signals and patterns to detect suspicious behavior (e.g. matching anonymous identifiers across referrer and referee journeys). Exact methods may change over time.

    **Related docs**

    * [Fraud detection](/guides/fraud-detection)
  </Accordion>

  <Accordion title="If an account is suspended for fraud, will the referrer see that in the widget?">
    Currently, the widget may not show a detailed reason. In some cases, communication may happen via the referrer’s payout email (PayPal/Venmo).
  </Accordion>

  <Accordion title="How does the self-referral detection system work?">
    If a new user converts to a paying user, Cello looks at multiple data points before issuing a reward. These data points are checked for both sides:

    1. The **new user** (referred person).
    2. The **referrer** based on the **UCC** the new user used.

    These datapoints are compared to detect cases where the referrer and referred user are the same person or repeating pattern is used to abuse the program.
  </Accordion>
</AccordionGroup>

## Integrations, SDKs & implementation

<AccordionGroup>
  <Accordion title="What is the Cello Referral Component?">
    The Referral Component is an embeddable UI that integrates directly into your application, giving logged-in users a complete referral experience. It is powered by the [Cello JS SDK](/sdk/client-side/cello-js-introduction) and is separate from [Attribution JS](/sdk/client-side/attribution-js-introduction), which handles referral tracking on landing pages.

    The component provides:

    * **Referral link sharing** - users can copy their link, share via social media, email, or QR code
    * **Progress tracking** - real-time dashboard showing clicks, signups, and earned rewards
    * **Reward payouts** - automated payouts via PayPal, Venmo, and other methods
    * **Notifications** - in-app and email alerts for referral activity and milestones
    * **Multi-language support** - 10+ languages with automatic detection
    * **Dark mode** - light and dark themes with configurable colors per theme

    It can be added as a floating button or integrated into your existing navigation using a [custom launcher](/referral-component/custom-launcher). Native SDKs are available for web, iOS, Android, and React Native.

    Integration requires three steps: load the SDK, generate a JWT token server-side, and boot the component with the user’s details.

    **Related docs**

    * [Referral Component introduction](/referral-component/introduction)
    * [Quickstart](/referral-component/quickstart)
    * [Custom launcher](/referral-component/custom-launcher)
  </Accordion>

  <Accordion title="Which programming languages/frameworks are supported?">
    Cello is framework-agnostic. The [Cello JS SDK](/sdk/client-side/cello-js-introduction) is a browser-based JavaScript library that works with any web framework whose output is HTML/JavaScript, including:

    * **React**, **Next.js**, **Vue**, **Angular**, **Svelte**, and vanilla JavaScript

    For **server-side rendering** frameworks (e.g., Next.js), Cello interactions must occur client-side - the SDK runs in the browser and cannot be initialized server-side. For **Angular** specifically, avoid using `<body>` as your AppRoot element to prevent a race condition with Cello’s DOM injection (see the [Angular notes in the quickstart](/referral-component/quickstart#do-you-support-angular)).

    For **mobile apps**, native SDKs are available:

    * [iOS SDK](/sdk/mobile/ios) - Swift/Objective-C with CocoaPods and SPM
    * [Android SDK](/sdk/mobile/android) - Kotlin/Java with Gradle
    * [React Native SDK](/sdk/mobile/react-native) - cross-platform mobile
    * [Flutter SDK](/sdk/mobile/flutter) - cross-platform mobile

    **Related docs**

    * [Cello JS SDK introduction](/sdk/client-side/cello-js-introduction)
    * [Quickstart](/referral-component/quickstart)
    * [Mobile SDK introduction](/sdk/mobile/introduction)
  </Accordion>

  <Accordion title="Can we remove the Cello branding?">
    No - Cello branding on the Referral Component is required and cannot be removed. This is primarily a trust and compliance requirement, not advertising.

    When referrers receive reward payouts, the transaction appears on their bank or PayPal account as a payout **from Cello**. The branding in the widget ensures users understand that the referral program is powered by Cello before they receive a payout. Without it, users may not recognize the payment and could flag it as fraud or issue chargebacks - which can harm both your referral program and your referrers.

    The branding is designed to be non-intrusive. While the Cello logo cannot be removed, the Referral Component inherits your app’s theming - including separately configurable light and dark mode colors - and many other aspects of the UI can be customized through the [Cello Portal](/guides/user-experience/overview).
  </Accordion>

  <Accordion title="Can we customize widget text and UI?">
    The Referral Component splits into two buckets: visual configuration that you do self-serve in the [Cello Portal](https://portal.cello.so), and copy/text changes that are managed by Cello on request.

    **Self-serve in the Portal:**

    * **Brand colors** - primary color, Cello button color, Fab icon color, and notification badge color, with separate values for light and dark mode
    * **Button style and position** - choose from 3 styles, set screen position, or use a [custom launcher](/referral-component/custom-launcher) integrated into your own menu
    * **Component type** - display as a pop-up or a modal, with configurable position
    * **Sharing options** - enable/disable link copy, social sharing (Twitter, LinkedIn, email), and QR codes
    * **Payment methods** - configure PayPal, Venmo, or other payout options for referrers
    * **Announcement display style** - choose between toasts, banners, or anchored announcements
    * **Language switching at runtime** - language enablement itself is configured by Cello, but the SDK exposes [`language`](/sdk/client-side/cello-js-usage#cellobootoptions) and [`changeLanguage`](/sdk/client-side/cello-js-usage#changelanguagelanguage) to switch between enabled languages

    **Requires a request to Cello (not self-serve):**

    * **Widget text** - onboarding steps, offer description, button labels, and any other in-widget copy
    * **New User Banner copy** - banner message and CTA wording
    * **Announcement text content**
    * **Email body copy**
    * **Translated copy in any enabled language** - Cello maintains translations centrally; customers do not edit translation strings directly
    * **Default language for your product** - English by default; contact support to change it
    * **Enabling additional languages** beyond what's already enabled for your product

    Why this split exists: Cello maintains consistent copy across 10+ languages and many reward structures, so copy changes are reviewed and rolled out by the Cello team. To request a copy change, email [support@cello.so](mailto:support@cello.so) or message your CSM with the surface, the current text, and the proposed new text (and the language(s) it should apply to).

    **Related docs**

    * [Configuring Referral Component](/guides/user-experience/configuring-referral-component)
    * [User experience overview](/guides/user-experience/overview#texts-and-language)
    * [Setting up campaigns](/guides/campaigns/setting-up-campaigns)
  </Accordion>

  <Accordion title="Can we update or customize copy anywhere in Cello (widget, banner, announcements, emails)?">
    No - copy updates are **not self-serve** in Cello. This applies to **all** user-facing text: the Referral Component (widget) onboarding and offer description, the New User Banner message, announcement text, and email body copy. Cello maintains consistent copy across customers to support 10+ languages and multiple reward structures.

    What you **can** configure self-serve in the [Portal](https://portal.cello.so/setup/notifications):

    * **Enable or disable** specific notification types (alerts, announcements, emails)
    * **Choose announcement display style** - toasts, banners, or anchored to your launcher
    * **Configure the activation campaign** - recurring announcements to re-engage inactive referrers
    * **Set reward amounts and program details** - which flow into the standardized email templates automatically
    * **Email template visual elements** - logo, hero/presentation image, and CTA link (see [Email template customization](/guides/user-experience/referral-notifications-and-emails#email-template-customization))
    * **Enable/disable** the New User Banner and adjust any styling options exposed in the Portal

    What requires Cello involvement:

    * **Widget text** - onboarding steps, offer description, button labels, in-widget copy
    * **New User Banner copy** - banner message and CTA wording
    * **Announcement text content**
    * **Email body copy** (e.g., "How It Works" page, reward email wording)
    * **Translated wording** in any enabled language - translations are managed centrally by Cello

    To request a copy change, email [support@cello.so](mailto:support@cello.so) or message your CSM with the surface (widget / banner / announcement / email), the current text, the proposed new text, and the language(s) the change should apply to.

    Based on Cello’s experience across hundreds of programs, modifications to email or announcement copy have minimal impact on referral performance. The biggest drivers of success are creating more impactful in-product touchpoints, such as Moments of Delight and deeper mobile app integration.

    **Related docs**

    * [Referral notifications and emails](/guides/user-experience/referral-notifications-and-emails)
    * [Configuring Referral Component - text and copy](/guides/user-experience/configuring-referral-component#customize-text-and-copy)
    * [User experience overview](/guides/user-experience/overview#texts-and-language)
  </Accordion>

  <Accordion title="Can we change placement/style of the landing/referral messages?">
    Yes - the referral landing page experience is highly customizable. You control the landing page itself, and Cello provides several approaches for the referee signup flow:

    1. **Dedicated landing page** (recommended) - build a custom page optimized for referral conversions. Dedicated pages have shown up to **2x higher conversion rates**. You have full control over layout, design, messaging, and CTAs.

    2. **Home page with inline personalization** - use your existing home page and add referral-specific personalization (referrer name, discounts) directly into your headline and CTA using the Attribution JS API (\`getReferrerName()\`, \`getCampaignConfig()\`).

    3. **Home page with New User Banner** (no code) - add the Cello attribution script and enable the New User Banner in the [Cello Portal](https://portal.cello.so/setup/new-user-experience). The banner automatically displays personalization and discount info to visitors arriving via referral links.

    For the **Referral Component** (the in-app widget for logged-in referrers), you can configure its placement (floating button position or [custom launcher](/referral-component/custom-launcher)), display type (pop-up or modal), and brand colors in the Portal.

    For adjustments beyond what’s available in the Portal, contact your CSM or the Cello product team with screenshots and the desired behavior.

    **Related docs**

    * [Optimizing referral landing pages](/guides/user-experience/optimizing-landing-pages)
    * [Personalizing referrals](/guides/user-experience/personalizing-referrals)
    * [Configuring Referral Component](/guides/user-experience/configuring-referral-component)
  </Accordion>

  <Accordion title="How do we validate a UCC?">
    Use the Referral Codes API endpoint `GET /referral-codes/{code}` to validate a UCC and identify the referrer it belongs to.

    The API returns:

    * `valid` - whether the UCC is a legitimate, active referral code
    * `productUserId` - the referrer’s user ID in your system
    * `campaignId` - the campaign the UCC is associated with

    Example response for a valid code:

    ```json theme={null}
    {
      "code": "UIPIWa2Hnnr",
      "productUserId": "12345678",
      "valid": true,
      "campaignId": "campaign_partners_1"
    }
    ```

    Common use cases for UCC validation:

    * **Self-referral prevention** - compare the returned `productUserId` (referrer) with the logged-in user’s ID; if they match, ignore the UCC
    * **Existing-user attribution** - validate the UCC on login before storing it for future purchase attribution
    * **Tampered URL detection** - if someone modifies the `ucc` query parameter, the API will return `valid: false`
    * **Discount eligibility** - validate the UCC before applying a new-user discount to ensure the code hasn’t been tampered with

    **Related docs**

    * [Fetch referral code info](/api-reference/referral-codes/fetch-referral-code-info)
    * [API authentication](/api-reference/authentication/get-token)
  </Accordion>

  <Accordion title="Can we grab the UCC directly from the URL instead of calling getUcc()?">
    Yes - the `ucc` is always present as a query parameter in the URL when a user clicks a referral link (e.g., `https://yoursite.com/?ucc=ABC123`). You can parse it directly from the URL using standard JavaScript:

    ```javascript theme={null}
    const ucc = new URLSearchParams(window.location.search).get('ucc');
    ```

    However, using `getUcc()` from the [Attribution JS](/sdk/client-side/attribution-js-introduction) library is recommended because it:

    * **Falls back to cookies** - if the user navigated away and the `ucc` is no longer in the URL, `getUcc()` retrieves it from the stored cookie
    * **Handles async loading** - waits for the attribution script to initialize before returning data
    * **Works across sessions** - cookies persist for 3 months, covering indirect signups where the user returns later

    If you read the `ucc` from the URL yourself, make sure to store it in your database linked to the user at signup, so it’s available when sending [signup](/attribution/tracking-signups) and [purchase](/attribution/tracking-purchase) events to Cello.

    **Related docs**

    * [Attribution JS usage & API](/sdk/client-side/attribution-js-usage)
    * [Web attribution](/attribution/for-web)
  </Accordion>

  <Accordion title="Does Cello integrate with Salesforce?">
    Yes. Cello integrates with Salesforce via **Apex triggers** that call the Cello API when specific conditions are met in your Salesforce workflow. This is the recommended approach for sales-led referral attribution.

    A typical setup uses an Apex trigger on the Opportunity object to send events to Cello when:

    * An Opportunity reaches a specific stage (e.g., "Proposal/Price Quote")
    * The Opportunity has a `cello_ucc` custom field populated

    This allows you to attribute deals and revenue back to the referrer who generated the lead. The trigger calls the Cello API to send demo-attended or purchase events based on your sales flow.

    The integration is not a native Salesforce app - it uses standard Apex triggers and HTTP callouts to the Cello API, giving you full control over which events are sent and when. You can also send events directly via the [Cello API](/api-reference/generic-events/send-event) if you prefer not to use Apex triggers.

    **Related docs**

    * [Salesforce Apex triggers guide](/integrations/salesforce-apex-triggers)
    * [Cello API - send events](/api-reference/generic-events/send-event)
  </Accordion>

  <Accordion title="Turbo (turbo-rails / Hotwire) hides the widget iframe on navigation. Any guidance?">
    This happens because Turbo replaces the page’s DOM during navigation, but the Cello widget iframe persists in an orphaned state. The fix is to shut down the widget before Turbo transitions, then re-initialize it on the new page.

    Add an event listener to call `shutdown` before Turbo navigates:

    ```javascript theme={null}
    document.addEventListener("turbo:visit", () => {
      window.Cello("shutdown");
    });
    ```

    Or alternatively, just before Turbo renders new content:

    ```javascript theme={null}
    document.addEventListener("turbo:before-render", () => {
      window.Cello("shutdown");
    });
    ```

    `turbo:visit` (recommended) fires at the start of navigation before any network requests, giving the cleanest teardown. `turbo:before-render` fires later, just before the new page content replaces the DOM.

    After shutdown, Cello needs to be re-initialized on the new page. If your Cello boot script is in the page layout, it will run automatically after Turbo renders. If not, listen for `turbo:load` to re-boot:

    ```javascript theme={null}
    document.addEventListener("turbo:load", () => {
      window.cello.cmd.push(async function (cello) {
        await cello.boot({ /* your boot config */ });
      });
    });
    ```

    **Related docs**

    * [Cello JS SDK](/sdk/client-side/cello-js-introduction)
    * [Quickstart](/referral-component/quickstart)
  </Accordion>

  <Accordion title="We integrated React Native (Expo) and see APIError error 1 on init. What should we check?">
    This error (`InitializationError`) typically means the SDK is connecting to the wrong environment. When using Expo, the `env` property defaults to `prod` - if your credentials are for the sandbox environment but `env` is not explicitly set, initialization will fail.

    Verify that the `env` value is correctly nested under the `@getcello/cello-react-native` Expo plugin in your `app.json`:

    ```json theme={null}
    {
      "expo": {
        "plugins": [
          [
            "@getcello/cello-react-native",
            {
              "env": "sandbox"
            }
          ]
        ]
      }
    }
    ```

    After changing the plugin configuration, you must rebuild the native app:

    ```bash theme={null}
    npx expo prebuild --clean
    ```

    Also check:

    * **Credentials** - ensure `productId` and `token` match the environment you selected (`sandbox` vs `prod`)
    * **Not using Expo Go** - the Cello SDK requires a custom native build and will not work in Expo Go (you’ll see a `LINKING_ERROR` instead)

    **Related docs**

    * [React Native SDK](/sdk/mobile/react-native)
  </Accordion>

  <Accordion title="Our signup discount isn’t applied when the referee reaches payment stage. Why?">
    Referral discounts are applied by **your** subscription platform (Stripe, Chargebee, etc.) during subscription creation - Cello does not apply them automatically. If the discount isn’t showing up, check the following in order:

    1. **Is the `ucc` available at checkout?** - verify the referral code was preserved from signup to the payment flow. Check your Stripe/Chargebee customer metadata for `cello_ucc`, or call `getUcc()` from the Attribution Library. If it’s missing, the attribution cookie may have been lost (e.g., cookie consent blocked it, or the user is on a different domain).

    2. **Is the user eligible for a discount?** - use the [New User Reward API](/api-reference/new-users/get-reward) to validate eligibility, or check for `cello_ucc` in the customer object in your payment platform. Note that `eligible: false` can also be returned if the user signed up with **their own UCC** or the signup was flagged as a **self-referral** by Cello's [fraud detection](/guides/fraud-detection) - self-referrals do not qualify for new-user discounts.

    3. **Is the coupon applied during subscription creation?** - discounts must be passed at the time the subscription is created (e.g., via `discounts` array in Stripe or `coupon_ids` in Chargebee). They cannot be attached after the fact through the API.

    4. **Is the coupon valid?** - verify the coupon ID exists in your payment platform, hasn’t expired, and matches the plan type being purchased.

    **Related docs**

    * [Apply discounts guide](/attribution/apply-discounts)
    * [New User Reward API](/api-reference/new-users/get-reward)
    * [Web attribution](/attribution/for-web)
  </Accordion>

  <Accordion title="I added the CDN script in index.html and sometimes the page errors on load. Why?">
    This typically happens because the Cello script downloads and executes before the `<body>` element is available in the DOM. Cello appends its UI to `<body>`, so if `<body>` doesn’t exist yet, initialization fails.

    **Recommended fix:** Ensure the script tag includes `type="module"` and `async`, and is placed in the `<head>`:

    ```html theme={null}
    <script type="module" src="https://assets.cello.so/app/latest/cello.js" async></script>
    ```

    The `type="module"` attribute defers execution until after the DOM is parsed, which prevents the race condition. If you’re still seeing issues:

    * **Use the command queue pattern** - initialize Cello through the queue so commands are buffered until the script is ready:
      ```javascript theme={null}
      window.cello = window.cello || { cmd: [] };
      window.cello.cmd.push(async function (cello) {
        await cello.boot({ /* your config */ });
      });
      ```
    * **Move the script to the end of `<body>`** - as a fallback, placing the `<script>` tag just before `</body>` ensures the DOM is fully available
    * **Angular users** - avoid using `<body>` as your AppRoot element, as this creates a race condition with Cello’s DOM injection. See the [Angular notes](/referral-component/quickstart#do-you-support-angular)

    **Related docs**

    * [Quickstart - load the script](/referral-component/quickstart#step-1-load-the-script)
    * [Cello JS SDK](/sdk/client-side/cello-js-introduction)
  </Accordion>

  <Accordion title="await window.CelloAttribution('getUcc') can hang if the script fails to load. What should we do?">
    If the Attribution JS script fails to load (due to network issues, ad blockers, or CSP restrictions), the `getUcc()` promise will never resolve or reject - it waits indefinitely for the script to initialize.

    **Never place `getUcc()` on a critical path** that blocks your signup or checkout flow. Instead, race it against a timeout:

    ```javascript theme={null}
    async function getUccWithTimeout(timeoutMs = 3000) {
      try {
        const ucc = await Promise.race([
          window.CelloAttribution('getUcc'),
          new Promise((_, reject) =>
            setTimeout(() => reject(new Error('Attribution timeout')), timeoutMs)
          )
        ]);
        return ucc;
      } catch {
        return null;
      }
    }
    ```

    If the timeout wins, proceed without the UCC - the user likely didn’t arrive via a referral link, or you can fall back to reading the `ucc` directly from the URL query parameter as a backup.

    **Related docs**

    * [Attribution JS usage & API](/sdk/client-side/attribution-js-usage)
    * [Attribution JS introduction](/sdk/client-side/attribution-js-introduction)
  </Accordion>

  <Accordion title="The widget doesn't load and the console shows 'Refused to connect / Refused to execute script... Content Security Policy' errors. What do we do?">
    Console errors containing `violates the following Content Security Policy directive` or `violates the document's Content Security Policy` mean your site's CSP headers are blocking Cello resources. Typical examples:

    ```text theme={null}
    Refused to execute inline script because it violates the following Content Security Policy directive: "script-src ..."
    Refused to load the script 'https://assets.cello.so/app/latest/cello.js' because it violates the following Content Security Policy directive: "script-src ..."
    Fetch API cannot load https://share.cello.so/api/products/.../initialize/v2. Refused to connect because it violates the document's Content Security Policy.
    Refused to load the image because it violates the following Content Security Policy directive: "img-src ..."
    ```

    The fix is on your side: extend your CSP with the Cello sources. The directive named in the error (`script-src`, `connect-src`, `img-src`, `style-src`, `font-src`) tells you which one is missing.

    ```text theme={null}
    script-src  https://assets.cello.so
    connect-src https://share.cello.so
    img-src     https://cdn.cello.so https://p.typekit.net blob: data:
    style-src   https://use.typekit.net 'unsafe-inline'
    font-src    https://use.typekit.net https://p.typekit.net
    ```

    These directives extend your existing policy; if you use a restrictive `default-src`, keep all of the above explicit. Sandbox environments use the equivalent `*.sandbox.cello.so` domains (for example `https://assets.sandbox.cello.so` and `https://share.sandbox.cello.so`).

    **Related docs**

    * [Quickstart - Content Security Policy (CSP)](/referral-component/quickstart#content-security-policy-csp)
    * [Quickstart - load the script](/referral-component/quickstart#step-1-load-the-script)
  </Accordion>

  <Accordion title="Can we embed referral links in our own marketing channels (newsletters, emails, etc.)?">
    Yes. You can include a referral link in newsletters, email campaigns, blog posts, in-app messages, or any owned channel to attribute signups back to a specific referrer and offer new-user discounts. The link just needs to contain the `ucc` parameter so the Attribution Library captures it on the landing page.

    To get a user's referral link programmatically, use the Active Link API:

    ```
    GET /referral-codes/active-link/{productUserId}
    ```

    This returns the user's active `ucc` and invite link, which you can embed in your templates. When recipients click the link and land on your site, the Attribution Library captures the `ucc` and attribution proceeds as normal.

    **Tips for marketing-channel referral links:**

    * **Use a dedicated or internal account** as the referrer if you want to track campaign-driven signups separately from user-to-user referrals
    * **Ensure the landing page has the Attribution Library installed** so the `ucc` is captured and persisted
    * **Configure a new-user discount** in your [campaign settings](/guides/campaigns/setting-up-campaigns) if you want to offer discounts to readers who sign up

    **Related docs**

    * [Fetch active link API](/api-reference/referral-codes/fetch-active-link)
    * [Optimizing referral landing pages](/guides/user-experience/optimizing-landing-pages)
    * [Incentivized casual contact loop](/guides/best-practices/incentivized-casual-contact-loop)
  </Accordion>

  <Accordion title="How do we cancel or stop reward payments when a subscription is cancelled or refunded?">
    Yes - Cello supports automatic and manual cancellation of rewards depending on your integration:

    **Stripe / Chargebee webhooks (automatic)**
    When you connect Stripe or Chargebee webhooks, Cello automatically detects cancellation and refund events:

    * `subscription.deleted` - stops future recurring rewards for that referral
    * `charge.refunded` - automatically cancels any **pending** reward for that transaction

    No additional API calls are needed; Cello reacts to these webhook events as they arrive.

    **Cello API (manual)**
    If you use the Cello API instead of webhooks, send a `charge-refunded` event via `POST /events` to cancel pending rewards for a specific transaction:

    ```bash theme={null}
    POST https://api.cello.so/events

    {
      "eventName": "ReferralUpdated",
      "payload": {
        "ucc": "cello-ucc",
        "newUserId": "new-user-id",
        "price": 1000,
        "currency": "EUR"
      },
      "context": {
        "event": {
          "trigger": "charge-refunded",
          "timestamp": "2024-01-15 10:00:00"
        },
        "subscription": {
          "invoiceId": "invoice-id"
        }
      }
    }
    ```

    **Important nuances:**

    * **Pending rewards** are automatically cancelled when a refund event is received
    * **Already paid-out rewards** are not clawed back from referrers - Cello does not ask referrers to return money
    * To mitigate refund risk, many programs **spread rewards over time** (e.g. monthly over 12 months) so future payouts can be stopped if the deal falls through
    * Cello can also configure a **payout delay** (e.g. 14 days) matching your refund window - contact your CSM to set this up
    * You can review reward status (CREATED, CANCELED, REJECTED, PROCESSED) in the Cello Portal under the Purchases page

    **Related docs**

    * [Tracking purchases - charge-refunded event](/attribution/tracking-purchase#charge-refunded)
    * [Fraud detection and payout delays](/guides/fraud-detection)
    * [Stripe webhook setup](/integrations/webhooks/stripe-webhook)
    * [In-product rewards and reward status](/guides/in-product-rewards)
  </Accordion>

  <Accordion title="Does the productId need to match our domain?">
    No. The `productId` is simply an identifier for your Cello account - it does not need to match your application domain, staging URL, or any specific URL pattern. It may look like a domain, but that is just a naming convention; it has no functional connection to where you deploy Cello.

    What **does** matter is using the correct credentials for each environment:

    * **Sandbox credentials** (`productId` + `PRODUCT_SECRET` from the Sandbox environment) for development and staging
    * **Production credentials** (`productId` + `PRODUCT_SECRET` from the Production environment) for your live application

    You can find both sets of credentials on the [Access Keys page](https://portal.cello.so/integrations/accesskeys) in the Cello Portal.

    **Common errors from mismatched credentials:**

    * Using a Sandbox `productId` with a Production `PRODUCT_SECRET` (or vice versa)
    * Calling the wrong Cello API environment from your backend
    * Using the wrong webhook signing secret for the environment

    **Related docs**

    * [Referral Component quickstart - prerequisites](/referral-component/quickstart#prerequisites)
    * [Implementation testbook](/guides/integration/implementation-testbook)
  </Accordion>

  <Accordion title="Is there a headless API? Do we have to use the Referral Component (widget)?">
    Cello is designed as a **hybrid system** - some parts are API-driven and fully headless, while the Referral Component (widget) handles the referrer-facing experience.

    **What is fully headless (API-only):**

    * **Attribution** - capturing referral codes, tracking signups, and tracking purchases all happen via the Cello API or Stripe/Chargebee webhooks, with no widget required
    * **Referral code validation** - `GET /referral-codes/{code}` to verify a UCC
    * **Active link retrieval** - `GET /referral-codes/active-link/{productUserId}` to fetch a user’s referral link programmatically
    * **Event sending** - `POST /events` for signup, purchase, and refund events

    **What the Referral Component provides (and cannot be rebuilt independently):**
    The widget handles the **referrer experience** - the part where users share their link, see who signed up, track rewards, and manage payouts. This includes:

    * Link sharing (copy, social media, email, QR code)
    * Progress tracking (clicks, signups, earnings)
    * Reward payouts and payment details management (PayPal/Venmo)
    * In-app notifications, announcements, and onboarding
    * Multi-language support and dark mode

    Cello does not currently provide an SDK or API to rebuild these features (such as the rewards view or payment details management) as a fully custom UI. The Referral Component is the only way to deliver the complete referrer experience.

    **What you can do - extend with custom placements:**
    While the Referral Component is required for the full experience, you can - and are encouraged to - surface referral information in additional places throughout your product using the Cello JS SDK or API:

    * Use [`getActiveUcc()`](/sdk/client-side/cello-js-usage#getactiveucc) to retrieve a user’s referral link
    * Use [`getCampaignConfig()`](/sdk/client-side/cello-js-usage#getcampaignconfig) to fetch reward amounts and program details
    * Use [`getLabels()`](/sdk/client-side/cello-js-usage#getlabels) to get localized text for your custom elements

    This allows you to build mini-cards, banners, or menu items that display the referral link and campaign info at [moments of delight](/guides/best-practices/contextual-sharing) - for example, after a user completes a milestone or receives positive feedback. These custom placements complement the Referral Component and are a best practice for increasing sharing activity.

    The Referral Component itself can be [themed, repositioned](/guides/user-experience/configuring-referral-component), and launched from a [custom launcher](/referral-component/custom-launcher) (any button, menu item, or link) to feel native to your product.

    **Related docs**

    * [Cello API reference](/api-reference/introduction)
    * [Referral Component introduction](/referral-component/introduction)
    * [Custom launcher](/referral-component/custom-launcher)
    * [Contextual sharing - moments of delight](/guides/best-practices/contextual-sharing)
    * [Cello JS SDK - usage](/sdk/client-side/cello-js-usage)
  </Accordion>

  <Accordion title="How does Cello handle language and localization?">
    Cello supports 10+ languages: English, German, Spanish, French, Dutch, Italian, Portuguese, Japanese, Danish, Slovak, and Romanian. More can be added on request.

    **What is enabled by default**

    * **English is the only language enabled out of the box** for every product, and it also serves as the **fallback** whenever an unknown or unenabled language is requested.

    **Enabling additional languages**

    * Any language other than English (including the supported languages listed above) must be **enabled for your product by Cello** before it takes effect. Passing a `language` code via the SDK does not enable it - if the language is not enabled, the component silently falls back to your product's default language.
    * To enable additional languages, or to add a brand-new language not in the supported list, contact your CSM or [support@cello.so](mailto:support@cello.so).

    **Changing the default language**

    * You can request Cello to change your product's **default language** from English to one of the enabled languages (e.g., German for a DACH-focused product). This is also done via support / CSM.

    **Editing the translated text**

    * Translations for each enabled language are written and maintained by Cello to stay consistent across reward structures. Customers **cannot** edit translation strings themselves in the Portal or via API. To request wording changes in any language, contact support with the surface, current text, and proposed new text.

    **How language is selected at runtime**

    | Surface                         | How language is selected                                                                                                                                                                               | Fallback                                            |
    | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------- |
    | **Referral Component (web)**    | Pass `language` (ISO 639-1) during [`cello.boot()`](/sdk/client-side/cello-js-usage#cellobootoptions), or call [`changeLanguage()`](/sdk/client-side/cello-js-usage#changelanguagelanguage) at runtime | Product default language (English unless changed)   |
    | **Referral Component (mobile)** | Pass `language` during SDK initialization ([iOS](/sdk/mobile/ios), [Android](/sdk/mobile/android), [React Native](/sdk/mobile/react-native), [Flutter](/sdk/mobile/flutter))                           | Device language, then product default, then English |
    | **Referee landing page**        | Detected from the visitor's browser settings                                                                                                                                                           | English                                             |
    | **Emails**                      | Last language used by the user in the widget                                                                                                                                                           | Product default language                            |

    **Changing language at runtime:**
    You can switch the language without re-initializing the component:

    * **Web:** `window.Cello("changeLanguage", "de")`
    * **Mobile:** `Cello.changeLanguage("de")` (iOS 14+ required on iOS)

    **Related docs**

    * [Texts and language overview](/guides/user-experience/overview#texts-and-language)
    * [Cello JS boot options](/sdk/client-side/cello-js-usage#cellobootoptions)
    * [Cello JS - changeLanguage](/sdk/client-side/cello-js-usage#changelanguagelanguage)
  </Accordion>

  <Accordion title="How do we retrieve campaign details (like reward percentage, new-user discount) programmatically?">
    Yes - campaign details are available through both the **Cello JS SDK** and the **Attribution JS SDK**. Use the JavaScript approach for displaying discount info in your UI. Use the REST API only for applying the discount on the backend or for server-side rendered apps.

    **Which method to use:**

    * `getCampaignConfig()` (JS) - best for **displaying** discount info client-side. Runs in the browser, no backend round-trip needed. This is the right choice for most apps.
    * [New User Reward API](/api-reference/new-users/get-reward) - use on the **backend** when applying the discount at checkout, or if your app is **server-side rendered** (e.g., PHP, Next.js SSR) and needs discount details before the page loads.

    **For logged-in users (Referral Component / Cello JS):**
    Use `getCampaignConfig()` to retrieve the current user’s campaign details:

    ```javascript theme={null}
    const config = await window.Cello("getCampaignConfig");
    ```

    Returns:

    * `primaryCurrency` - reward currency code
    * `revenuePercentage` - referrer reward as a percentage of revenue
    * `rewardCap` - maximum reward per referral
    * `newSignupBonus` - bonus reward for each signup
    * `newPurchaseBonus` - bonus reward for each purchase
    * `newUserDiscountPercentage` - discount for referred new users (decimal: `0.1` = 10%)
    * `newUserDiscountMonth` - how many months the discount lasts

    **For landing page visitors (Attribution JS):**
    Use `getCampaignConfig()` from the Attribution SDK to display discount info before signup:

    ```javascript theme={null}
    const config = await window.CelloAttribution("getCampaignConfig");
    const discountPercent = config.newUserDiscountPercentage * 100;
    const months = config.newUserDiscountMonth;
    // e.g. "10% off for 3 months"
    ```

    **Mobile SDKs** also expose `getCampaignConfig()` on [iOS](/sdk/mobile/ios), [Android](/sdk/mobile/android), [React Native](/sdk/mobile/react-native), and [Flutter](/sdk/mobile/flutter).

    **Note on `campaignId` in the referral-codes API:**
    When calling `GET /referral-codes/{code}`, the response includes a `campaignId` field. For the **default campaign**, this field returns an empty string - this is expected behavior, not an error. Non-default campaigns return their actual campaign ID.

    **Related docs**

    * [Cello JS - getCampaignConfig()](/sdk/client-side/cello-js-usage#getcampaignconfig)
    * [Attribution JS introduction](/sdk/client-side/attribution-js-introduction)
    * [Personalizing referral landing pages](/guides/user-experience/personalizing-referrals)
    * [Referral codes API](/api-reference/referral-codes/validate-referral-code)
  </Accordion>
</AccordionGroup>

## Authentication & tokens

<AccordionGroup>
  <Accordion title="Why is my JWT token being rejected? How does the iat timestamp validation work?">
    The `iat` (issued at) field in the JWT payload is a Unix timestamp that tells Cello when the token was created. Cello enforces a validation window on this value:

    * The `iat` **cannot be older than 24 hours** - tokens issued more than 24 hours ago are considered expired
    * The `iat` **cannot be set in the future** - tokens with a future timestamp are rejected as invalid

    If your server’s clock is out of sync (even by a few minutes), the `iat` value may fall outside these bounds and the token will be rejected, causing a boot failure with a token/authorization error.

    **How to fix:**

    1. **Verify your server’s system clock** - ensure it is synchronized using NTP (Network Time Protocol)
    2. **Use `Math.floor(Date.now() / 1000)`** (or equivalent in your language) to generate the `iat` value dynamically rather than hardcoding it
    3. **Check your environment** - staging and production servers may have different time configurations

    **Example token payload:**

    ```json theme={null}
    {
      "productId": "your-product-id",
      "productUserId": "user-123",
      "iat": 1662712365
    }
    ```

    **Related docs**

    * [User authentication and JWT generation](/sdk/client-side/user-authentication)
    * [Token update and expiry handling](/guides/token-update)
  </Accordion>

  <Accordion title="How does Cello API token authentication work? Does the refresh token expire?">
    The Cello REST API uses a two-token authentication model (separate from the widget JWT used for the Referral Component):

    1. **Authenticate** - call `POST /token` with your `accessKeyId` and `secretAccessKey` (from the [Access Keys page](https://portal.cello.so/integrations/accesskeys)). This returns:
       * `accessToken` - short-lived token used in the `Authorization` header for API requests
       * `refreshToken` - longer-lived token used to get new access tokens without re-authenticating
       * `expiresIn` - how long the access token is valid

    2. **Refresh** - when the access token expires, call `POST /token` with the `refreshToken` to get a new `accessToken`. This does **not** return a new refresh token.

    3. **Re-authenticate** - the refresh token expires after **5 days**. Once expired, you must perform a full authentication again with your `accessKeyId` and `secretAccessKey` to obtain both a new access token and a new refresh token.

    There is no separate endpoint to retrieve only a new refresh token - refresh tokens are only issued during full authentication.

    **Recommended pattern for long-running integrations:**

    * Store the refresh token securely after initial authentication
    * Use it to refresh the access token before it expires
    * Implement a fallback to full re-authentication when the refresh token itself expires (after 5 days)

    **Related docs**

    * [API authentication - get token](/api-reference/authentication/get-token)
    * [API introduction](/api-reference/introduction)
  </Accordion>
</AccordionGroup>
