Skip to main content

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

  1. Your backend calls Swirepay's POST /v3/checkout-session with your secret key and scope: "transfer" — see Checkout session. This must happen server-side; the secret key must never reach the browser.
  2. Swirepay's backend returns entity.encryption — a compact, opaque secure token string.
  3. 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.).
  4. Your frontend mounts <swirepay-contact-onboarding>, sets secureToken (and the other props) as JS properties, and registers successCallback/errorCallback.
  5. 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 attributeTypeRequiredDescription
accountGidaccount-gidstringYesYour Swirepay account identifier. Without it the element renders an error instead of loading.
secureTokensecure-tokenstringYesThe compact token (entity.encryption) from Checkout session. May be attached after initial mount.
contactGidcontact-gidstringNoGid 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.
customerPrefillJsonStringcustomer-prefill-json-stringJSON stringNoPre-fills (but doesn't necessarily lock) the name/email/phone fields. See shape below. Ignored entirely if contactGid is set — see §7.
contactTypePrefillcontact-type-prefillstringNoPre-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.
addressFieldPrefrillJsonStringaddress-field-prefill-json-stringJSON stringNoPre-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.
defaultAddressCountrydefault-address-countrystringNoPre-selects/pins the address country. One of US, IN.
themeJsonStringthemeJSON stringNoVisual theming — see §6 below.
successCallback(JS property only)(payload: ContactOnboardingSuccessPayload) => voidNoCalled once the contact + funding source have been created successfully. See §5.
errorCallback(JS property only)(payload: ErrorPayload) => voidNoCalled 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-scope secure-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-string JSON — console-warned only, falls back to empty name/email/phone.
  • Malformed address-field-prefill-json-string JSON — console-warned only, falls back to empty address fields.
  • Malformed/invalid theme JSON — 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:

  • contactGid matches the input prop name — the input prop that identifies an existing contact is contactGid/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-gid prop (no new contact was created), contactGid in the payload falls back to that same prop value you supplied, since there's no freshly-created contact to read a gid from.
  • contactType always comes from the widget's own selected/pre-filled state (contactTypePrefill or whatever the shopper picked), never echoed back from the API.
  • fundingSourceGid/fundingSourceStatus come from the funding-source creation result, with no fallback — they'll be null if 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.

KeyTypeAffects
componentBackgroundColorcolorOverall component background
inputBackgroundColorcolorInput/select field background
inputBorderRadiuslength (px/rem/em/%)Input/select corner radius
infoBackgroundColorcolorInfo banner background
infoBorderColorcolorInfo banner border
buttonBackgroundColorcolorPrimary button background
buttonBorderRadiuslengthPrimary button corner radius
buttonTextColorcolorPrimary button text color
spacingXs / spacingSm / spacingMd / spacingLglengthInternal spacing scale
fontFamilynon-empty stringFont family
fontColorcolorBody text color
fontColorErrorcolorValidation-error text color
fontSizeSm / fontSizeMd / fontSizeLglengthFont size scale
fontWeightRegular / fontWeightMedium / fontWeightBoldinteger 100–900Font 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 propOverridden when contact-gid is set?Editable after load?
customer-prefill-json-string (name/email/phone)Yes — parsing is skipped entirely if contactGid is presentNo, always locked read-only
contact-type-prefillYes — same, skipped entirely if contactGid is presentNo, always locked read-only
address-field-prefill-json-stringEffectively 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 separatelyphone 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 set el.contactGid = "...", and the successCallback payload 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>'s customerGid/customer-gid prop — 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:

ValueNotes
CUSTOMER
EMPLOYEE
VENDOR
SENDERUS-only — see below
MARKETPLACEUS-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 = "...", not secure-token="..." beyond the initial empty placeholder).
  • Malformed prefill JSON failing silently — both customer-prefill-json-string and address-field-prefill-json-string swallow parse errors and fall back to empty state rather than calling errorCallback; validate your JSON server-side before injecting it as an attribute.
  • Confusing this element's contact-gid with <swirepay-checkout>'s customer-gid — they're different props on different elements identifying different kinds of records (a contact vs. a customer); see §8.
  • Assuming a contact-gid locks 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/MARKETPLACE without restricting the address country to US — those two contact types only stay selected if the address country is US; changing the country afterward silently clears the selection (see §9).
  • Setting default-address-country to CA — this element currently only supports US/IN addresses; CA isn't a selectable option here (unlike <swirepay-checkout>'s billing address, which does support CA).
  • 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 +1XXXXXXXXXX string as phone in the prefill JSONphone is just the national digits; the country calling code is a separate field (see §7).
  • Expecting cause in the error payload to carry an HTTP status code — it doesn't; only name/message/stack survive for an error-based cause. Parse message instead if you need to distinguish failure reasons.