Contact OnBoarding: setup and integration flow
swirepay-contact-onboarding is a self-contained custom element (web component) that collects a
customer's contact details, address, and a linked bank account (for later transfers), and creates
the resulting contact/funding-source records via a PCI/PII-isolated iframe your page never touches
directly.
This page covers everything an implementor needs: the end-to-end flow, mounting the element, every
prop it accepts, the JSON shapes those props expect, and the exact shape of what the
successCallback/errorCallback payloads carry. For minting the checkout session, see
Checkout session.
1. The end-to-end integration flow
- Your backend calls Swirepay's
POST /v3/checkout-sessionwith your secret key andscope: "transfer"— see Checkout session. This must happen server-side; the secret key must never reach the browser. - Swirepay's backend returns
entity.encryption— a compact, opaque secure token string. - Your backend hands that secure token to your frontend, along with whatever prop values you want to pre-fill (customer/address prefill, contact type, theme, etc.).
- Your frontend mounts
<swirepay-contact-onboarding>, setssecureToken(and the other props) as JS properties, and registerssuccessCallback/errorCallback. - The shopper fills in (or reviews pre-filled/locked) contact details, address, and links a bank account, then submits.
There is no separate webhook recommendation for this element the way there is for
<swirepay-checkout> or <swirepay-transfer-center> — onboarding doesn't have an ongoing
transaction status to reconcile after the fact the way a payment or transfer does;
successCallback firing means the contact/funding-source records were created successfully at
that moment.
2. Live vs. Test mode
Your Swirepay account has a Live / Test toggle in the merchant dashboard. Each mode issues its own secret key (used when minting the checkout session):
- Test mode is a sandbox — contacts and bank-account links you create are not real, no funds ever move, and you can freely exercise the entire onboarding flow (including bank-linking via Plaid's own test/sandbox credentials for US accounts) without any real-world consequence.
- Live mode issues a live secret key and creates real contacts/funding sources against real bank accounts.
The client-side integration code is identical in both modes — same API endpoint, same element, same props. Only the secret key your backend uses to mint the session changes. Always develop and test against Test mode first, then swap in your live secret key when you go live.
3. Load the SDK and mount the element
Install the package from npm:
npm install @swirepay-developer/swirepay-frontend-payment-sdk@latest
// Registers <swirepay-checkout>/<swirepay-contact-onboarding> as a side effect of the import.
import '@swirepay-developer/swirepay-frontend-payment-sdk';
Or, if you don't use a bundler, load it directly from a CDN with a plain <script> tag (pin an
exact version rather than a floating tag, so a future release can't silently change behavior on
your page):
<!doctype html>
<html>
<body>
<swirepay-contact-onboarding
id="onboarding"
account-gid="account-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
></swirepay-contact-onboarding>
<script src="https://unpkg.com/@swirepay-developer/swirepay-frontend-payment-sdk@latest/dist/prod/swirepay-sdk.iife.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
customElements.whenDefined('swirepay-contact-onboarding').then(async () => {
const el = document.getElementById('onboarding');
// Fetch the secure token from YOUR backend — never mint it here.
const res = await fetch('/api/swirepay/onboarding-token', { method: 'POST' });
const { secureToken } = await res.json();
// secureToken/successCallback/errorCallback are set as JS properties,
// not HTML attributes (attributes can only hold strings).
el.secureToken = secureToken;
el.successCallback = (payload) => console.log('onboarding complete', payload);
el.errorCallback = (payload) => console.error('onboarding failed', payload);
});
});
</script>
</body>
</html>
Until secureToken is set, the element shows a loading state and does not create its internal
iframe at all — so it's safe to mount the tag immediately and attach the token once your backend
call resolves.
4. Props reference
| Prop (JS property) | HTML attribute | Type | Required | Description |
|---|---|---|---|---|
accountGid | account-gid | string | Yes | Your Swirepay account identifier. Without it the element renders an error instead of loading. |
secureToken | secure-token | string | Yes | The compact token (entity.encryption) from Checkout session. May be attached after initial mount. |
contactGid | contact-gid | string | No | Gid of an existing contact — loads and locks their known details instead of collecting fresh input. Also the field name used for this value in the successCallback payload — see §8. Unrelated to <swirepay-checkout>'s own customerGid prop, which identifies a different kind of record (a customer, not a contact) on a different element. |
customerPrefillJsonString | customer-prefill-json-string | JSON string | No | Pre-fills (but doesn't necessarily lock) the name/email/phone fields. See shape below. Ignored entirely if contactGid is set — see §7. |
contactTypePrefill | contact-type-prefill | string | No | Pre-selects a contact type. See §9 for the full list and a US-only restriction on two of the values. Also ignored if contactGid is set. |
addressFieldPrefrillJsonString | address-field-prefill-json-string | JSON string | No | Pre-fills the address section. See shape below. Note the field is spelled "Prefrill" (not "Prefill") in both the attribute and property name — this is the real spelling in the SDK, not a typo in this doc. |
defaultAddressCountry | default-address-country | string | No | Pre-selects/pins the address country. One of US, IN. |
themeJsonString | theme | JSON string | No | Visual theming — see §6 below. |
successCallback | (JS property only) | (payload: ContactOnboardingSuccessPayload) => void | No | Called once the contact + funding source have been created successfully. See §5. |
errorCallback | (JS property only) | (payload: ErrorPayload) => void | No | Called on a fatal setup/submission error. See §5. |
successCallback/errorCallback are functions, so — like secureToken — they can only be set as
JS properties (el.successCallback = fn), never as HTML attributes.
Name, email, and phone are collected on every onboarding, but only name is strictly required —
email and phone are optional; if you (or the shopper) provide either, it's validated, but leaving
them blank is allowed. This is different from <swirepay-checkout>, where all three are always
required — see §7 for the prefill shape and phone-number format.
Country-specific bank linking
For a default-address-country/selected country of US, bank accounts are linked live via Plaid.
For IN (India), the widget instead collects bank/routing details manually (account number + IFSC
code) — no extra prop is needed to switch this; it's driven entirely by the selected address
country. Only US and IN are currently supported address countries for this element.
5. Error and success payloads
Error payload
errorCallback receives an object shaped like:
type ErrorPayload = {
message: string;
cause?: string | number | boolean | { name: string; message: string; stack?: string } | Record<string, unknown> | unknown[] | undefined;
fatal: boolean;
};
cause is normalized so it survives crossing the widget's internal iframe boundary via
postMessage. A thrown error becomes { name, message, stack } (any HTTP status code the
underlying error carried is not preserved); a string/number/boolean cause passes through
unchanged (this is how an application-level rejection reason from Swirepay's backend usually
arrives, e.g. "[REJECTED]: reason"); a plain object/array is JSON round-tripped; anything else
becomes undefined.
- Fatal setup errors unmount the entire onboarding UI — no form left to retry from. Fired for:
missing
account-gid, a malformed/wrong-scopesecure-token, and failure to load required scripts or fetch prerequisite data on mount. - Recoverable submission failures (e.g. bank-linking failed, or creating the contact/funding source failed after the shopper filled everything in) leave the form mounted so the shopper can correct details and retry.
errorCallback's fatal field tells you which one just happened: true for a fatal setup error,
false for a recoverable submission failure — use it to decide how to react rather than trying to
infer severity from message/cause content.
Silent failures (no errorCallback call at all):
- Malformed
customer-prefill-json-stringJSON — console-warned only, falls back to empty name/email/phone. - Malformed
address-field-prefill-json-stringJSON — console-warned only, falls back to empty address fields. - Malformed/invalid
themeJSON — console-warned only, falls back to default theming (see §6).
Success payload
successCallback receives a shaped, fixed set of fields — not the full raw contact/funding-source
records the widget works with internally (which include full address, tax info, verification
internals, etc. that merchants don't need):
type ContactOnboardingSuccessPayload = {
contactGid: string | null;
contactType: string | null;
fundingSourceGid: string | null;
fundingSourceStatus: string | null;
};
A few important nuances:
contactGidmatches the input prop name — the input prop that identifies an existing contact iscontactGid/contact-gid(see §8), and this payload returns the same value under the same field name,contactGid.- If you reused an existing contact via the
contact-gidprop (no new contact was created),contactGidin the payload falls back to that same prop value you supplied, since there's no freshly-created contact to read agidfrom. contactTypealways comes from the widget's own selected/pre-filled state (contactTypePrefillor whatever the shopper picked), never echoed back from the API.fundingSourceGid/fundingSourceStatuscome from the funding-source creation result, with no fallback — they'll benullif that result doesn't have those exact fields.
6. Theming
theme/themeJsonString accepts a flat JSON object, identical for both <swirepay-contact-onboarding>
and <swirepay-checkout> (they share the same underlying theming code) — every key is optional; anything
you omit falls back to the SDK's defaults, and if the JSON itself is malformed or fails validation
the entire theme is discarded (fails closed to defaults) rather than partially applied.
| Key | Type | Affects |
|---|---|---|
componentBackgroundColor | color | Overall component background |
inputBackgroundColor | color | Input/select field background |
inputBorderRadius | length (px/rem/em/%) | Input/select corner radius |
infoBackgroundColor | color | Info banner background |
infoBorderColor | color | Info banner border |
buttonBackgroundColor | color | Primary button background |
buttonBorderRadius | length | Primary button corner radius |
buttonTextColor | color | Primary button text color |
spacingXs / spacingSm / spacingMd / spacingLg | length | Internal spacing scale |
fontFamily | non-empty string | Font family |
fontColor | color | Body text color |
fontColorError | color | Validation-error text color |
fontSizeSm / fontSizeMd / fontSizeLg | length | Font size scale |
fontWeightRegular / fontWeightMedium / fontWeightBold | integer 100–900 | Font weight scale |
"color" accepts hex (#rgb, #rgba, #rrggbb, #rrggbbaa), rgb()/rgba(), or hsl()/hsla().
Example:
{
"buttonBackgroundColor": "#3b5bfd",
"buttonTextColor": "#ffffff",
"inputBorderRadius": "8px",
"fontFamily": "Inter, sans-serif"
}
7. Prop interactions
contact-gid overrides the various prefill props — when it's set, the widget loads that
contact's real details instead:
| Prefill prop | Overridden when contact-gid is set? | Editable after load? |
|---|---|---|
customer-prefill-json-string (name/email/phone) | Yes — parsing is skipped entirely if contactGid is present | No, always locked read-only |
contact-type-prefill | Yes — same, skipped entirely if contactGid is present | No, always locked read-only |
address-field-prefill-json-string | Effectively yes, but not instantly — the JSON is still parsed and briefly applied on mount, then overwritten once the contact's real address loads. In practice the true contact's address always wins, just not immediately. | Yes — see below |
The address section is the one exception to "locked once a gid is set": when contact-gid is
present, an "Edit"/"Save" toggle appears next to the address fields, letting the shopper unlock and
change it even though every other field stays locked.
customer-prefill-json-string shape
{
"name": "Jane Doe",
"email": "jane@example.com",
"countryCallingCode": "+1",
"phone": "5555550100",
"disableInput": true
}
All fields are optional. countryCallingCode and phone are supplied separately — phone is
just the national-number digits (no country code, no leading +), and the widget concatenates them
itself (countryCallingCode + phone) to validate the result as a full E.164 number. For the example
above, that's +1 + 5555550100 → +15555550100. Passing a combined string like "+15555550100"
directly in phone will fail validation — keep the calling code and the local number as two
separate fields. Remember that email and phone are optional here (see §4) —
provide them if you have them, but you don't need placeholder values just to satisfy validation.
disableInput: true locks all three of name/email/phone together as read-only once they're
pre-filled — it's a single switch, not a per-field setting. Only relevant when contact-gid is
not set (see the override table above) — malformed JSON here is silently ignored (see
§5), falling back to an empty, editable form.
address-field-prefill-json-string shape
{
"street": "123 Main St",
"city": "San Francisco",
"state": "CA",
"postalCode": "94105",
"country": "US"
}
All fields optional, each defaulting to empty/unset if omitted. country should be US or IN to
match a real selectable option. Malformed JSON is silently ignored the same way as the customer
prefill above.
8. contactGid naming
- The input prop/attribute is
contactGid/contact-gid— you setel.contactGid = "...", and thesuccessCallbackpayload returns the same value under the same field name,payload.contactGid(see §5). Input and output names match on this element. - This is unrelated to
<swirepay-checkout>'scustomerGid/customer-gidprop — different element, different kind of record (a Swirepay customer, not a contact). The two elements don't share this prop, and setting one has no effect on the other.
9. Contact type options
contactTypePrefill/store.contactType accepts one of:
| Value | Notes |
|---|---|
CUSTOMER | |
EMPLOYEE | |
VENDOR | |
SENDER | US-only — see below |
MARKETPLACE | US-only — see below |
OTHER |
SENDER and MARKETPLACE are only available when the address country is US — the widget itself
enforces this: those two options simply aren't offered in the contact-type selector for any other
country, and if the country is changed away from US after one of them was selected, the contact
type is automatically cleared. If your integration plans to use either of these two types, it's
worth restricting/defaulting the address country to US in your own flow up front, so the shopper
isn't offered a contact type that then gets silently cleared out from under them later in the form.
10. Testing your integration
Test end-to-end in Test mode first (see §2): mint a session with your
test secret key, mount the element exactly as shown in §3,
and walk through the full flow — contact details, address, contact type, and bank-linking (using
Plaid's test/sandbox credentials for a US address) — before switching to a live secret key.
11. Common pitfalls
- Forgetting
account-gid— the element renders an inline error instead of loading anything. - Minting the checkout-session token in browser JS — never do this outside of local testing; it exposes your secret key to anyone viewing page source/network traffic.
- Setting
secureToken/callbacks as HTML attributes — they must be JS properties (el.secureToken = "...", notsecure-token="..."beyond the initial empty placeholder). - Malformed prefill JSON failing silently — both
customer-prefill-json-stringandaddress-field-prefill-json-stringswallow parse errors and fall back to empty state rather than callingerrorCallback; validate your JSON server-side before injecting it as an attribute. - Confusing this element's
contact-gidwith<swirepay-checkout>'scustomer-gid— they're different props on different elements identifying different kinds of records (a contact vs. a customer); see §8. - Assuming a
contact-gidlocks the address section like it locks name/email/phone — it doesn't; the shopper can still edit the address via the "Edit"/"Save" toggle (see §7). - Offering
SENDER/MARKETPLACEwithout restricting the address country toUS— those two contact types only stay selected if the address country isUS; changing the country afterward silently clears the selection (see §9). - Setting
default-address-countrytoCA— this element currently only supportsUS/INaddresses;CAisn't a selectable option here (unlike<swirepay-checkout>'s billing address, which does supportCA). - Assuming email/phone are required like they are in
<swirepay-checkout>— for this element, only name is strictly required; email/phone are optional but validated if provided. - Passing a combined
+1XXXXXXXXXXstring asphonein the prefill JSON —phoneis just the national digits; the country calling code is a separate field (see §7). - Expecting
causein the error payload to carry an HTTP status code — it doesn't; onlyname/message/stacksurvive for an error-based cause. Parsemessageinstead if you need to distinguish failure reasons.