Skip to content

Scell Onboarding Widget — Developer Documentation

Package: @scell/onboarding-widget v2.0.0 Custom element: <scell-onboarding> Build output: ES module + IIFE (UMD CJS also exported)


Table of Contents

  1. Overview
  2. Installation
  3. Quick Start
  4. Configuration
  5. Onboarding Flow — The 3 Steps
  6. Events and Callbacks
  7. Customization
  8. Integration — React
  9. Integration — Vue 3
  10. Integration — Vanilla JS
  11. Public API Reference
  12. Troubleshooting

1. Overview

The Scell Onboarding Widget is a framework-agnostic Web Component (<scell-onboarding>) that embeds a complete B2B onboarding flow into any web page. It is designed for partner platforms that delegate the Scell account creation process to their end customers.

What it does

  • Guides a business user through a 3-step onboarding delegated entirely to SuperPDP
  • Opens a SuperPDP OAuth2 popup that handles account creation, KYB, and identity verification
  • Closes the popup and shows a success screen once SuperPDP returns the authorization code
  • Returns an authorization_code to the partner platform upon completion, which can then be exchanged for tenant credentials via the Scell Partner API

Why it exists

Partners integrating Scell (invoicing, signatures) need their own clients to self-onboard. Rather than building a custom KYB UI, partners embed this widget. The widget delegates the entire KYB process to SuperPDP via OAuth2 and receives a callback with the authorization code. The partner exchanges that code for API credentials using POST /v1/onboarding/exchange.

What changed from v1

v1 (deprecated) handled KYB internally: SIRET verification via INSEE, manual document uploads, representative form. v2 delegates everything to SuperPDP. The widget HTML interface and events are backward-compatible where possible, but the step names and some events have changed (see sections 5 and 6).

Technical design

  • Implemented as a native Custom Element (HTMLElement subclass)
  • Uses Shadow DOM (mode: open) for complete style encapsulation — host page CSS cannot bleed in
  • Zero runtime dependencies (no React, Vue, Angular, or any external library)
  • Communicates with the Scell API using standard fetch, headers X-Publishable-Key and X-Session-Id
  • Opens a SuperPDP authorization popup via window.open and listens for a postMessage from the backend callback page
  • Emits standard CustomEvent instances that bubble up through the DOM
  • Supports light, dark, and auto (OS preference) themes via CSS custom properties exposed on :host
  • Fully responsive, tested down to 320 px viewport width

2. Installation

npm / pnpm / yarn

bash
npm install @scell/onboarding-widget
# or
pnpm add @scell/onboarding-widget
# or
yarn add @scell/onboarding-widget

The package ships pre-built in dist/. No build step required on the consumer side.

CDN (script tag — IIFE build)

html
<script src="https://cdn.scell.io/widget/v1/onboarding.js"></script>

Or pin to a specific version:

html
<script src="https://cdn.scell.io/widget/1.1.0/onboarding.js"></script>

The IIFE build registers the <scell-onboarding> custom element automatically and exposes window.ScellOnboarding.

ES module (CDN)

html
<script type="module">
  import '@scell/onboarding-widget';
</script>

Or from a CDN supporting ESM:

html
<script type="module" src="https://cdn.scell.io/widget/v1/onboarding.esm.js"></script>

3. Quick Start

Minimum viable integration — 5 lines of HTML:

html
<!DOCTYPE html>
<html>
<body>
  <script src="https://cdn.scell.io/widget/v1/onboarding.js"></script>
  <scell-onboarding publishable-key="pk_test_YOUR_KEY"></scell-onboarding>
</body>
</html>

The widget renders at full width and 600 px tall by default. The environment (sandbox or production) is determined automatically from the publishable key prefix (pk_test_* → sandbox, pk_live_* → production). Labels are in French and the light theme is used.


4. Configuration

Configuration is passed as HTML attributes on the <scell-onboarding> element. All attributes are reactive: changing them after mount updates the widget.

