Skip to main content

Defining AppSwitch credentials

A guide for POS developers on how to scope Integrator credentials and avoid CLIENT_INVALID_INTEGRATOR (error 120).

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-YourPOSSPAY-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 raises 120.


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 backend 2401 (unknown integrator id).

  • SOFTPAY_NO_APP / 75 that turns into 120 once the production app is installed — a tell-tale sign .target() was never set to SANDBOX.

  • error 120 with 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

INTEGRATOR_UNAUTHORIZED

Environment

Are these prod creds on prod (and sandbox on sandbox)?

120 / 100 (+ nested 2401)

Environment

Id correct & case-exact for this environment? ! prefix in log = SDK flagged it invalid

SOFTPAY_NO_APP / 75120

Environment

.target() not set / set to the wrong app

120 / -543 ("label mismatch")

White-label

.label("…") set, and not via .targetApp(); restart if previously wrong

120 / -12005

Clock skew

Enable network time on the device, restart

120

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- vs SPAY- 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.

Did this answer your question?