Your current login is a line item.
Ours is infrastructure.
Replace your app login in two days. Users still tap Continue with Google, Apple, or email.
In the background they get a self-custodial wallet and a portable on-chain identity. 1,000 users free, gas sponsored, no lock-in.
Integrate bonuz login.
Everything starts in the developer hub at app.bonuz.market/developers: create a project, configure it, copy the snippet, go live. This guide walks the same steps and covers what the hub does not: events, the token check on your server, CSP and WordPress.
- 1
Create a project
Start in the developer hub.
Sign in to app.bonuz.market, open Developers and pick the brand the app belongs to (no brand yet: create one first). Press New project and fill in three things: the App name people see on the sign-in screen, the Website it runs on (optional now), and the Platform: Website / web app, Mobile app, or both. You land on the project with its key,
bz_…. The key is public and only says which app is calling; the sites under Domains are what protect it. It is not a Web3Auth client id: every bonuz app shares bonuz's one Web3Auth project, so you never need one. - 2
Domains
Add every site you sign in from.
In the project, open Domains and add each origin: https, host and port, paths do not matter. That includes http://localhost:3000 with the exact port you run on. Local addresses are approved the moment you add them; bonuz enables a real domain by hand, usually within a day. Until a site is approved, sign-in on it shows a blocker message instead of the login flow. Mobile apps send no site and use the project key alone.
- 3
Sign-in, menu, wallet
Choose what people see.
Everything here is a setting, not code. The SDK fetches it on page load, so a change reaches your app without a redeploy.
Sign-in
Methods and their order, app name, logo, accent colour, theme, terms and privacy links, with a live preview.
Profile menu
What people see when they tap their avatar: built-in rows, your own links, the order.
Wallet
Wallet window on or off, send, fund, custom tokens, test networks, which chain it opens on.
Settings
Project name, platform, key, archive.
Sign-in methods are email (a one-time code), Google and Apple. Each one creates the same bonuz account and wallet.
- 4
Copy the snippet
The code the Overview tab hands you.
Open Overview in your project and copy the snippet for your stack: the key is already filled in there. Below is the same code with
bz_…in its place. Nothing to install for a website: the widget loads from bonuz.id and renders the button inside the div, in a shadow DOM, so your CSS and ours never collide. For React, LoginModal comes from @bonuz/sdk/auth/ui; pair it with ConnectButton, because a ConnectButton alone signs in with Google only. The @bonuz/sdk on npm today is an unrelated 0.1.0; 2.x ships inside the bonuz monorepo until it is published.WordPress (as a Custom HTML block), Webflow, Shopify, plain HTML: paste and done.
index.html<!-- bonuz login: paste where the sign-in button should appear (WordPress: in a Custom HTML block) --> <div data-bonuz-login></div> <script type="module" src="https://bonuz.id/sdk/v1/bonuz.js" data-project="bz_…"></script> <script> // window.bonuz exists as soon as the script starts. bonuz:ready says the widget is up, not that // anyone is signed in; the user arrives with bonuz:login (on sign-in, and when a saved session is restored) window.addEventListener("bonuz:ready", (e) => console.log("bonuz ready for", e.detail.project)); window.addEventListener("bonuz:login", (e) => console.log("signed in", e.detail.user)); // {id, handle, name, walletAddress} window.addEventListener("bonuz:logout", () => console.log("signed out")); // For your own API: a fresh token, refreshed if it was about to expire async function callMyApi() { const token = await window.bonuz.getFreshToken(); // waits for a restoring session; null when nobody is signed in return fetch("/api/me", { headers: { Authorization: "Bearer " + token } }); } // window.bonuz.getToken() is the same thing without waiting: the current token, or null </script>In the hub these come with your own project key filled in, plus an AI prompt tab you can paste into Claude Code, Cursor, Copilot or ChatGPT to set it all up for you.
- 5
window.bonuz and the three events
Listen for the user, then fetch a token.
Everything the widget knows reaches your page through window.bonuz and three events on window. bonuz:ready fires once when the widget is up (also when the config fetch fails, so your page can degrade). bonuz:login carries the user and fires on a fresh sign-in and when a returning visitor is restored. bonuz:logout fires when the session ends.
app.js// window.bonuz exists from the moment the script runs. login() and logout() // called before the widget is up are queued; the getters answer at once. window.addEventListener('bonuz:ready', (e) => { // The widget is up. e.detail.project is your project name, or null if the // config fetch failed. It does not mean anyone is signed in yet: a returning // visitor's session is still being restored. Take the user from bonuz:login. console.log('bonuz ready for', e.detail.project) }) // Fires on a fresh sign-in AND when a returning visitor's session is restored window.addEventListener('bonuz:login', async (e) => { showSignedIn(e.detail.user) const token = await window.bonuz.getFreshToken() // Promise<string | null> await fetch('/api/session', { method: 'POST', body: JSON.stringify({ token }) }) }) window.addEventListener('bonuz:logout', () => showSignedOut()) // Also on window.bonuz: // login() opens the sign-in (a full-page redirect, no popup) // logout() ends the session // getToken() sync: the current access token, or null // getUser() sync: { id, handle, name, walletAddress }, or null // (null for a returning visitor until the session is restored) - 6
Verify the token on your server
One GET to the bonuz API.
bonuz publishes no JWKS or public key, so do not decode or verify the token yourself. Ask the bonuz API who it belongs to: a 200 returns
{ id, walletAddress, handle, email }, a 401 means invalid, expired or logged out, a 404 means the account is gone. The answer confirms the person, not which app issued the sign-in.server.ts// Your server. The token arrived from the browser. const res = await fetch('https://admin.bonuz.xyz/api/v2/auth/me', { headers: { Authorization: `Bearer ${token}` }, }) if (res.status === 401) return unauthorized() // invalid, expired or logged out if (res.status === 404) return unauthorized() // the account is gone if (!res.ok) return tryAgainLater() const { id, walletAddress, handle, email } = await res.json() // That answer is who is signed in. Key your own records on id; handles can change. // It confirms the person, not which app issued the sign-in, so never grant admin rights from it alone. // Rate limited per server IP: remember a 200 for up to a minute (never past the token's exp). - 7
CSP and COOP
The minimum your host page must allow.
If your site sends a Content-Security-Policy, these are the floors. The RPC hosts are the ones the bonuz wallet reads balances from; add any others your own code calls. Email, Google and Apple sign-in redirect to the sign-in page and back, so they work with any Cross-Origin-Opener-Policy. Only the qID passkey window, which only bonuz apps offer, needs an opener: plain same-origin would break it there, so same-origin-allow-popups (or no COOP) is the safe default.
headersContent-Security-Policy: script-src 'self' https://bonuz.id https://*.web3auth.io 'unsafe-eval' 'wasm-unsafe-eval'; style-src 'self' https://bonuz.id; /* add 'unsafe-inline' only for very old browsers */ connect-src 'self' https://bonuz.id https://admin.bonuz.xyz https://*.web3auth.io wss://*.web3auth.io https://mainnet.base.org https://sepolia.base.org https://*.publicnode.com; /* the wallet's RPCs */ frame-src https://*.web3auth.io; img-src 'self' data: https:; /* sign-in logos */ Cross-Origin-Opener-Policy: same-origin-allow-popups /* safe default, or no COOP. Email, Google and Apple sign-in redirect and work with any COOP; only the qID passkey window (bonuz's own apps) needs an opener, which same-origin breaks */ - 8
WordPress
Paste it as a Custom HTML block.
Use one Custom HTML block containing the div and the script tag, or a header/footer plugin that pastes raw HTML. Enqueueing the script from functions.php does not work: WordPress prints neither the type="module" (classic enqueue) nor the data-project attribute (module enqueue) the widget needs.
Custom HTML block<!-- One Custom HTML block where the sign-in button should appear --> <div data-bonuz-login></div> <script type="module" src="https://bonuz.id/sdk/v1/bonuz.js" data-project="bz_…"></script> <!-- Enqueueing from the theme does not work: wp_enqueue_script() drops type="module", and wp_enqueue_script_module() prints no data-project, so the widget never starts. A header or footer plugin that pastes raw HTML works the same as the block above. --> - 9
Go live
Sign in once. Watch it fill.
The Overview checklist tracks it for you: project created, website added, bonuz enables your site, snippet pasted and one sign-in done. From the first sign-in, Users lists everyone who signed in to your app (their public bonuz profile, never their email) and Analytics shows monthly active users, sign-ins and how many arrived with a bonuz ID from another app. The first 1,000 monthly active users of each project are free, whichever way they sign in.
What your app receives.
The script tag exposes id, handle, name and wallet address. The React hook also exposes the sign-in email. Sign-in itself happens on the sign-in provider's page (Web3Auth, Google or Apple), so one-time codes and passwords never touch your page.
Script tag
id, handle, name, walletAddress
The user object carries no email. Those four fields are what the host page gets.
React hook
id, handle, name, walletAddress, email (optional)
The React path also exposes the sign-in email on user.email. Store it only if you need it.
Your server
id, walletAddress, handle, email
What GET /api/v2/auth/me returns for that token. Key your own data on the id; handles can change.
Already in production.
Real products built on the same SDK login.
What you ship now vs. what you ship with bonuz.
Same UX. Fundamentally different infrastructure underneath.
Old stack
Login as a line item
- ✕Email + password. Breach risk on you.
- ✕Magic link vendor lock-in.
- ✕No wallet. No on-chain identity. No reach beyond your app.
- ✕Loyalty is a database row, not a portable asset.
- ✕Seed phrases scare 95% of your users away.
bonuz SDK login
Login as infrastructure
- ✓Continue with Google, Apple, or email. Same UX your users already know.
- ✓Self-custodial wallet (MPC/TSS) provisioned silently in the background.
- ✓Portable bonuz ID: verified socials, attestations, and graph from day one.
- ✓Gas sponsored. Users never see a fee or a seed phrase.
- ✓No lock-in. Your users stay yours, but now they are also yours on-chain.
Three phases. Start shipping today.
Drop in the login first. Expand into engagement and the graph when you're ready.
Drop-in login
~ 2 daysReplace your existing auth with the bonuz SDK login button. Users keep the same flow. You get wallets, bonuz IDs, and session handling out of the box.
Activate engagement
~ 1 weekStart issuing DNFTs: loyalty cards, passes, vouchers, certificates. Gas sponsored. State machine handled by the protocol.
Plug into the graph
OngoingQuery the social graph, read cross-app attestations, and let users bring reputation in from every other bonuz integration.
Login vendor vs. bonuz SDK login
A side-by-side of what you pay for and what you get.
Live on-chain. Today.
Verifiable, audited, and already securing real users.
Identity registry
0x9220070245b67130977FdF32acA4acdF6aD163cC
Attestation protocol
0xe6461B0C86bF85023a5f359BAdF7012d2ef5ad7e
Engagement / DNFT core
0x7034625223C3fc8628214cd0504fdE7Cf5CEDA63
The obvious questions.
Answered before you ask.
Will this change how my users log in?
No. The button still says Continue with Google, Apple, or email. The wallet and bonuz ID are provisioned silently in the background.
Am I locking myself into bonuz?
No. The SDK is free, the protocol is open, and the user owns their keys. If you ever walk away, your users keep their wallet and identity.
Who pays gas?
You do, from a sponsored developer wallet. Your users never see a fee. Gas costs on our target chains are a fraction of a cent per action.
Is it secure?
MPC/TSS key management with MFA. Smart contracts audited by Hacken (10/10 security score). No single point of failure, no custody risk for you.
What does it cost?
The first 1,000 monthly active users of each app are free, whichever way they sign in. Above that, paid plans are coming, and sign-in is never cut off for anyone. Gas you choose to sponsor is separate.
Swap your login today.
Keep your UX. Gain a self-custodial wallet, a portable on-chain identity, and a full engagement layer.