Attributes reference

AttributeTypeDefaultRequiredDescription
publishable-keystringYesYour Scell publishable key (pk_live_... or pk_test_...). The widget will not initialize without this. The key prefix determines the environment automatically: pk_test_* → sandbox, pk_live_* → production.
theme"light" | "dark" | "auto""light"NoColor scheme. "auto" respects the OS-level prefers-color-scheme media query.
locale"fr" | "en""fr"NoInterface language. All labels, error messages, and placeholders are translated.
external-idstringNoYour internal customer or user identifier. Forwarded as-is in the onboarding:completed event payload and in the callback URL query string as external_id. Useful to correlate the Scell session with your own records.
callback-urlstringNoIf provided, the browser is redirected to this URL 2 seconds after a successful submission. The authorization code is appended as ?code=AUTH_CODE. If external-id is also set, &external_id=... is also appended.
widthstring"100%"NoCSS width of the host element (any valid CSS length: 480px, 100%, 50vw…).
heightstring"600px"NoCSS height of the host element. The inner content scrolls if it overflows. 650px is recommended for the documents step.

HTML example with all options

html
<scell-onboarding
  publishable-key="pk_live_a1b2c3d4e5f6"
  theme="auto"
  locale="en"
  external-id="customer_7890"
  callback-url="https://app.example.com/onboarding/callback"
  width="520px"
  height="680px"
></scell-onboarding>

Changing attributes dynamically

javascript
const widget = document.querySelector('scell-onboarding');

// Switch to dark theme at runtime
widget.setAttribute('theme', 'dark');

// Switch locale
widget.setAttribute('locale', 'en');

Note: Only theme triggers a full re-render when changed after mount. Other attribute changes update the internal config but do not re-render the current step.


5. Onboarding Flow — The 3 Steps

The widget walks users through a linear, guided flow. KYB, identity verification, and account creation are handled entirely by SuperPDP via an OAuth2 popup — the widget does not collect company or representative data directly.

Progress is tracked by the API session. If the user closes the popup without completing, they can click the connect button again to reopen it.

Step 1 — Connect (connect)

Purpose: Initiate the SuperPDP OAuth2 authorization.

What the user sees:

  • A brief explanation of what will happen (SuperPDP will verify their company and identity)
  • A "Connecter avec SuperPDP" / "Connect with SuperPDP" button

What happens behind the scenes:

  • On button click: POST /api/v1/onboarding/superpdp/authorize with the session ID
  • The API returns a { authorize_url } pointing to the SuperPDP authorization endpoint
  • The widget calls window.open(authorize_url, 'superpdp_auth', 'width=600,height=700') to open the popup
  • The widget transitions to step 2 and begins listening for a postMessage from the popup origin

Layout:

+----------------------------------+
|  [scell.io logo]                 |
|  [1] Connect > [2] > [3]         |
|                                  |
|  Vérifiez votre entreprise       |
|  SuperPDP va guider la           |
|  vérification de votre identité  |
|  et de votre entreprise.         |
|                                  |
|  [ Connecter avec SuperPDP ]     |
+----------------------------------+

Step 2 — Redirect (redirect)

Purpose: Wait for the user to complete the SuperPDP flow in the popup.

What the user sees:

  • A loading/waiting screen with a spinner or animated indicator
  • A message indicating they should complete the process in the popup
  • A "Réouvrir la fenêtre" / "Reopen window" link in case the popup was closed or blocked

What happens behind the scenes:

  • The widget waits for a postMessage from the SuperPDP callback page (served by the Scell backend)
  • When the backend POST /api/v1/onboarding/superpdp/callback receives the OAuth2 code, it exchanges it for tokens, creates the tenant, and posts { type: 'superpdp:success', authorizationCode } to the opener window
  • On receiving the message, the widget closes the popup and transitions to step 3
  • On { type: 'superpdp:error' }: inline error with a retry button

