@vield-dev/embed
v0.5.3
Published
Vield embeddable widgets + SSO handoff SDK (hybrid integration).
Downloads
35
Readme
@vield-dev/embed
Vield hybrid integration SDK — drop Vield's crypto-backed lending flows into your own site as embeddable widgets, with an SSO handoff so your signed-in user lands straight into the flow.
- Framework-agnostic (vanilla TS) — works with React, Vue, plain HTML.
- Renders as a sandboxed iframe and communicates via
postMessage(the Stripe/Plaid model). - Auto-resizes and emits lifecycle events (
ready,resize,navigate,completed,error). - The SSO handoff token travels in the URL fragment, so it is never logged or sent as a query.
Hybrid has three parts: this SDK (your site), the Vield-hosted embed app (the iframe content), and the
POST /v2/embed/sessionsendpoint (your backend mints the handoff token with your API key). This package is the first part.
Install
npm install @vield-dev/embedOr via a <script> tag (exposes window.Vield):
<script src="https://cdn.jsdelivr.net/npm/@vield-dev/embed"></script>Quick start
1. Mint a session token on your backend (never in the browser) with your Vield API key:
POST https://api.vield.io/v2/embed/sessions
Authorization: Bearer <your-partner-api-key>
{ "flow": "apply", "customerId": "<vield-customer-id>", "customerRef": "your-user-id" }
→ { "sessionToken": "es_…", "expiresAt": "…" }customerId is required — every session is bound to one of your Vield
customers (create/list them via the Customers API), and the id must belong to
your organisation. customerRef is an optional opaque tag of your own.
2. Mount the flow on your frontend:
import { create } from '@vield-dev/embed';
const vield = create({
environment: 'production', // or 'uat'
publishableKey: 'vp_live_…', // public — identifies your org for branding
});
const embed = vield.mount('#vield-container', {
flow: 'apply', // 'apply' | 'loan' | 'onboarding'
sessionToken, // from step 1
locale: 'en-AU',
appearance: {
// quick token branding (also used for flash-free first paint)
variables: { primaryColor: '#1f2a44', accentColor: '#3b5bdb', radius: '10px', fontFamily: 'Inter, sans-serif' },
// arbitrary CSS custom properties
cssVariables: { '--vield-shadow': '0 1px 3px rgba(0,0,0,.1)' },
// a partner-hosted stylesheet (HTTPS)
stylesheetUrl: 'https://cdn.partner.com/vield-embed.css',
// and/or raw CSS for full control of the embedded UI
customCss: `.vld-button { text-transform: uppercase; letter-spacing: .04em; }`,
},
onReady: () => console.log('embed ready'),
onCompleted: (result) => console.log('done', result),
onError: (err) => console.error(err.code, err.message),
});
// restyle live at any time:
embed.updateAppearance({ variables: { primaryColor: '#0a7' } });
// later, when navigating away:
embed.destroy();Script-tag equivalent: const vield = Vield.create({ … }).
Styling — fully customizable
The embed renders in a cross-origin iframe, so the partner page can't reach into it directly. Instead
you pass an appearance object; the SDK sends it to the embed (small variables also ride in the URL
for flash-free first paint) and the embed applies it:
variables— high-level design tokens (colours, radius, font) → CSS variables on the embed root.cssVariables— arbitrary CSS custom properties on the embed root.stylesheetUrl— an HTTPS partner-hosted stylesheet loaded into the embed. The host must be allow-listed for your org (same place you configureframe-ancestorsorigins) — the embed's CSP fails closed for unlisted style hosts, so an unlistedstylesheetUrlis silently ignored rather than loaded. Contact Vield or use the partner console to add a style host.customCss— raw CSS injected into the embed document (complete control of the UI). Always works (no allow-listing needed) since it's inline, not a cross-origin fetch.
Change styling at runtime with instance.updateAppearance(appearance).
Theme presets
Start from a named base and override only what you need:
appearance: { theme: 'dark', variables: { accentColor: '#6ea8fe' } }theme: light (default) · dark · minimal. Precedence: preset → variables → cssVariables → customCss.
Stable theming surface
For reliable customCss, the embed guarantees these hooks (they won't change without a major version):
- CSS variables (set on the embed
:root):--v-primary,--v-accent,--v-bg,--v-fg,--v-muted,--v-danger,--v-line,--v-radius,--v-font,--v-font-size. - Class names:
.vld(root),.vld-brand,.vld-steps/.vld-step,.vld-label,.vld-amount,.vld-chip,.vld-lvr,.vld-field/.vld-input,.vld-upload,.vld-summary,.vld-btn,.vld-link,.vld-done/.vld-check.
Debugging
Pass debug: true to create() to log the message bridge + lifecycle to the console.
Use with any framework
The core is framework-agnostic — it mounts into any DOM element and talks to the embed over
postMessage, so it works everywhere. A first-class React binding ships at
@vield-dev/embed/react; every other framework uses the core directly (mount on init, destroy()
on teardown).
React — @vield-dev/embed/react
import { VieldEmbed } from '@vield-dev/embed/react';
<VieldEmbed
environment="production"
publishableKey="vp_live_…"
flow="apply"
sessionToken={token}
appearance={{ variables: { primaryColor: '#1f2a44' }, customCss: '.vld-button{text-transform:uppercase}' }}
onCompleted={(r) => console.log(r)}
/>Vue 3
<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue';
import { create } from '@vield-dev/embed';
const el = ref(); let embed;
onMounted(() => { embed = create({ publishableKey: 'vp_live_…' }).mount(el.value, { flow: 'apply', sessionToken }); });
onBeforeUnmount(() => embed?.destroy());
</script>
<template><div ref="el" /></template>Angular
import { create } from '@vield-dev/embed';
@Component({ selector: 'vield-embed', template: '<div #host></div>' })
export class VieldEmbedComponent implements AfterViewInit, OnDestroy {
@ViewChild('host') host!: ElementRef<HTMLElement>;
private embed?: ReturnType<ReturnType<typeof create>['mount']>;
ngAfterViewInit() { this.embed = create({ publishableKey: 'vp_live_…' }).mount(this.host.nativeElement, { flow: 'apply', sessionToken }); }
ngOnDestroy() { this.embed?.destroy(); }
}Svelte
<script>
import { onMount, onDestroy } from 'svelte';
import { create } from '@vield-dev/embed';
let el, embed;
onMount(() => { embed = create({ publishableKey: 'vp_live_…' }).mount(el, { flow: 'apply', sessionToken }); });
onDestroy(() => embed?.destroy());
</script>
<div bind:this={el}></div>Plain HTML / Next.js / Nuxt
Script tag → window.Vield.create({ … }). In Next.js/Nuxt, call create() in a client-only hook
('use client' effect / onMounted) since it touches the DOM. Same shape as the snippets above.
API
create(config)→VieldClientconfig.environment?: 'uat' | 'production'(defaultproduction)config.publishableKey: string(required)config.embedOrigin?: string(self-host / testing)
client.mount(target, options)→VieldEmbedInstancetarget: CSS selector orHTMLElementoptions.flow,options.sessionToken(required),theme,locale,params,height, and theonReady/onEvent/onResize/onCompleted/onErrorcallbacks- Locale note:
localeis threaded through end-to-end today, but the hosted embed app only rendersen-AUright now — other locale values are accepted and forwarded but currently have no effect on the rendered copy. This will change as more locales ship; the option exists now so integrations don't need to change shape later.
instance.post(type, payload?)— send a message into the embed (e.g. live theme update)instance.destroy()— remove the iframe and listeners (idempotent)
Security notes
- Mint
sessionTokenserver-side only; it is short-lived and org-scoped. - The SDK only accepts
postMessageevents from the Vield embed origin and from its own iframe, and every message is additionally bound to a per-instance random nonce, so a spoofed or cross-embed message is rejected even if the origin check alone were somehow bypassed. - The
publishableKeyis safe in the browser — it cannot create loans or read customer data. - The embed iframe is sandboxed with
allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox.allow-scripts+allow-same-origintogether are usually flagged by security scanners as a sandbox-escape pattern — that warning assumes the framed content is untrusted third-party content. Here the framed content is Vield's own first-party widget (same security boundary as this SDK), so the combination is intentional defense-in-depth against the host page, not a mitigation against the widget itself. What the sandbox deliberately does not grant is anyallow-top-navigation*flag, so the embed can never navigate the parent page regardless of what runs inside it. - Never grants
allow-top-navigation*— the embed cannot navigate or redirect the host page.
Develop
npm install
npm run build # ESM + CJS + IIFE (window.Vield) + type declarations → dist/
npm run typecheck