Capture website leads via the API
How it works
Section titled “How it works”Your website visitor has no BUZ login, so you don’t run OAuth on every form submission. Instead you connect once, then reuse a refresh token:
- Connect once — a user in your BUZ organisation signs in and approves your integration. You receive a refresh token.
- Store the refresh token securely on your server (never in the browser).
- On each enquiry — exchange the refresh token for a short-lived access token and
POSTthe lead to the Sales API.
This is the same OAuth authorization-code + PKCE flow used to connect Xero or Google — the token BUZ issues is a real user token that carries your organisation and permissions, so nothing is hard-coded and no allow-listing is required.
1. Register your integration
Section titled “1. Register your integration”Registering an OAuth client is not yet self-service, so contact BUZ support to register your integration. You’ll provide:
- Redirect URI — where BUZ returns the user after they approve, e.g.
https://yoursite.com/api/buz/callback - The scopes you need (see Scopes below)
You’ll receive a Client ID and Client secret. Keep the secret server-side.
| Endpoint | URL |
|---|---|
| Authorize | https://login.buzmanager.com/connect/authorize |
| Token | https://login.buzmanager.com/connect/token |
| List the user’s organisations | https://api.buzmanager.com/identity/My/Permissions |
| Create a lead | https://api.buzmanager.com/sales/leads |
2. Connect once (authorization code + PKCE)
Section titled “2. Connect once (authorization code + PKCE)”Add a one-time “Connect to BUZ” page. When clicked, generate a PKCE verifier and redirect to the authorize endpoint:
// GET /connect/startimport { randomBytes, createHash } from "node:crypto";const b64url = (b) => b.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"");
const verifier = b64url(randomBytes(32));const challenge = b64url(createHash("sha256").update(verifier).digest());const state = b64url(randomBytes(16));// Persist { state, verifier } in an HttpOnly, Secure, SameSite=Lax cookie (10 min).
const url = "https://login.buzmanager.com/connect/authorize?" + new URLSearchParams({ response_type: "code", client_id: YOUR_CLIENT_ID, redirect_uri: "https://yoursite.com/api/buz/callback", scope: "openid profile offline_access buz.sales.read buz.sales.write buz.inventory.read", state, code_challenge: challenge, code_challenge_method: "S256", prompt: "login", // force an explicit sign-in as your integration user});// 302 redirect the browser to `url`.The user signs in, sees a consent screen listing the permissions you requested, and approves.
BUZ redirects back to your callback with ?code=…&state=…. Exchange the code for tokens:
// GET /api/buz/callback (verify `state` against the cookie first)const res = await fetch("https://login.buzmanager.com/connect/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: "https://yoursite.com/api/buz/callback", code_verifier: verifier, // from the cookie client_id: YOUR_CLIENT_ID, client_secret: YOUR_CLIENT_SECRET, }),});const { refresh_token, access_token } = await res.json();// Store refresh_token securely (e.g. AWS SSM SecureString, a secrets manager, or an encrypted row).3. Find the organisation
Section titled “3. Find the organisation”The token carries every organisation the user belongs to — you don’t hard-code an org id. Ask BUZ
which organisations this user has, and use that organisation’s id as the Selected-Organization
header on subsequent calls:
const orgs = await fetch("https://api.buzmanager.com/identity/My/Permissions", { headers: { Authorization: `Bearer ${access_token}` },}).then((r) => r.json());
// orgs.userOrganizationPreferences[].organization.{ pkId, name }// One org → use it. Several → let the person connecting pick one, then store the chosen pkId.Connect as a user who belongs to one organisation (a dedicated “Website integration” user is ideal) and this is fully automatic.
4. Create a lead on each enquiry
Section titled “4. Create a lead on each enquiry”Mint an access token from the stored refresh token, then POST the lead. Set the
Selected-Organization header to the org id from step 3.
// Mint a fresh access token (cache it until it expires)const { access_token } = await fetch("https://login.buzmanager.com/connect/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: STORED_REFRESH_TOKEN, client_id: YOUR_CLIENT_ID, client_secret: YOUR_CLIENT_SECRET, }),}).then((r) => r.json());
// Create the leadawait fetch("https://api.buzmanager.com/sales/leads", { method: "POST", headers: { "Content-Type": "application/json", "Selected-Organization": ORG_ID, Authorization: `Bearer ${access_token}`, }, body: JSON.stringify({ CompanyName: "Jane's Interiors", FirstName: "Jane", LastName: "Doe", Email: "jane@example.com", Phone: "0400 000 000", Mobile: "0400 000 000", Description: "Website enquiry", Notes: "Interested in roller blinds for a new home.", // Optional tentative appointment (leave StartTime as "00:00" for an all-day/top-of-day slot) AppointmentDate: "2026-08-01", StartTime: "09:00", // Products of interest — inventory group PkIds (see GET /inventory/Groups), qty 1 each Items: [{ InventoryGroupPkId: "…", Qty: 1 }], // Your marketing questions, keyed by option code MarketingAnswers: { Source: "Website", TYPE: "Retailer" }, }),});Keeping the connection alive
Section titled “Keeping the connection alive”- Access tokens are short-lived (~1 hour) — cache and re-mint from the refresh token as needed.
- The refresh token is long-lived but will eventually lapse after a long idle period. If a refresh fails, send the person back through Connect once to re-establish it.
- Store the refresh token encrypted and server-side only.
Scopes reference
Section titled “Scopes reference”Request the least privilege you need:
| Scope | Grants |
|---|---|
openid profile |
Sign-in / identity |
offline_access |
A refresh token (required for connect-once) |
buz.sales.read |
Read lead marketing options |
buz.sales.write |
Create leads |
buz.inventory.read |
Read inventory groups (products of interest) |
Reference implementation
Section titled “Reference implementation”Our own site, buzsoftware.com.au, is built exactly this way: connect once, refresh token stored server-side, org captured from the connected user, and each demo request posted as a lead — with the form fields driven live by the org’s marketing questions and products.