Popup blocked: If window.open returns null, the widget shows an inline message asking the user to allow popups and provides a direct link to the authorization URL.

Layout:

+----------------------------------+
|  [1 done] > [2 ACTIVE] > [3]    |
|                                  |
|  Vérification en cours...        |
|  Complétez le processus dans     |
|  la fenêtre SuperPDP.            |
|                                  |
|  [spinner]                       |
|                                  |
|  Fenêtre fermée ?                |
|  [ Réouvrir la fenêtre ]         |
+----------------------------------+

Step 3 — Complete (complete)

Purpose: Confirm success and deliver the authorization code.

What the user sees:

  • A success message confirming the account is ready
  • No form — all data was collected by SuperPDP

What happens behind the scenes:

  • The onboarding:completed event fires with the authorizationCode
  • If callback-url is set, the browser is redirected after 2 seconds

Layout:

+----------------------------------+
|  [1 done] > [2 done] > [3 DONE] |
|                                  |
|  Compte créé avec succès !       |
|  Votre espace Scell est prêt.    |
|                                  |
|  [checkmark icon]                |
|                                  |
|  Vous allez être redirigé...     |
+----------------------------------+

6. Events and Callbacks

The widget communicates with the host page exclusively through CustomEvents. All events bubble up the DOM and are composed (they cross Shadow DOM boundaries).

Listening to events

javascript
const widget = document.querySelector('scell-onboarding');

widget.addEventListener('onboarding:completed', (event) => {
  console.log(event.detail.authorizationCode);
});

Or at the document level (events bubble):

javascript
document.addEventListener('onboarding:completed', (event) => {
  // event.target is the <scell-onboarding> element
  exchangeCodeWithBackend(event.detail.authorizationCode);
});

Event reference

onboarding:started

Fired once, immediately after the API session is created.

typescript
interface OnboardingStartedPayload {
  sessionId: string; // UUID of the created onboarding session
}

Use case: Store the session ID in case you need to check status via your own backend.

javascript
widget.addEventListener('onboarding:started', (e) => {
  localStorage.setItem('scell_session_id', e.detail.sessionId);
});

onboarding:step

Fired every time the widget transitions to a different step.

typescript
interface OnboardingStepPayload {
  step: 'connect' | 'redirect' | 'complete';
  progress: number; // 0, 50, or 100
}

progress values: connect → 0, redirect → 50, complete → 100.

javascript
widget.addEventListener('onboarding:step', (e) => {
  updateProgressBar(e.detail.progress);
});

onboarding:popup-opened

Fired when the SuperPDP authorization popup is successfully opened.

typescript
interface PopupOpenedPayload {
  authorizeUrl: string; // The SuperPDP URL loaded in the popup
}

Use case: analytics, logging, or displaying a "popup is open" notice to the user.

javascript
widget.addEventListener('onboarding:popup-opened', (e) => {
  console.log('SuperPDP popup opened:', e.detail.authorizeUrl);
});

onboarding:completed

Fired after the SuperPDP callback is processed and the tenant is created. This is the primary event your integration must handle.

typescript
interface OnboardingCompletedPayload {
  authorizationCode: string; // One-time code — exchange immediately
  externalId?: string;       // Reflects the external-id attribute if set
}

Critical: The authorizationCode is short-lived. Exchange it for tenant credentials via POST /v1/onboarding/exchange as soon as possible. Do not expose it in browser storage.

javascript
widget.addEventListener('onboarding:completed', async (e) => {
  const { authorizationCode, externalId } = e.detail;

  // Send to YOUR backend, which calls the Scell Partner API
  const response = await fetch('/api/onboarding/exchange', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ code: authorizationCode, customerId: externalId })
  });

  const { tenantApiKey } = await response.json();
  // Store tenantApiKey for this customer
});

onboarding:error

Fired on initialization errors (failed session creation) or any unrecoverable API error.

