Embed widget (<script> + Web Components)
The embed widget is the framework-agnostic way to add Akin loyalty + auth to
any site — Vue, Angular, Svelte, plain HTML, or a server-rendered stack. It ships
as Web Components
over @akin-travel/sdk-core, so you integrate with a single
<script> tag and a few custom elements. No framework lock-in, no build step.
Using React? The React SDK (providers + hooks + headless components) is the better fit. Reach for the embed widget for everything else.
Quickstart
Add the script and drop in the elements:
Use a publishable key.
data-api-keysits in your page source, so it must be a publishable (browser-safe) key — read-only by construction. Never put a secret (server) key here. See publishable vs secret keys.
<script
src="https://cdn.jsdelivr.net/npm/@akin-travel/sdk-embed/dist/akin-embed.global.js"
data-partner-id="YOUR_PARTNER_ID"
data-api-key="YOUR_PUBLISHABLE_PARTNER_API_KEY"
data-environment="production"
data-firebase-api-key="…"
data-firebase-auth-domain="…"
data-firebase-project-id="…"
data-firebase-app-id="…"
data-verify-callback-url="https://your-site.example/loyalty"></script>
<akin-login></akin-login>
<akin-loyalty-card></akin-loyalty-card>
<akin-tier-progress
data-tiers='[{"displayName":"Silver","points":1000},{"displayName":"Gold","points":5000}]'></akin-tier-progress>That’s it. The script auto-initialises from its own data-* attributes,
registers the custom elements, and (if the page is the magic-link landing) completes
the pending sign-in from the URL token. A member signs in via <akin-login>, and
the loyalty widgets render their tier and points.
The elements
| Tag | What it renders |
|---|---|
<akin-login> | Magic-link email form, plus passkey and Google sign-in when available; a signed-in summary with sign-out once authenticated |
<akin-loyalty-card> | The member’s AKIN points, spendable balance, and tier badge |
<akin-tier-progress> | The current tier and a progress bar toward the next one |
<akin-tier-progress> needs the tier ladder to draw a bar — pass it as
data-tiers (JSON, sorted or unsorted):
<akin-tier-progress
data-tiers='[{"displayName":"Silver","points":1000},{"displayName":"Gold","points":5000},{"displayName":"Platinum","points":15000}]'></akin-tier-progress>Configuration (data-* attributes)
Set these on the <script> tag (auto-init) or pass the camelCase equivalents to
AkinEmbed.init({...}).
| Attribute | Required | Description |
|---|---|---|
data-partner-id | ✓ | Your partner UUID |
data-api-key | ✓ | Your partner API key (pk_…) |
data-environment | production (default), staging, or development | |
data-api-url | Override the GraphQL endpoint (defaults per environment) | |
data-verify-callback-url | Where magic-link / signup emails return the member | |
data-gip-tenant-id | GIP tenant id (multi-tenancy) | |
data-firebase-api-key | auth | Firebase/GIP web config |
data-firebase-auth-domain | auth | Firebase/GIP web config |
data-firebase-project-id | auth | Firebase/GIP web config |
data-firebase-app-id | auth | Firebase/GIP web config |
data-debug | Verbose console logging | |
data-auto-init | false to skip auto-init and call AkinEmbed.init(...) yourself |
The four data-firebase-* fields are required for sign-in (magic link, passkey,
Google all run through Firebase/GIP). Without them the widgets render but auth is
disabled.
Theming
CSS custom properties inherit through the Shadow DOM boundary, so the widgets
honour the same partner token contract as white-label automatically. Set the
namespaced --akin-* tokens, or expose the white-label tokens (--primary,
--background, --foreground, --secondary, --muted, --border, --radius)
— the widgets fall back to them, then to neutral defaults. OKLCH, hex, and rgb
values all pass through verbatim.
:root {
--akin-primary: oklch(0.62 0.19 28);
--akin-radius: 0.75rem;
--akin-font: 'Inter', system-ui, sans-serif;
}Per-widget override with data-theme (JSON):
<akin-loyalty-card data-theme='{"primary":"#0f766e","radius":"1rem"}'></akin-loyalty-card>Because everything renders inside an open Shadow DOM, host-page CSS can’t leak into the widgets and the widgets’ CSS can’t leak out.
Events
The elements emit composed CustomEvents that cross the Shadow DOM boundary:
document.querySelector('akin-login')
.addEventListener('akin:authenticated', (e) => {
console.log('signed in', e.detail.member);
});akin:magic-link-sent, akin:authenticated, akin:signed-out, akin:error.
Framework mount snippets
The elements are standard custom elements — they work in any framework. A couple
need a one-line hint so the framework doesn’t try to resolve <akin-*> as its own
component.
Vue
// vite.config.ts
import vue from '@vitejs/plugin-vue';
export default {
plugins: [vue({ template: { compilerOptions: {
isCustomElement: (tag) => tag.startsWith('akin-'),
} } })],
};<template>
<akin-login />
<akin-loyalty-card />
</template>Angular
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })
export class AppModule {}<akin-login></akin-login>
<akin-loyalty-card></akin-loyalty-card>Svelte
No configuration needed — use the tags directly:
<akin-login />
<akin-loyalty-card />npm install (bundler integration)
If you’d rather bundle the widgets with your app than load the CDN script:
npm install @akin-travel/sdk-embed @akin-travel/sdk-coreimport { createAkinEmbed } from '@akin-travel/sdk-embed';
createAkinEmbed({
partnerId: 'YOUR_PARTNER_ID',
apiKey: 'YOUR_PARTNER_API_KEY',
environment: 'production',
firebaseApiKey: '…',
firebaseAuthDomain: '…',
firebaseProjectId: '…',
firebaseAppId: '…',
verifyCallbackUrl: 'https://your-site.example/loyalty',
});createAkinEmbed returns the controller for programmatic use
(requestMagicLink, signInWithPasskey, signInWithGoogle, signOut,
subscribe), and the elements bind to it automatically.
Troubleshooting: Failed to fetch on staging
If a staging integration throws Failed to fetch / a CORS preflight error in the
browser console, your bundle predates the 2026-06 staging endpoint move. Staging
migrated off the raw Cloud Run URL onto the branded proxy
https://staging-api.akintravel.com/graphql; only the branded host passes the API’s
CORS allowlist. Two fixes:
-
Upgrade to the latest
@akin-travel/sdk-embed+@akin-travel/sdk-core(the corrected default ships insdk-core≥0.1.1), or -
Override immediately without upgrading — point the endpoint at the branded host:
<script … data-environment="staging" data-api-url="https://staging-api.akintravel.com/graphql"></script>or, via the programmatic API,
createAkinEmbed({ …, apiUrl: 'https://staging-api.akintravel.com/graphql' }).
Versioning & platform notes
The embed widget is the recommended path for non-React frameworks, including Vue — see Platform support. It follows the SDK versioning & release policy.