Embed onboarding
The embedded onboarding component opens the frameOS KYC and compliance flow as an inline modal — your users never leave your page. Before the browser can open the modal, your backend needs to create the account, provision capabilities, and mint a session.
All three steps use your secret key and must run server-side. Your secret key never touches the browser.
1. Create an account with capabilities
Capabilities determine which steps appear in the onboarding flow. Provision only what your integration needs — adding capabilities later is fine, but every capability adds steps to the flow.
curl --request POST \
--url https://api.framepayments.com/v1/accounts \
--header 'Authorization: Bearer YOUR_SECRET_KEY' \
--header 'Content-Type: application/json' \
--data '{
"type": "individual",
"profile": {
"individual": {
"email": "user@example.com"
}
},
"capabilities": ["kyc", "phone_verification", "card_receive"]
}'
See the Capabilities concept for the full list of available capabilities.
Prefill as much profile information as you know when creating the account. The onboarding flow renders an empty field for everything that isn't prefilled — even known values like email — so prefilling reduces the field count the user sees and lifts completion rates.
2. Mint an onboarding session
A session scopes the publishable key in the browser to one onboarding flow against this account. Mint a fresh session per flow — don't cache them.
curl --request POST \
--url https://api.framepayments.com/v1/onboarding_sessions \
--header 'Authorization: Bearer YOUR_SECRET_KEY' \
--header 'Content-Type: application/json' \
--data '{
"account_id": "<ACCOUNT_ID>"
}'
The response includes a client_secret. That's the only field your frontend needs.
3. Pass client_secret to the frontend
Return the client_secret from your own endpoint and call onboarding.open() in the browser.
const onboarding = await Frame.createOnboarding("<PUBLISHABLE_KEY>");
const { client_secret } = await fetch("/api/onboarding/start", {
method: "POST",
}).then((r) => r.json());
onboarding.open({ clientSecret: client_secret });
The component handles every step of the flow from there. Listen for frame:complete (or set onSuccess) on the element to know when the user finishes; see the embedded onboarding reference for the full event surface.
Reconcile from webhooks, not events
The browser fires onSuccess / frame:complete the moment the user finishes the last step in the modal. Webhooks confirm what Frame actually accepted — they may arrive slightly later and they're the authoritative signal for downstream state. Treat the browser event as a UI cue ("show the success screen"); treat the webhook as the source of truth ("mark this account onboarded in our database").