typescript
interface OnboardingErrorPayload {
  code: string;    // Machine-readable error code, e.g. "INIT_ERROR", "INVALID_PARTNER_KEY"
  message: string; // Human-readable description
}
javascript
widget.addEventListener('onboarding:error', (e) => {
  console.error(`[Scell] ${e.detail.code}: ${e.detail.message}`);
  showFallbackUI();
});

TypeScript types for event handlers

If you use TypeScript, you can augment HTMLElementEventMap for type-safe listeners:

typescript
import type {
  OnboardingStartedPayload,
  OnboardingStepPayload,
  PopupOpenedPayload,
  OnboardingCompletedPayload,
  OnboardingErrorPayload
} from '@scell/onboarding-widget';

// Declare the custom element type
declare global {
  interface HTMLElementTagNameMap {
    'scell-onboarding': import('@scell/onboarding-widget').ScellOnboarding;
  }

  interface HTMLElementEventMap {
    'onboarding:started': CustomEvent<OnboardingStartedPayload>;
    'onboarding:step': CustomEvent<OnboardingStepPayload>;
    'onboarding:popup-opened': CustomEvent<PopupOpenedPayload>;
    'onboarding:completed': CustomEvent<OnboardingCompletedPayload>;
    'onboarding:error': CustomEvent<OnboardingErrorPayload>;
  }
}

7. Customization

7.1 Theme — light, dark, auto

Set via the theme attribute:

html
<scell-onboarding theme="dark" publishable-key="pk_test_..."></scell-onboarding>

"auto" uses window.matchMedia('(prefers-color-scheme: dark)') at render time. It does not reactively update if the OS preference changes while the widget is mounted — use attributeChangedCallback or re-set the attribute from an matchMedia listener if dynamic switching is needed.

7.2 CSS Custom Properties

The widget exposes design tokens as CSS custom properties on :host. Because Shadow DOM isolates internal styles, these properties must be set on the host element from outside:

css
scell-onboarding {
  --scell-primary: #6366f1;        /* Main accent color (buttons, links, active step) */
  --scell-primary-hover: #818cf8;  /* Hover state */
  --scell-primary-active: #4f46e5; /* Active/pressed state */
  --scell-background: #ffffff;     /* Widget background */
  --scell-surface: #f8fafc;        /* Card and input backgrounds */
  --scell-surface-hover: #f1f5f9;  /* Surface hover */
  --scell-border: #e2e8f0;         /* Borders */
  --scell-border-focus: #818cf8;   /* Input focus ring color */
  --scell-text: #0f172a;           /* Primary text */
  --scell-text-secondary: #475569; /* Secondary text */
  --scell-text-muted: #94a3b8;     /* Muted/placeholder text */
  --scell-success: #22c55e;        /* Success color */
  --scell-success-bg: #f0fdf4;     /* Success background */
  --scell-error: #ef4444;          /* Error color */
  --scell-error-bg: #fef2f2;       /* Error background */
  --scell-warning: #f59e0b;        /* Warning color */
  --scell-warning-bg: #fffbeb;     /* Warning background */
  --scell-info: #3b82f6;           /* Info color */
  --scell-info-bg: #eff6ff;        /* Info background */
}

All properties are optional. Unset properties fall back to the theme defaults (light or dark).

Example — brand color override:

css
scell-onboarding {
  --scell-primary: #7c3aed;
  --scell-primary-hover: #8b5cf6;
  --scell-primary-active: #6d28d9;
  --scell-border-focus: #8b5cf6;
}

7.3 Host element sizing

The widget fills its host element. Control sizing with standard CSS:

css
scell-onboarding {
  width: 100%;
  max-width: 560px;
  height: 680px;
  border-radius: 12px;
  border: 1px solid #e2e8f0;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
  overflow: hidden;
  display: block; /* Set automatically by the widget */
}

Or use the HTML attributes:

