A short, precise guide for POS developers integrating the Softpay AppSwitch SDK.
The setup
The Integrator Credentials (Id + Secret) are your runtime "api key" for creating a Client. They are merchant-agnostic — the same set is used for all your apps in one environment, and a different set for the other environment.
Kotlin / Java (Integrator.Builder, ≥ 1.6.0)
val integrator = Integrator.Builder()
.id(integratorId = "Spay-YourPOS", merchant = "YourPOS Acme Inc") // case-sensitive id; merchant = org name, not a store
.secret(secret = charArrayOf('e','8','3','3','7','c','c','c', /* ... */ 'c','8'))
.target(target = SoftpayTarget.SANDBOX) // pin the environment — see Pitfall 1
// .label(label = "yourpos") // REQUIRED for white-label apps — see Pitfall 2
.build()
Maui / Xamarin (C# — note BuildIntegrator() and PascalCase methods)
char[] secret = new char[] { 'e','8','3','3','7','c','c','c', /* ... */ 'c','8' };Integrator integrator = new Integrator.Builder()
.Id("Spay-YourPOS", "YourPOS Acme Inc")
.Secret(secret)
.MauiEnvironment("POS v1.2", posAppId) // use .XamarinEnvironment(...) for Xamarin
.Target(SoftpayTarget.Sandbox) // pin the environment — see Pitfall 1
// .Label("yourpos") // REQUIRED for white-label apps — see Pitfall 2
.BuildIntegrator();
On AppSwitch ≤ 1.6.0 the Builder is the only way to set a MauiEnvironment; from 1.6.1 the relevant C# Integrator constructors accept one too.
Rules that matter:
Id is case-sensitive and whitespace-free.
Spay-YourPOS≠SPAY-YourPOS.Secret is an even-length hex string. Supply it as a literal
charArrayOf(...)/new char[]{...}— never"...".toCharArray()(the String lingers in the JVM string pool). Ideally deliver it from your backend or encrypted storage.The same
(Id, Secret)cannot be reused across different target Softpay apps on the same device — that raises120.
The three pitfalls (this is where tickets come from)
Pitfall 1 — Environment mismatch (most common)
Using sandbox credentials against production (or vice versa), or building the Integrator without a .target() so it connects to whichever app it finds first.
Symptoms
Backend returns
INTEGRATOR_UNAUTHORIZED.120 = CLIENT_INVALID_INTEGRATOR / 100, often with nested backend2401(unknown integrator id).SOFTPAY_NO_APP / 75that turns into120once the production app is installed — a tell-tale sign.target()was never set toSANDBOX.error 120with no AppSwitch calls in Softpay backend logs at all → the SDK rejected the credentials before ever reaching us (also check the on-device Softpay app is ≥ 1.4.0).
Fix: Use prod credentials for prod, sandbox credentials for sandbox — never mix. Always set .target(SoftpayTarget.SANDBOX|PRODUCTION) to pin which app you connect to, especially when both apps may be installed on one device.
Pitfall 2 — Missing or wrong .label() for white-label apps
For any white-label Softpay app (io.softpay.<label> / io.softpay.<label>.sandbox), the SDK cannot deduce the label on its own in SDK ≤ 1.6.0 — you must set it. It is auto-deduced only from 1.6.1+.
Symptoms
120 / -543(REMOTE origin), backend log:App-switch label mismatch: acme != null.
Fix: Set .label("acme") together with .target(...). Do not use .targetApp(name = "io.softpay.acme", ...) for white-label apps — .targetApp() is only for 3rd-party, non-io.softpay packages (e.g. com.payment.app). For an io.softpay.* white-label, .label() is the correct and required mechanism.
Kotlin / Java
val integrator = Integrator.Builder() .id("Spay-YourPOS", "YourPOS Acme Inc") .secret(charArrayOf(/* ... */)) .label(label = "acme") // required for white-label in SDK ≤ 1.6.0 .target(target = SoftpayTarget.SANDBOX) .build()
Maui / Xamarin (C#)
Integrator integrator = new Integrator.Builder() .Id("Spay-YourPOS", "YourPOS Acme Inc") .Secret(secret) .Label("acme") // required for white-label in SDK ≤ 1.6.0 .Target(SoftpayTarget.Sandbox) .BuildIntegrator();
If a label was previously set wrong and cached, restart fresh after correcting it.
Pitfall 3 — Device clock skew
Credential validation fails when the device time is too far off.
Symptoms
120 / -12005— this detailed code specifically means the device clock is out of tolerance, even when the label and credentials are correct.
Fix: Enable automatic network time on the device and restart the app.
Error code quick reference
What you see | Bucket | First thing to check |
| Environment | Are these prod creds on prod (and sandbox on sandbox)? |
| Environment | Id correct & case-exact for this environment? |
| Environment |
|
| White-label |
|
| Clock skew | Enable network time on the device, restart |
| Environment / version | Wrong creds rejected client-side; confirm Softpay app ≥ 1.4.0 |
Checklist before you raise a ticket
Prod credentials on prod, sandbox on sandbox — never mixed.
Id is exact and case-correct (
Spay-vsSPAY-matters).target(SANDBOX|PRODUCTION)is set explicitly.White-label app?
.label("…")is set (and you are not using.targetApp()for it). On SDK ≤ 1.6.0 this is mandatory.Device clock is on automatic/network time (rules out
-12005).Secret supplied as a literal
charArrayOf(...), not"...".toCharArray().
Source: Softpay Developer Docs (SDK → Credentials / Integrator; Failures.CLIENT_INVALID_INTEGRATOR) and support case history 2023–2025. Contact Softpay Support to be (re)issued Integrator credentials.