Scell Onboarding Widget — Developer Documentation
Package: @scell/onboarding-widget v1.1.0 Custom element: <scell-onboarding> Build output: ES module + IIFE (UMD CJS also exported)
Table of Contents
- Overview
- Installation
- Quick Start
- Configuration
- Onboarding Flow — The 4 Steps
- Events and Callbacks
- Customization
- Integration — React
- Integration — Vue 3
- Integration — Vanilla JS
- Public API Reference
- 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 4-step KYB (Know Your Business) onboarding
- Verifies the company SIRET number against the French company registry (INSEE/Sirene)
- Optionally validates the EU intra-community VAT number (VIES)
- Collects and uploads KYB compliance documents (Kbis, ID card, proof of address, bank details)
- Captures legal representative information and consent
- Returns an
authorization_codeto 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 and receive a callback with the authorization code. The partner exchanges that code for API credentials using POST /v1/onboarding/exchange-code.
Technical design
- Implemented as a native Custom Element (
HTMLElementsubclass) - 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, headersX-Publishable-KeyandX-Session-Id - Emits standard
CustomEventinstances that bubble up through the DOM - Supports
light,dark, andauto(OS preference) themes via CSS custom properties exposed on:host - Fully responsive, tested down to 320 px viewport width
2. Installation
npm / pnpm / yarn
npm install @scell/onboarding-widget
# or
pnpm add @scell/onboarding-widget
# or
yarn add @scell/onboarding-widgetThe package ships pre-built in dist/. No build step required on the consumer side.
CDN (script tag — IIFE build)
<script src="https://cdn.scell.io/widget/v1/onboarding.js"></script>Or pin to a specific version:
<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)
<script type="module">
import '@scell/onboarding-widget';
</script>Or from a CDN supporting ESM:
<script type="module" src="https://cdn.scell.io/widget/v1/onboarding.esm.js"></script>3. Quick Start
Minimum viable integration — 5 lines of 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. It connects to the sandbox environment, shows labels in French, and uses the light theme.
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
| Attribute | Type | Default | Required | Description |
|---|---|---|---|---|
publishable-key | string | — | Yes | Your Scell publishable key (pk_live_... or pk_test_...). The widget will not initialize without this. |
environment | "sandbox" | "production" | "sandbox" | No | Determines the API base URL. Sandbox: https://api-sandbox.scell.io/v1. Production: https://api.scell.io/v1. |
theme | "light" | "dark" | "auto" | "light" | No | Color scheme. "auto" respects the OS-level prefers-color-scheme media query. |
locale | "fr" | "en" | "fr" | No | Interface language. All labels, error messages, and placeholders are translated. |
external-id | string | — | No | Your 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-url | string | — | No | If 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. |
width | string | "100%" | No | CSS width of the host element (any valid CSS length: 480px, 100%, 50vw…). |
height | string | "600px" | No | CSS height of the host element. The inner content scrolls if it overflows. 650px is recommended for the documents step. |
HTML example with all options
<scell-onboarding
publishable-key="pk_live_a1b2c3d4e5f6"
environment="production"
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
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 4 Steps
The widget walks users through a linear, guided flow. Navigation is sequential: each step must be completed before proceeding. Users can navigate back to previous steps.
Progress is tracked by the API: if the user refreshes or reopens the widget with the same session, the API will resume from the saved step.
Step 1 — SIRET (siret)
Purpose: Identify the company.
What the user does:
- Enters a 14-digit SIRET number
- The input auto-formats to
XXX XXX XXX XXXXXas the user types - Clicks "Vérifier" / "Verify"
What happens behind the scenes:
POST /v1/onboarding/verify-companywith{ siret }in the body- On success: the API returns company data (name, SIREN, legal form, address); the widget transitions to step 2
- On
SIRET_NOT_FOUND: inline error below the field - On network error: generic retry message
Validation:
- Exactly 14 digits required (non-digit characters are stripped before validation)
- Client-side check runs before the API call
Layout:
+----------------------------------+
| [scell.io logo] |
| [1] SIRET > [2] > [3] > [4] |
| |
| Identifiez votre entreprise |
| Entrez le numero SIRET... |
| |
| SIRET |
| [ ___ ___ ___ _____ ] |
| < error message if any > |
| |
| [ Verifier ] |
+----------------------------------+Step 2 — Verification (verify)
Purpose: Confirm company data and optionally validate the EU VAT number.
What the user sees:
- A read-only card displaying: raison sociale, SIRET (formatted), SIREN, legal form (forme juridique), and address retrieved from the registry
- An optional input for the EU intra-community VAT number (e.g.,
FR12345678901) - "Vérifier la TVA" button that calls
POST /v1/onboarding/verify-vat - "Passer cette étape" / "Skip this step" link (VAT is optional)
- Back / Continue buttons
What happens behind the scenes:
- VAT verification calls
POST /v1/onboarding/verify-vatwith{ vat_number } - On valid VAT: success alert inline, VAT stored in session
- Clicking "Continue" without verifying VAT is allowed
Layout:
+----------------------------------+
| [1 done] > [2 ACTIVE] > [3] > [4] |
| |
| Confirmez les informations |
| |
| +-- Informations de l'ent. ----+|
| | Raison sociale ACME SAS ||
| | SIRET 123 456... ||
| | SIREN 123 456 789||
| | Forme juridique SAS ||
| | Adresse 12 rue... ||
| +------------------------------+|
| |
| TVA intracommunautaire (opt.) |
| [ FR____________ ] [Verifier] |
| < success/error status > |
| |
| [ Retour ] [ Continuer ] |
+----------------------------------+Step 3 — Documents (documents)
Purpose: Collect KYB compliance documents.
Document types:
| Type key | Label (FR) | Required |
|---|---|---|
kbis | Extrait Kbis (moins de 3 mois) | Yes |
id_card | Pièce d'identité du représentant légal | Yes |
proof_of_address | Justificatif de domicile | No |
bank_details | RIB / IBAN | No |
File constraints:
- Accepted formats: PDF, JPEG, PNG
- Maximum size: 10 MB per file
What happens:
- Clicking the upload button triggers a hidden
<input type="file"> POST /v1/onboarding/documentswithFormDatacontainingtypeandfile- On success: the document appears in the list with a "En attente" (pending) badge
- Documents can be replaced at any time before proceeding
- Clicking "Continue" with missing required documents highlights the incomplete items in red and blocks navigation
Layout:
+----------------------------------+
| [1] > [2] > [3 ACTIVE] > [4] |
| |
| Documents justificatifs |
| |
| DOCUMENTS OBLIGATOIRES |
| +-- Extrait Kbis -----------+ |
| | kbis_acme.pdf [En att.] | |
| | [Remplacer] | |
| +---------------------------+ |
| +-- Piece d'identite -------+ |
| | [Choisir] | |
| +---------------------------+ |
| |
| DOCUMENTS OPTIONNELS |
| +-- Justificatif domicile --+ |
| ... |
| |
| [ Retour ] [ Continuer ] |
+----------------------------------+Step 4 — Complete (complete)
Purpose: Capture legal representative details and final consent.
Form fields:
| Field | Type | Required |
|---|---|---|
First name (firstName) | text | Yes |
Last name (lastName) | text | Yes |
Email (email) | Yes | |
Phone (phone) | tel | No |
Role / Position (role) | select | Yes |
| Accept Terms & Conditions | checkbox | Yes |
| Accept Privacy Policy | checkbox | Yes |
Role options: Dirigeant / CEO, Directeur financier / CFO, Directeur technique / CTO, Manager, Autre.
Pre-fill: If the API session already carries representative data, the form fields are pre-populated.
On submission:
POST /v1/onboarding/completewith session ID, representative object, and consent flags- On success: the form is replaced by a success message; the
onboarding:completedevent fires with theauthorizationCode; ifcallback-urlis set, redirection happens after 2 seconds
On error:
- The submit button reverts; an inline error alert displays the message from the API
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
const widget = document.querySelector('scell-onboarding');
widget.addEventListener('onboarding:completed', (event) => {
console.log(event.detail.authorizationCode);
});Or at the document level (events bubble):
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.
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.
widget.addEventListener('onboarding:started', (e) => {
localStorage.setItem('scell_session_id', e.detail.sessionId);
});onboarding:step
Fired every time the user navigates to a different step (including back navigation).
interface OnboardingStepPayload {
step: 'siret' | 'verify' | 'documents' | 'complete';
progress: number; // 0, 25, 50, or 75
}Note: progress is 75 when the user reaches the complete step. The value becomes implicitly 100 only when onboarding:completed fires.
widget.addEventListener('onboarding:step', (e) => {
updateProgressBar(e.detail.progress);
});onboarding:siret-verified
Fired when the SIRET validation succeeds and the company data is retrieved.
interface SiretVerifiedPayload {
companyName: string;
siret: string; // 14-digit string, no spaces
}widget.addEventListener('onboarding:siret-verified', (e) => {
console.log(`Company identified: ${e.detail.companyName}`);
});onboarding:documents-uploaded
Fired each time a document is successfully uploaded (not when the step is completed, but after each individual file upload).
interface DocumentsUploadedPayload {
count: number; // Total number of documents uploaded so far in this session
}onboarding:completed
Fired after a successful final submission. This is the primary event your integration must handle.
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-code as soon as possible. Do not expose it in browser storage.
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.
interface OnboardingErrorPayload {
code: string; // Machine-readable error code, e.g. "INIT_ERROR", "INVALID_PARTNER_KEY"
message: string; // Human-readable description
}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:
import type {
OnboardingStartedPayload,
OnboardingStepPayload,
SiretVerifiedPayload,
DocumentsUploadedPayload,
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:siret-verified': CustomEvent<SiretVerifiedPayload>;
'onboarding:documents-uploaded': CustomEvent<DocumentsUploadedPayload>;
'onboarding:completed': CustomEvent<OnboardingCompletedPayload>;
'onboarding:error': CustomEvent<OnboardingErrorPayload>;
}
}7. Customization
7.1 Theme — light, dark, auto
Set via the theme attribute:
<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:
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:
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:
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:
<scell-onboarding width="560px" height="680px" ...></scell-onboarding>7.4 Internationalization (FR / EN)
Two locales are built-in. Set the locale attribute:
<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):
const widget = document.querySelector('scell-onboarding');
widget.setAttribute('locale', 'en');
// The widget re-renders the current step in the new localeNote: 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.
// onboarding-widget-registration.ts (import once at app root)
import '@scell/onboarding-widget';// OnboardingPage.tsx
import { useEffect, useRef } from 'react';
import type {
OnboardingCompletedPayload,
OnboardingErrorPayload,
OnboardingStepPayload
} 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}%`);
};
el.addEventListener('onboarding:completed', handleComplete);
el.addEventListener('onboarding:error', handleError);
el.addEventListener('onboarding:step', handleStep);
return () => {
el.removeEventListener('onboarding:completed', handleComplete);
el.removeEventListener('onboarding:error', handleError);
el.removeEventListener('onboarding:step', handleStep);
};
}, [onComplete, onError]);
return (
<scell-onboarding
ref={widgetRef}
publishable-key={publishableKey}
external-id={externalId}
environment="production"
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:
// 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;
'environment'?: 'sandbox' | 'production';
'theme'?: 'light' | 'dark' | 'auto';
'locale'?: 'fr' | 'en';
'external-id'?: string;
'callback-url'?: string;
'width'?: string;
'height'?: string;
},
ScellOnboarding
>;
}
}
}9. Integration — Vue 3
<!-- OnboardingWidget.vue -->
<template>
<scell-onboarding
ref="widgetRef"
:publishable-key="publishableKey"
:external-id="externalId"
environment="production"
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:
// 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:
<!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"
environment="sandbox"
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)`);
});
// SIRET verified — you know which company is onboarding
widget.addEventListener('onboarding:siret-verified', (e) => {
console.log(`Company: ${e.detail.companyName} (SIRET: ${e.detail.siret})`);
});
// Document uploaded — optional tracking
widget.addEventListener('onboarding:documents-uploaded', (e) => {
console.log(`Documents uploaded so far: ${e.detail.count}`);
});
// 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 siret, discards any collected company data and documents, destroys the current API session reference, and re-renders.
Use this to allow the same widget instance to start a fresh onboarding flow.
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: 'siret' | 'verify' | 'documents' | 'complete'
const currentStep = widget.getCurrentStep();
// e.g. "documents"getProgress(): number
Returns the current progress as a number between 0 and 75.
| Step | Value |
|---|---|
siret | 0 |
verify | 25 |
documents | 50 |
complete | 75 |
Progress reaches 100 implicitly when onboarding:completed fires (no separate method).
const progress = widget.getProgress();
// e.g. 50Inherited attributes (HTMLElement)
Since <scell-onboarding> is a standard custom element, you can also use:
widget.setAttribute('theme', 'dark')— changes theme and triggers re-renderwidget.getAttribute('locale')— reads current localewidget.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.
<!-- 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 withpk_test_, production keys withpk_live_. - Wrong
environment: Using a production key withenvironment="sandbox"(or vice versa) causes authentication failure. - CORS: If you are running on
localhostand your browser blocks the request, check that your Scell partner account haslocalhostwhitelisted 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:
<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:
- The property name is spelled exactly as documented (prefix
--scell-). - The rule targets
scell-onboarding(the element itself), not a class inside it.
/* 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
localStorageorsessionStoragebefore 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 and company data.
If the widget always starts from step 1 after a refresh, check that:
- The
external-idattribute is set consistently (same value as the previous load). - The session has not expired (sessions expire after a fixed TTL; check your Scell Partner dashboard for the configured value).
The widget shows a perpetual spinner after SIRET entry
The SIRET verification call is pending or has silently failed. Check:
- Browser Network panel for
POST /v1/onboarding/verify-company - Whether the request is blocked by an ad blocker or browser extension (the Scell sandbox domain may be flagged)
- Whether the SIRET entered is a valid, active French SIRET (closed or dissolved companies may return
SIRET_NOT_FOUND)
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:
// 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 — bypassing real document verification
In sandbox mode (environment="sandbox"), the API accepts any file for document upload without real validation. You can upload dummy PDFs. The authorization code returned at the end is also a sandbox code — use it with POST /v1/onboarding/exchange-code against https://api-sandbox.scell.io/v1.
SIRET verification in sandbox mode is connected to a test dataset. Use the following test SIRETs:
| SIRET | Company | Notes |
|---|---|---|
12345678900012 | ACME TEST SAS | Valid, active |
99999999900001 | TEST CORP SARL | Valid, active, no VAT |
00000000000000 | — | Always returns SIRET_NOT_FOUND |