html
<scell-onboarding width="560px" height="680px" ...></scell-onboarding>

7.4 Internationalization (FR / EN)

Two locales are built-in. Set the locale attribute:

html
<scell-onboarding locale="en" ...></scell-onboarding>

The locale affects all text in the widget: step labels, button labels, error messages, placeholders, and consent checkbox links.

To switch locale dynamically (e.g., based on a user preference toggle):

javascript
const widget = document.querySelector('scell-onboarding');
widget.setAttribute('locale', 'en');
// The widget re-renders the current step in the new locale

Note: Locale switching re-renders the current step, which resets any form input the user had typed in that step. Do not switch locales mid-flow unless you provide explicit user control (a language selector).


8. Integration — React

Import the side-effect import to register the custom element, then use it as JSX.

typescript
// onboarding-widget-registration.ts (import once at app root)
import '@scell/onboarding-widget';
tsx
// OnboardingPage.tsx
import { useEffect, useRef } from 'react';
import type {
  OnboardingCompletedPayload,
  OnboardingErrorPayload,
  OnboardingStepPayload,
  PopupOpenedPayload
} from '@scell/onboarding-widget';

interface OnboardingWidgetProps {
  publishableKey: string;
  externalId: string;
  onComplete: (code: string) => void;
  onError: (code: string, message: string) => void;
}

export function OnboardingWidget({
  publishableKey,
  externalId,
  onComplete,
  onError
}: OnboardingWidgetProps) {
  const widgetRef = useRef<HTMLElement>(null);

  useEffect(() => {
    const el = widgetRef.current;
    if (!el) return;

    const handleComplete = (e: Event) => {
      const { authorizationCode } = (e as CustomEvent<OnboardingCompletedPayload>).detail;
      onComplete(authorizationCode);
    };

    const handleError = (e: Event) => {
      const { code, message } = (e as CustomEvent<OnboardingErrorPayload>).detail;
      onError(code, message);
    };

    const handleStep = (e: Event) => {
      const { step, progress } = (e as CustomEvent<OnboardingStepPayload>).detail;
      console.log(`Step: ${step}, Progress: ${progress}%`);
    };

    const handlePopupOpened = (e: Event) => {
      const { authorizeUrl } = (e as CustomEvent<PopupOpenedPayload>).detail;
      console.log('SuperPDP popup opened:', authorizeUrl);
    };

    el.addEventListener('onboarding:completed', handleComplete);
    el.addEventListener('onboarding:error', handleError);
    el.addEventListener('onboarding:step', handleStep);
    el.addEventListener('onboarding:popup-opened', handlePopupOpened);

    return () => {
      el.removeEventListener('onboarding:completed', handleComplete);
      el.removeEventListener('onboarding:error', handleError);
      el.removeEventListener('onboarding:step', handleStep);
      el.removeEventListener('onboarding:popup-opened', handlePopupOpened);
    };
  }, [onComplete, onError]);

  return (
    <scell-onboarding
      ref={widgetRef}
      publishable-key={publishableKey}
      external-id={externalId}
      theme="light"
      locale="fr"
      height="680px"
      style={{ borderRadius: '12px', border: '1px solid #e2e8f0' }}
    />
  );
}

TypeScript JSX type declarations — add to a .d.ts file in your project:

typescript
// custom-elements.d.ts
import type { ScellOnboarding } from '@scell/onboarding-widget';

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'scell-onboarding': React.DetailedHTMLProps<
        React.HTMLAttributes<ScellOnboarding> & {
          'publishable-key'?: string;
          'theme'?: 'light' | 'dark' | 'auto';
          'locale'?: 'fr' | 'en';
          'external-id'?: string;
          'callback-url'?: string;
          'width'?: string;
          'height'?: string;
        },
        ScellOnboarding
      >;
    }
  }
}

9. Integration — Vue 3

