Skip to main content
Developer Documentation

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.

Install in 30 seconds
index.html
html
<!-- 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

Sandbox Mode Available: Use your own test publishable key 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

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

Step 2: Add the component

html
<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

javascript
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.

AttributeTypeRequiredDescription
publishable-keystringYesYour publishable key (pk_live_xxx or pk_test_xxx)
external-idstringNoYour internal identifier for this sub-tenant (link with your system)
callback-urlstringNoFull-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"NoColor theme. Default: "light"
locale"fr" | "en"NoWidget language. Default: "fr"
widthstringNoWidget width. Default: "100%"
heightstringNoWidget height. Default: "600px"
white-labelboolean (attribut HTML)NoWhite-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.

EventPayloadDescription
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.

  1. 1The widget calls POST /onboarding/sessions with your publishable key (pk_*) to create a session linked to your parent tenant.
  2. 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.
  3. 3The widget opens a popup to that URL. The end user authenticates with SUPER PDP and completes KYB (SIREN, VAT, legal representative).
  4. 4SUPER PDP redirects to POST /onboarding/superpdp/callback with a code and the state.
  5. 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).
  6. 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.

CriterionSandboxProduction
Publishable key prefixpk_test_*pk_live_*
Secret key prefixsk_test_*sk_live_*
DatabaseIsolated. Test data, no fiscal value.Real data, retained 10 years (ISCA self-certification).
SUPER PDPSUPER 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 KYBSimplified KYB — any test SIREN works. No real verification.Full KYB (SIREN, VAT, legal representative verified by SUPER PDP).
Sub-tenantsCreated in the sandbox database, never visible from your live keys.Created in the production database, immediately operational for invoicing.
PricingFree, 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.

ScellOnboarding.tsx
tsx
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.

URLPurpose
/api/v1/widget/oauth-callbackCallback 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/callbackLegacy POST endpoint kept for widget v1 integrations (back-compat). Same response contract as widget v2.
/api/v1/me/superpdp/callbackSelf-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.

Security: Always verify the webhook signature using the secret key from your dashboard. The signature is included in the 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.

scell:onboarding:complete
json
// 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.

POST /api/v1/invoices
javascript
// 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

EventDescription
sub_tenant.onboardedA 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.transmittedAn invoice issued for this sub-tenant was successfully transmitted to SUPER PDP. Includes superpdp_id and timestamp.
invoice.acceptedThe invoice was accepted by the recipient (Factur-X lifecycle).
invoice.rejectedThe invoice was rejected by the recipient or SUPER PDP. Payload includes rejection_reason.
invoice.incoming.receivedAn 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.

Your cookie preferences

We use cookies to improve your experience. Essential cookies are always active. Cookie policy.