Onboarding widget
Embed a fully compliant company verification and e-invoicing onboarding flow in your application with just one line of code. SIREN validation, VAT verification, and legal representative confirmation included.
<!-- 1. Charger le script (un seul include, n'importe ou dans <head> ou <body>) -->
<script src="https://cdn.scell.io/widget/v1/onboarding.js"></script>
<!-- 2. Poser le widget la ou il doit s'afficher -->
<scell-onboarding
publishable-key="pk_test_xxxxxxxx"
external-id="user_42"
callback-url="https://votre-app.com/onboarding/done"
></scell-onboarding>
<!-- 3. Ecouter l'evenement de fin pour recuperer sub_tenant + credentials -->
<script>
document.querySelector('scell-onboarding')
.addEventListener('onboarding:completed', (e) => {
const { subTenant, credentials } = e.detail;
console.log('Sub-tenant cree :', subTenant.id, subTenant.name);
console.log('A utiliser pour facturer :', credentials);
});
</script>Quick Start
pk_test_* (from your dashboard) to test the component in sandbox mode. Test data is not persisted.The Scell.io onboarding component handles the entire company verification flow: SIREN/SIRET lookup, VAT number validation, address verification, and legal representative confirmation.
Step 1: Include the script
<script src="https://cdn.scell.io/widget/v1/onboarding.js"></script>Step 2: Add the component
<scell-onboarding
publishable-key="pk_live_xxxxxxxx"
external-id="user_42"
callback-url="https://votre-app.com/onboarding/done"
theme="auto"
locale="fr"
></scell-onboarding>Step 3: Handle events
const widget = document.querySelector('scell-onboarding');
widget.addEventListener('onboarding:completed', (event) => {
// event.detail respecte le type OnboardingCompletedPayload
const { subTenant, credentials, externalId } = event.detail;
// subTenant : { id, tenant_id, name, siret, vat_number, onboarding_status, is_active }
// onboarding_status : pending_superpdp | superpdp_redirected | superpdp_authorized
// | superpdp_pending_review | active | superpdp_failed
// credentials : { api_endpoint, parent_tenant_id, sub_tenant_id, external_id, superpdp_company_id }
// A partir d'ici votre backend peut emettre des factures via :
// POST {credentials.api_endpoint}/invoices
// X-API-Key: <votre sk_live_*> (cle parent — securisee, JAMAIS dans le navigateur)
// body: { "sub_tenant_id": subTenant.id, ... } (scope sur le sub-tenant)
});
widget.addEventListener('onboarding:error', (event) => {
console.error(event.detail.code, event.detail.message);
});Live Demo
This demo uses a test publishable key. Create your account to get your own pk_test_* key and test the widget with your data.
Test the Scell.io Onboarding component directly below. This widget allows your users to verify their company (SIREN, VAT, legal representative) and complete their registration.
This widget runs in sandbox mode. To integrate it, see the Quick Start guide above.
API Reference
Attributes
Configure the component behavior using HTML attributes.
| Attribute | Type | Required | Description |
|---|---|---|---|
publishable-key | string | Yes | Your publishable key (pk_live_xxx or pk_test_xxx) |
external-id | string | No | Your internal identifier for this sub-tenant (link with your system) |
callback-url | string | No | Full-page redirect URL after onboarding (used when widget is NOT inside a popup, e.g. embedded iframe without opener). Receives ?onboarding=success&sub_tenant_id=…&external_id=… |
theme | "light" | "dark" | "auto" | No | Color theme. Default: "light" |
locale | "fr" | "en" | No | Widget language. Default: "fr" |
width | string | No | Widget width. Default: "100%" |
height | string | No | Widget height. Default: "600px" |
white-label | boolean (attribut HTML) | No | White-label mode: hides the header with the Scell.io logo. The progress tracker and business content remain visible. Bare attribute (<scell-onboarding white-label>) is enough, or use "true" / "1" / "on" / "yes". Default: Scell.io header visible. |
White-label mode
The white-label attribute hides the header with the Scell.io logo. The progress tracker and all business content (SIRET form, SUPER PDP redirect, completion screen) stay visible. Useful when you embed the widget without any visible Scell.io mention.
Accepted values: bare attribute (<scell-onboarding white-label>), or "true" / "1" / "on" / "yes". Any other value (including "false" and a missing attribute) keeps the header visible.
<!-- Mode marque blanche (attribut nu, ou white-label="true") -->
<scell-onboarding
publishable-key="pk_live_xxxxxxxx"
external-id="user_42"
callback-url="https://votre-app.com/onboarding/done"
theme="light"
locale="fr"
white-label
></scell-onboarding>Events
Listen to component events to react to user actions and state changes.
| Event | Payload | Description |
|---|---|---|
onboarding:started | { sessionId: string } | An onboarding session has been created on Scell.io. No SUPER PDP call yet. |
onboarding:step | { step: "connect" | "redirect" | "complete", progress: number } | The user moved to a new step (0% → 50% → 100%). |
onboarding:completed | { subTenant: SubTenantSummary, credentials: OnboardingCredentials, externalId?: string } | The Scell.io sub-tenant is created and ready for invoicing. SUPER PDP OAuth 2.1 tokens are encrypted at rest and used automatically by the backend to transmit invoices. |
onboarding:error | { code: string, message: string } | An error occurred (popup closed, invalid CSRF state, SUPER PDP KYB failure, etc.). |
How it works
The <scell-onboarding> widget is the only official integration method. It drives an OAuth 2.1 + PKCE flow that creates both a Scell.io sub-tenant and the SUPER PDP registration in the same user session.
- 1The widget calls POST /onboarding/sessions with your publishable key (pk_*) to create a session linked to your parent tenant.
- 2The widget calls POST /onboarding/superpdp/authorize: the Scell.io backend generates PKCE (code_verifier/code_challenge S256), a CSRF state, and returns the SUPER PDP authorize URL.
- 3The widget opens a popup to that URL. The end user authenticates with SUPER PDP and completes KYB (SIREN, VAT, legal representative).
- 4SUPER PDP redirects to POST /onboarding/superpdp/callback with a code and the state.
- 5The Scell.io backend exchanges code+verifier for tokens (access + refresh), fetches the company info, and creates a sub-tenant linked to your parent tenant (with its own sk_/pk_ keys, invoice numbering, balance).
- 6The widget receives the scell:onboarding:complete event with the sub_tenant_id and initial credentials.
Why a single mode?
Since May 5th 2026, the 'Redirect Flow' and 'API Only' modes are removed. The widget covers every use case while guaranteeing OAuth 2.1 + PKCE compliance and parent/sub-tenant isolation. OAuth code exchange, refresh token rotation, and SUPER PDP KYB are handled by the Scell.io backend — you only embed one line of code.
Sandbox vs Production
Scell.io provides two fully isolated environments. The mode is automatically determined by the prefix of the publishable key you pass to the widget — no extra configuration needed.
| Criterion | Sandbox | Production |
|---|---|---|
| Publishable key prefix | pk_test_* | pk_live_* |
| Secret key prefix | sk_test_* | sk_live_* |
| Database | Isolated. Test data, no fiscal value. | Real data, retained 10 years (ISCA self-certification). |
| SUPER PDP | SUPER PDP sandbox account. Invoices are NOT transmitted to the real Peppol/PPF network. | SUPER PDP production account. Real transmission on the French electronic network (PPF). |
| SUPER PDP KYB | Simplified KYB — any test SIREN works. No real verification. | Full KYB (SIREN, VAT, legal representative verified by SUPER PDP). |
| Sub-tenants | Created in the sandbox database, never visible from your live keys. | Created in the production database, immediately operational for invoicing. |
| Pricing | Free, unlimited. | See Pricing page. |
How to switch
Just swap the value of the publishable-key attribute. The widget, the Scell.io backend, and the underlying SUPER PDP account all switch automatically. No code, API URL, or configuration changes required.
Example — switching from test to live
-<scell-onboarding publishable-key="pk_test_xxxxxxxx" />
+<scell-onboarding publishable-key="pk_live_xxxxxxxx" />Best practices
- Test in sandbox until the user journey is fully validated BEFORE switching to live.
- Store live keys in server-side environment variables — NEVER in the browser.
- pk_test_* keys are public by design (front-end OK). sk_test_*/sk_live_* keys are secret (back-end only).
- An invoice issued in sandbox has no legal or fiscal value.
Framework Examples
Copy-paste examples for popular frameworks.
import { useEffect, useRef } from 'react';// Declarer le custom element pour TypeScript (React 19)declare module 'react' { namespace JSX { interface IntrinsicElements { 'scell-onboarding': React.DetailedHTMLProps< React.HTMLAttributes<HTMLElement> & { 'publishable-key': string; 'external-id'?: string; 'callback-url'?: string; 'theme'?: 'light' | 'dark' | 'auto'; 'locale'?: 'fr' | 'en'; 'width'?: string; 'height'?: string; }, HTMLElement >; } }}interface OnboardingResult { subTenant: { id: string; tenant_id: string; name: string; siret: string | null; vat_number: string | null; onboarding_status: | 'pending_superpdp' | 'superpdp_redirected' | 'superpdp_authorized' | 'superpdp_pending_review' | 'active' | 'superpdp_failed'; is_active: boolean; }; credentials: { api_endpoint: string; parent_tenant_id: string; sub_tenant_id: string; external_id: string | null; superpdp_company_id: string | null; }; externalId?: string;}interface Props { publishableKey: string; externalId?: string; callbackUrl?: string; onComplete?: (result: OnboardingResult) => void; onError?: (err: { code: string; message: string }) => void;}export function ScellOnboarding({ publishableKey, externalId, callbackUrl, onComplete, onError }: Props) { const ref = useRef<HTMLElement>(null); useEffect(() => { // Le script CDN s'auto-enregistre comme custom element global, // donc on n'a a le charger qu'une seule fois par page. if (document.querySelector('script[data-scell-widget]')) return; const script = document.createElement('script'); script.src = 'https://cdn.scell.io/widget/v1/onboarding.js'; script.async = true; script.dataset.scellWidget = 'true'; document.head.appendChild(script); }, []); useEffect(() => { const el = ref.current; if (!el) return; const handleComplete = (e: Event) => onComplete?.((e as CustomEvent<OnboardingResult>).detail); const handleError = (e: Event) => onError?.((e as CustomEvent<{ code: string; message: string }>).detail); el.addEventListener('onboarding:completed', handleComplete); el.addEventListener('onboarding:error', handleError); return () => { el.removeEventListener('onboarding:completed', handleComplete); el.removeEventListener('onboarding:error', handleError); }; }, [onComplete, onError]); return ( <scell-onboarding ref={ref} publishable-key={publishableKey} external-id={externalId} callback-url={callbackUrl} theme="auto" locale="fr" /> );}OAuth 2.1 redirect URIs registered with SUPER PDP
Reference info: Scell.io declared 3 official redirect_uris on the SUPER PDP side. You DO NOT need to configure anything to embed the widget — these URIs are used internally by Scell flows.
| URL | Purpose |
|---|---|
/api/v1/widget/oauth-callback | Callback for the <scell-onboarding> widget embedded on your site. Creates the sub-tenant + returns sub_tenant + credentials to the widget via postMessage. |
/api/v1/onboarding/superpdp/callback | Legacy POST endpoint kept for widget v1 integrations (back-compat). Same response contract as widget v2. |
/api/v1/me/superpdp/callback | Self-service onboarding from the Scell.io dashboard (logged-in tenant admin). Not related to widget integration. |
Webhooks
Receive real-time notifications when onboarding events occur. Configure your webhook URL in the dashboard.
x-scell-signature header.Payload received in the widget (onboarding:completed event)
This payload is delivered both via window.opener.postMessage(...) (to the parent hosting the widget) and as event.detail on the onboarding:completed event of the <scell-onboarding> element.
// Payload envoye par window.opener.postMessage(...) ET recu via// l'event 'onboarding:completed' (event.detail) cote widget consumer.{ "type": "scell:onboarding:complete", "success": true, "sub_tenant": { "id": "01975f7c-...", "tenant_id": "01975f78-...", "name": "ACME SAS", "siret": "12345678901234", "vat_number": "FR12345678901", "onboarding_status": "active", "is_active": true }, "credentials": { "api_endpoint": "https://api.scell.io/api/v1", "parent_tenant_id": "01975f78-...", "sub_tenant_id": "01975f7c-...", "external_id": "user_42", "superpdp_company_id": "spp_company_99" }}Issue an invoice right after onboarding
As soon as onboarding:completed fires, the sub-tenant is operational: SUPER PDP OAuth 2.1 tokens encrypted at rest, KYB validated, isolated invoice numbering. Zero wait.
// Apres l'event onboarding:completed, votre backend peut emettre des// factures Factur-X au nom du sub-tenant immediatement. Les tokens// OAuth 2.1 SUPER PDP sont chiffres en base et utilises automatiquement// par Scell pour transmettre la facture chez SUPER PDP.//// IMPORTANT — Numerotation : ne jamais passer 'invoice_number' dans le// body. La numerotation est entierement geree par Scell.io. La facture// recoit un identifiant brouillon 'DRAFT-XXXXX' a la creation, puis un// numero definitif 'XXXXX-YYYYMM-NNNNN' a l'emission (sequence chrono-// logique sans rupture, conforme aux articles 242 nonies A et 289 du CGI).// Le numero attribue est retourne dans la reponse (champ 'invoice_number').// Modele d'auth : la cle sk_live_* appartient au TENANT parent. Pour// emettre au nom d'un sub-tenant, on passe son 'sub_tenant_id' dans le// body. La company emettrice est resolue automatiquement par Scell.io —// aucun 'company_id' a fournir.const res = await fetch('https://api.scell.io/api/v1/invoices', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.SCELL_PARENT_SECRET_KEY, // sk_live_* (parent — JAMAIS dans le navigateur) }, body: JSON.stringify({ sub_tenant_id: subTenant.id, // scope sur le sub-tenant issue_date: '2026-05-05', buyer_siret: '98765432100012', buyer_name: 'Customer SAS', lines: [ { description: 'Prestation', quantity: 1, unit_price_excl_tax: 1000, vat_rate: 20 } ], }),});// Reponse (extrait) :// {// "id": "inv_01975f80...",// "invoice_number": "T0001-202605-00042", <- attribue par Scell.io// "status": "issued",// "issue_date": "2026-05-05",// "total_excl_tax": 1000,// ...// }const { invoice_number } = await res.json();Available Webhook Events
| Event | Description |
|---|---|
sub_tenant.onboarded | A Scell.io sub-tenant was just created via the widget. The payload contains sub_tenant_id, external_id, siret, onboarding_status. Persist the mapping in your database and enable invoicing for this client. |
invoice.transmitted | An invoice issued for this sub-tenant was successfully transmitted to SUPER PDP. Includes superpdp_id and timestamp. |
invoice.accepted | The invoice was accepted by the recipient (Factur-X lifecycle). |
invoice.rejected | The invoice was rejected by the recipient or SUPER PDP. Payload includes rejection_reason. |
invoice.incoming.received | An incoming invoice for this sub-tenant just arrived via SUPER PDP. Scell.io resolves it automatically by superpdp_company_id and exposes it in /tenant/incoming-invoices. |
Ready to integrate?
Create your account and get your API keys in minutes. Start with 100 free credits.