vue
<!-- OnboardingWidget.vue -->
<template>
  <scell-onboarding
    ref="widgetRef"
    :publishable-key="publishableKey"
    :external-id="externalId"
    theme="auto"
    locale="fr"
    height="680px"
  />
</template>

<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';
import '@scell/onboarding-widget';
import type {
  OnboardingCompletedPayload,
  OnboardingErrorPayload
} from '@scell/onboarding-widget';

const props = defineProps<{
  publishableKey: string;
  externalId: string;
}>();

const emit = defineEmits<{
  complete: [code: string, externalId: string | undefined];
  error: [code: string, message: string];
}>();

const widgetRef = ref<HTMLElement | null>(null);

function handleComplete(e: Event) {
  const detail = (e as CustomEvent<OnboardingCompletedPayload>).detail;
  emit('complete', detail.authorizationCode, detail.externalId);
}

function handleError(e: Event) {
  const detail = (e as CustomEvent<OnboardingErrorPayload>).detail;
  emit('error', detail.code, detail.message);
}

onMounted(() => {
  const el = widgetRef.value;
  if (!el) return;
  el.addEventListener('onboarding:completed', handleComplete);
  el.addEventListener('onboarding:error', handleError);
});

onBeforeUnmount(() => {
  const el = widgetRef.value;
  if (!el) return;
  el.removeEventListener('onboarding:completed', handleComplete);
  el.removeEventListener('onboarding:error', handleError);
});
</script>

Vite config — tell Vue to treat scell-* tags as custom elements:

typescript
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag.startsWith('scell-')
        }
      }
    })
  ]
});

10. Integration — Vanilla JS

Complete self-contained example:

html
<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Onboarding Client</title>
  <style>
    body {
      font-family: system-ui, sans-serif;
      display: flex;
      justify-content: center;
      align-items: flex-start;
      min-height: 100vh;
      padding: 40px 20px;
      background: #f5f5f5;
      box-sizing: border-box;
    }

    .widget-wrapper {
      width: 100%;
      max-width: 540px;
    }

    scell-onboarding {
      border: 1px solid #d9d9d9;
      border-radius: 8px;
      overflow: hidden;

      /* Brand color overrides */
      --scell-primary: #7c3aed;
      --scell-primary-hover: #8b5cf6;
      --scell-primary-active: #6d28d9;
      --scell-border-focus: #8b5cf6;
    }

    #status-message {
      margin-top: 16px;
      padding: 12px;
      border-radius: 6px;
      font-size: 14px;
      display: none;
    }

    #status-message.success {
      background: #f0fdf4;
      color: #166534;
      border: 1px solid #22c55e;
    }

    #status-message.error {
      background: #fef2f2;
      color: #991b1b;
      border: 1px solid #ef4444;
    }
  </style>
