Gamma Duo Form & Embed Guide
Put the Gamma Duo loan application form on any website with one <script> tag,
or link applicants to our hosted form. Either way, applications flow through
the same validated pipeline as the Partner API and land with our
operations team (and, on final submission, the lender).
- Hosted form:
$BASE_URL/(link to it directly, or iframe it). - Embeddable widget:
$BASE_URL/embed/gamma-duo-form.iife.js, a standalone bundle (React included, styles self-injected). No dependencies, no build step, no API key.
1. Quick start
Script tag (any website)
<div id="loan-form"></div>
<script src="$BASE_URL/embed/gamma-duo-form.iife.js"></script>
<script>
GammaDuoForm.mount("#loan-form", { product: "car" });
</script>
Declarative (auto-mounts on load)
<div data-gamma-duo-form='{"product":"personal"}'></div>
<script src="$BASE_URL/embed/gamma-duo-form.iife.js"></script>
React app
import { GammaDuoForm } from "gamma-duo-form";
<GammaDuoForm product="car" submitUrl="$BASE_URL/api/v1/applications" />
When embedding on a domain other than ours, always pass submitUrl (see §4);
the widget derives its other endpoints from it.
2. Options
| Option | Default | Notes |
|---|---|---|
product |
"car" |
"car" or "personal" |
direction |
"sectioned" |
layout style: "sectioned" · "flow" · "focus" |
accent |
"#E8476A" |
brand accent colour (any CSS colour) |
font |
"Plus Jakarta Sans / Inter" |
also "Sora / Inter", "Manrope" |
mascot |
"milestones" |
"off" · "milestones" · "always" |
height |
"auto" |
"auto" (flows in page), "fill", "640px", or a px number |
persist |
true |
saves progress in the browser so applicants can resume |
initialValues |
(none) | object of form fields to prefill (same vocabulary as §3's hand-off links) |
logoUrl / mascotUrl |
(none) | white-label the brand assets |
submitUrl |
/api/v1/applications |
required for cross-origin embeds: full URL of our intake endpoint |
attribution |
auto | captured from the host page URL (?refsource=, utm_*); pass an object to override or false to disable |
onStepComplete(stepId, form) / onChange(form) |
(none) | host page hooks (analytics etc.) |
The layout responds to the container width, not just the viewport, so the form renders correctly inside a narrow column.
Attribution: add ?refsource=your_id to any page that hosts the widget
(or to links pointing at our hosted form) and every application from that
visit is tagged with your referral source; first touch persists across
return visits. Note this is claimed attribution; if you need verified,
per-application attribution and server-side control, use the
Partner API instead.
3. Prefilled hand-off links (traffic partners)
If you drive applicants to our hosted form, you can hand over everything you already know about them so they start at Step 1 with those answers filled in.
Have an API key? Prefer the server-side hand-off:
POST /api/v1/partner-drafts(Partner API guide, §3.1) stores what you send and returns a short resume link for the applicant. Nothing travels in the URL, and the lead exists on our side even if they never open the link. The?prefill=links below need no credentials, but the data rides in the link itself.
Build the link:
$BASE_URL/?refsource=YOUR_PARTNER_ID&prefill=<encoded JSON>
refsource(required for attribution) - your agreed partner identifier. Every application from the visit is tagged with it, and the first touch persists even if the applicant returns later with a clean URL.prefill- a JSON object of form fields, encoded either way:- URI-encoded JSON:
encodeURIComponent(JSON.stringify(data)) - base64url of the UTF-8 JSON (shorter links, no
%soup)
- URI-encoded JSON:
product(optional) -car(default) orpersonal.
// Building a hand-off link (browser or Node 18+)
const data = {
firstName: "Jane", lastName: "Smith", email: "[email protected]",
phone: "0411 223 344", dateOfBirth: "1990-04-15",
address: "12 Smith Street, Richmond VIC 3121",
loanAmount: 25000, depositAmount: 5000,
};
const b64 = btoa(String.fromCharCode(...new TextEncoder().encode(JSON.stringify(data))))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
const url = `$BASE_URL/?refsource=YOUR_PARTNER_ID&prefill=${b64}`;
Fields you can prefill
Any real form field is accepted; unknown keys are silently ignored. The useful set:
| Field | Type / values |
|---|---|
firstName / lastName |
string |
dateOfBirth |
YYYY-MM-DD |
email |
string |
phone |
Australian mobile, 04######## (spaces ok) |
address |
single line, e.g. "12 Smith Street, Richmond VIC 3121" |
previousAddress |
single line (if under 3 years at current) |
yearsAtAddress |
number |
residentialStatus |
OWN_MORTGAGE · OWN_NO_MORTGAGE · RENTING · BOARDING · WITH_PARENTS |
maritalStatus |
SINGLE · MARRIED · DEFACTO · DIVORCED · WIDOWED |
residencyStatus |
AU_CITIZEN · AU_PERMANENT_RESIDENT · AU_TAX_RESIDENT · NON_AU_RESIDENT |
numberOfDependents |
number |
loanAmount / depositAmount |
number (AUD) |
loanTerm |
years, 1-7 |
paymentFrequency |
weekly · fortnightly · monthly |
loanPurpose |
personal loans: DEBT_CONSOLIDATION · HOME_IMPROVEMENT · TRAVEL · WEDDING · MEDICAL · MAJOR_PURCHASE · EDUCATION · OTHER |
employmentType |
PAYG · CASUAL · CONTRACT · SELF_EMPLOYED · RETIRED · UNEMPLOYED |
employmentTime |
FULL_TIME · PART_TIME · CASUAL (basis, when employed) |
employerCompanyName / occupationName |
string |
employerContactNumber |
string, min 8 digits |
grossSalary |
number |
incomePeriod |
WEEKLY · FORTNIGHTLY · MONTHLY · ANNUALLY |
vehicleFound |
boolean - with rego (string) if known |
Behaviour and privacy rules
- The applicant lands on Step 1 and walks the full journey; every prefilled value is visible, editable and re-validated as they go.
- Consents and declarations can never be preset by a link (credit check, privacy, contact opt-in, hardship declaration) - the applicant answers those personally. Internal keys are ignored too.
- The
prefillparameter is stripped from the URL immediately after load, so personal details don't sit in the address bar, history, bookmarks or analytics page-view URLs. - If the applicant has been here before, their own saved answers beat the link's values - a hand-off never overwrites what someone typed.
- Always send applicants over HTTPS, and only include details you actually hold - less is better in a URL.
- This is convenience prefill with claimed attribution. If you need authenticated, server-attributed submission instead, use the Partner API.
4. Cross-origin embedding
The widget talks to three endpoints, all derived from submitUrl:
| Purpose | Derived endpoint |
|---|---|
| Final submission | submitUrl (e.g. $BASE_URL/api/v1/applications) |
| Bank-statements session (Step 6) | …/api/v1/bank-session |
| Server-side draft saves / resume | …/api/v1/drafts |
CORS is open by default and can be locked to specific origins on our side; tell us the domains you'll embed on and we'll allowlist them.
These endpoints carry per-visitor rate limits sized well above real applicant
behaviour. Because the widget calls them from each applicant's own browser,
embedding sites never share a limit; a rate-limited response (429) simply
retries after the Retry-After interval.
5. The form's endpoints (reference)
These are the public endpoints behind the widget. They serve the anonymous form, so they require no API key. Server-to-server integrators should use the Partner API instead, which authenticates you and attributes applications to your organisation.
POST /api/v1/applications (final submission)
{
"applicationId": "3f9d2c80-…", // optional UUID; reused across the whole journey
"product": "car", // or "personal"
"form": { /* the widget's form state */ },
"attribution": { "refsource": "…", "utm_source": "…" } // optional
}
- 200
{ applicationId, reference: "GGD-######", status: "submitted", … }: the application is recorded on our platform. Depending on platform configuration it is either forwarded to the lender immediately (the response then includes the lender acknowledgement undernimo) or captured for review, in which case the response also carries"deferred": trueand our operations team completes the lodgement. - 422
{ error: "validation_failed", fields: [{ field, message }] }: same validation rules as the Partner API (see §6 there), including consents and, for working applicants, the employer contact number. - 424
nimo_rejected/nimo_auth_failed/nimo_unreachable: lender round-trip problems, same catalogue as the Partner API.
POST /api/v1/bank-session (Step 6, bank statements)
Called when the applicant reaches the income-verification step. Validates the
data captured so far (consents and employment are not yet required at this
point), registers the application with the lender, and returns
{ nimoReference, bankStatementsUrl }; bankStatementsUrl is what the widget
opens full-screen for illion bank-statement sharing. Bank credentials are
entered on illion's hosted page only; they never touch Gamma Duo or your site.
GET /api/v1/bank-session?id=<applicationId> returns { completed: boolean }
- true once illion has delivered the statements data back to us. The widget polls this while the statements screen is open and closes it automatically on completion; no integration work is needed on your side.
POST /api/v1/drafts · GET /api/v1/drafts?id=…&token=…
Progressive server-side draft capture. The first save (after Step 1) creates
the application and returns a resumeToken; the widget stores it and sends it
with every later save. GET returns the draft for resume; this is what our
"finish your application" email links use
($BASE_URL/?resume=<applicationId>&rt=<token>). Tokens are single-purpose,
hashed at rest and expiring; a link that has expired simply starts a fresh
application.
GET /api/v1/opt-out?id=…&token=…
One-click unsubscribe page linked from application emails. Stops service emails for that application without affecting the application itself.
GET /api/v1/health
No auth. Returns { ok: true, service: "gamma-duo-intake", nimo: "configured", … }.
nimo: "simulated" means the lender connection isn't configured in that
environment (submissions complete end-to-end but nothing is lodged). Add
?deep=1 to also verify database connectivity.
6. The applicant journey (what the widget does)
- Steps 1-5: details, vehicle/purpose, situation, ID, loan term. Progress is saved locally and (from Step 1) as a server-side draft, so applicants can leave and resume.
- Step 6: income verification via illion bank statements (full-screen, with a manual "I've finished" fallback).
- Steps 7-8: income, expenses, assets & liabilities. Working applicants must provide an employer contact number (the lender phone-verifies employment).
- Step 9: review, credit-check + privacy consents, submit. The applicant
sees a reference (
GGD-######) and a thank-you screen.
Abandoned applications receive a recovery email with a secure resume link (where the applicant opted in to contact), and are eventually purged under our data-retention policy.
7. Support
Contact your Gamma Duo integration representative for embed onboarding, CORS allowlisting, custom branding, or a walkthrough of the hosted form. For server-to-server submission, see the Partner API.