</head>
<body>
  <div class="widget-wrapper">
    <scell-onboarding
      id="onboarding"
      publishable-key="pk_test_YOUR_KEY_HERE"
      theme="light"
      locale="fr"
      external-id="customer_42"
      height="680px"
    ></scell-onboarding>

    <div id="status-message"></div>
  </div>

  <script src="https://cdn.scell.io/widget/v1/onboarding.js"></script>

  <script>
    const widget = document.getElementById('onboarding');
    const statusMessage = document.getElementById('status-message');

    function showStatus(message, type) {
      statusMessage.textContent = message;
      statusMessage.className = type;
      statusMessage.style.display = 'block';
    }

    // Session start — useful for logging or storing the session ID
    widget.addEventListener('onboarding:started', (e) => {
      console.log('Session started:', e.detail.sessionId);
    });

    // Step navigation — update a custom progress indicator if needed
    widget.addEventListener('onboarding:step', (e) => {
      console.log(`Step: ${e.detail.step} (${e.detail.progress}% complete)`);
    });

    // SuperPDP popup opened — optional tracking
    widget.addEventListener('onboarding:popup-opened', (e) => {
      console.log('SuperPDP popup opened:', e.detail.authorizeUrl);
    });

    // Onboarding completed — critical handler
    widget.addEventListener('onboarding:completed', async (e) => {
      const { authorizationCode, externalId } = e.detail;

      try {
        // Exchange the code via YOUR backend
        // NEVER call the Scell Partner API directly from the browser
        const response = await fetch('/api/onboarding/finalize', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            code: authorizationCode,
            customerId: externalId
          })
        });

        if (!response.ok) throw new Error('Exchange failed');

        showStatus('Inscription reussie ! Votre compte Scell est actif.', 'success');

      } catch (err) {
        showStatus('Erreur lors de la finalisation. Contactez le support.', 'error');
        console.error('Exchange error:', err);
      }
    });

    // Errors — display a user-facing message
    widget.addEventListener('onboarding:error', (e) => {
      const { code, message } = e.detail;
      console.error(`Widget error [${code}]: ${message}`);

      if (code === 'INVALID_PUBLISHABLE_KEY') {
        showStatus('Configuration incorrecte. Contactez votre prestataire.', 'error');
      } else {
        showStatus('Une erreur est survenue. Veuillez recharger la page.', 'error');
      }
    });
  </script>
</body>
</html>

11. Public API Reference

The <scell-onboarding> element exposes the following JavaScript methods in addition to the standard HTMLElement API.

reset(): void

Resets the widget to its initial state: clears the current step back to connect, closes any open SuperPDP popup, destroys the current API session reference, and re-renders.

Use this to allow the same widget instance to start a fresh onboarding flow.

javascript
const widget = document.querySelector('scell-onboarding');
widget.reset();

Note: A new API session is created automatically when the widget re-renders after reset().


getCurrentStep(): OnboardingStep

Returns the current step key as a string.

Return type: 'connect' | 'redirect' | 'complete'

javascript
const currentStep = widget.getCurrentStep();
// e.g. "documents"

getProgress(): number

Returns the current progress as a number between 0 and 100.

StepValue
connect0
redirect50
complete100
javascript
const progress = widget.getProgress();
// e.g. 50

Inherited attributes (HTMLElement)

Since <scell-onboarding> is a standard custom element, you can also use:

  • widget.setAttribute('theme', 'dark') — changes theme and triggers re-render
  • widget.getAttribute('locale') — reads current locale
  • widget.removeAttribute('callback-url') — removes optional config
  • All standard DOM methods (addEventListener, dispatchEvent, etc.)

12. Troubleshooting

"Publishable key is required" shown in the widget

The publishable-key attribute is missing or empty. Ensure you set it before the element connects to the DOM.

html
<!-- Wrong -->
<scell-onboarding></scell-onboarding>

<!-- Correct -->
<scell-onboarding publishable-key="pk_test_abc123"></scell-onboarding>

onboarding:error fires with code INIT_ERROR immediately

The session creation call to POST /v1/onboarding/sessions failed. Common causes:

  • Invalid publishable-key: Verify the key in your Scell Partner dashboard. Sandbox keys start with pk_test_, production keys with pk_live_. The environment is determined automatically from the key prefix.
  • CORS: If you are running on localhost and your browser blocks the request, check that your Scell partner account has localhost whitelisted in allowed origins (sandbox only).
  • Network issue: Check the browser Network panel for the failing request and inspect the response body.

The widget is invisible / has zero height

The custom element is a replaced element with display: block set by the widget. However, if its parent has height: 0 or overflow: hidden, the widget collapses. Also verify that the height attribute (or CSS height) is set:

html
<scell-onboarding height="650px" ...></scell-onboarding>

Shadow DOM styles conflict with my page

They should not — the Shadow DOM isolates the widget's styles. If you see styling issues, check that you are not using !important rules targeting scell-onboarding * from your page CSS (which cannot pierce Shadow DOM) or that you are not using a CSS reset that targets the host element itself (scell-onboarding { ... }). Only host-level rules apply; inner element rules do not.


CSS custom properties are not applied

Custom properties set on scell-onboarding from the host page do pierce Shadow DOM — this is the intended mechanism. Verify:

  1. The property name is spelled exactly as documented (prefix --scell-).
  2. The rule targets scell-onboarding (the element itself), not a class inside it.
css
/* Correct — targets the host element */
scell-onboarding {
  --scell-primary: #7c3aed;
}

/* Wrong — .scell-btn is inside Shadow DOM and cannot be targeted from outside */
scell-onboarding .scell-btn {
  background: #7c3aed;
}

onboarding:completed fires but exchanging the code fails

The authorizationCode is a one-time, short-lived token. Do not:

  • Store it in localStorage or sessionStorage before exchanging it
  • Delay the exchange by more than a few seconds
  • Call the exchange endpoint more than once with the same code

If the exchange fails, the user must restart the onboarding. There is no way to re-issue the authorization code.


The widget does not resume after page refresh

Session resumption is supported by the API: if the same publishable-key and external-id combination previously created a session that is still active (not expired), the API returns the saved step.

If the widget always starts from step 1 (connect) after a refresh, check that:

  1. The external-id attribute is set consistently (same value as the previous load).
  2. The session has not expired (sessions expire after a fixed TTL; check your Scell Partner dashboard for the configured value).

Note: because the SuperPDP flow runs in a popup, the main page can be refreshed without losing the session — the popup closes independently.


The SuperPDP popup is blocked by the browser

Browsers block popups that are not triggered directly by a user gesture. The widget opens the popup inside the button click handler, which satisfies most browsers. If the popup is still blocked:

  • Do not wrap the <scell-onboarding> element inside an <iframe> — cross-origin iframes cannot open popups.
  • Verify that no browser extension (ad blocker, privacy extension) is suppressing the popup.
  • As a fallback, the widget shows a direct link to the authorization URL when window.open returns null.

The widget stays on the redirect step indefinitely

The widget is waiting for a postMessage from the SuperPDP callback page. Common causes:

  • Callback URL mismatch: The redirect URI registered in your SuperPDP developer account must exactly match the Scell backend callback URL (https://api.scell.io/api/v1/onboarding/superpdp/callback). A mismatch causes SuperPDP to show an error instead of redirecting.
  • Popup closed before completing: The user closed the popup manually. The "Réouvrir la fenêtre" link reopens it.
  • postMessage origin blocked by CSP: Check your page's Content-Security-Policy — it must not block frame-ancestors or restrict message sources from api.scell.io.
  • Network error during callback exchange: Check POST /api/v1/onboarding/superpdp/callback in the Network panel of the popup window's DevTools (open DevTools before the popup appears, or use --remote-debugging-port).

TypeScript: "Property 'scell-onboarding' does not exist on type 'JSX.IntrinsicElements'"

Add the JSX type declarations described in section 8 to a .d.ts file included in your tsconfig.json. Alternatively, use React.createElement without JSX type checking for a quick workaround:

tsx
// Quick workaround (not recommended long-term)
const ScellWidget = 'scell-onboarding' as unknown as React.ComponentType<Record<string, string>>;
<ScellWidget publishable-key="pk_test_..." />

Sandbox testing

In sandbox mode (using a pk_test_* key), the SuperPDP popup connects to the SuperPDP sandbox environment. Use the SuperPDP sandbox test credentials to simulate a successful KYB flow without real company data.

The authorization code returned at the end is a sandbox code. Exchange it with POST /v1/onboarding/exchange against https://api.scell.io/api/v1 — this will create a sandbox tenant (DB rdb_sandbox).

SuperPDP sandbox test credentials are managed in your SuperPDP developer account. Contact Scell support if you need a pre-configured sandbox environment for integration testing.

Documentation Scell.io