Skip to content

FASSTAP / Worldline SDK Configuration

The Sagio merchant Android app uses Worldline's FASSTAP MPOS SDK (vendor: Soft Space) to turn an off-the-shelf Android phone into an NFC payment terminal. This doc covers how the SDK is configured — i.e. how a merchant device gets the credentials and endpoints it needs to talk to Soft Space.

Design principle

The merchant never types Worldline configuration. Everything comes from the Sagio API.

The merchant Settings screen exposes only the status of the Worldline credentials and a Refresh button. All values are provisioned server-side and pulled on demand.

Data flow

┌──────────────────┐     authenticate     ┌──────────────────┐
│  Merchant App    │ ───────────────────▶ │  Sagio API       │
│  (Android)       │ ◀─── access token ── │  (Hono / CF)     │
└──────────────────┘                      └──────────────────┘
        │                                          │
        │   GET /api/merchant-app/fasspay-config   │
        │ ───────────────────────────────────────▶ │
        │                                          │
        │ ◀────────────── { 11 fields } ────────── │
┌──────────────────┐
│ SettingsRepo     │  decrypt + persist to EncryptedSharedPreferences
│  .saveConfig()   │
└──────────────────┘
┌──────────────────┐
│ FasstapManager   │  builds SSMPOSSDKConfiguration and calls SSMPOSSDK.init
│  .initialize()   │
└──────────────────┘

Key code paths:

  • API endpoint: apps/api/src/controllers/merchantAppController.tsgetFasstapConfig
  • API config defaults: apps/api/src/config/index.tsfasspay
  • Android fetch: apps/terminal/app/src/main/java/io/sagio/merchant/data/SettingsRepository.ktfetchRemoteConfig()
  • Android SDK init: apps/terminal/app/src/main/java/io/sagio/merchant/sdk/FasstapManager.ktinitialize()
  • Settings UI: apps/terminal/app/src/main/java/io/sagio/merchant/ui/screens/SettingsScreen.kt

The 11 SDK fields, classified

The Soft Space SDK requires 11 values in its SSMPOSSDKConfiguration.Builder. We classify them by where they come from:

Field Class Source
accessKey Required env FASSTAP_ACCESS_KEY (from Soft Space onboarding)
secretKey Required env FASSTAP_SECRET_KEY (from Soft Space onboarding)
uniqueId Required env FASSTAP_UNIQUE_ID — the user's MUID in the Soft Space merchant portal (per onboarded mobile user)
developerId Required env FASSTAP_DEVELOPER_ID — the company SSO identifier in the Soft Space merchant portal (per onboarded company)
environment Env, default UAT FASSTAP_ENVIRONMENT
attestationHost Env-derived default UAT → https://mpos-uat.fasspay.com:9001/v2, PROD → https://mpos.fasspay.com:9001/v2. Override with FASSTAP_ATTESTATION_HOST.
keyloadingHost Env-derived default UAT → https://mpos-uat.fasspay.com:9002/key/api/v3, PROD → https://mpos.fasspay.com:9002/key/api/v3. Override with FASSTAP_KEYLOADING_HOST.
attestationCertPinning Baked-in UAT default sha256/BJlJjxY7OHxhAz6yqy2gm58+qlP0AGwnBHDIG6zkhfU= from Soft Space SDK demo. Override with FASSTAP_ATTESTATION_CERT_PINNING (or for PROD).
keyloadingCertPinning Baked-in UAT default Same SHA-256 pin as attestation (Soft Space uses one cert across both hosts in UAT). Override per env.
keyloadingCACert Baked-in UAT default Soft Space's SSPaymentCA PEM, taken from the SDK demo. Override with FASSTAP_KEYLOADING_CA_CERT.
googlePlayProjectNumber Baked-in default Soft Space's own project number (757874674469) — they own Play Integrity verification, not us. Override with FASSTAP_GPLAY_PROJECT_NUMBER only if Soft Space issues a tenant-specific one.

Merchant portal: uniqueId and developerId

Soft Space (Liew Zhi Ying, May 2026) confirmed these are not emailed separately — look them up in the Soft Space merchant portal for the onboarded user/company:

SDK field Portal name Example (Sagio UAT automation)
uniqueId MUID of the user nydtubnqssautomationtester4
developerId Company SSO identifier yjOItT3r3MKqRZE

For UAT, set FASSTAP_UNIQUE_ID and FASSTAP_DEVELOPER_ID on the API from the portal row that matches the test merchant user. In production, if each Sagio merchant maps to its own Soft Space company/user, these will need to be per-merchant (see open questions).

developerId and FASSTAP_SSO_ID are the same identifier string — developerId goes into the SDK config returned to the app; ssoId is reserved for Sagio API → Worldline REST calls.

Backend-only: SSO identifier

The SDK does not accept a separate SSO setter — SSMPOSSDKConfiguration.Builder has no setSsoId (verified by disassembling ssmobile-mpos-sdk-release.aar). The phone authenticates to Soft Space using libAccessKey / libSecretKey / libUniqueID / libDeveloperID and fetches its own SSO token internally via LoginModel.performGetSSOToken(...) at runtime.

The same company SSO identifier used as developerId (e.g. yjOItT3r3MKqRZE) is also used as a request header on Worldline's server-to-server REST endpoints (settlement, transaction reporting, merchant management). Wired up as:

  • FASSTAP_SSO_ID env var
  • config.fasspay.ssoId on the API
  • Consumed by future Worldline REST client wrappers in apps/api/src/clients/ — not by the merchant-app fasspay-config response

Confirm with Soft Space which endpoints require it and the exact header name (X-SSO-ID is a guess) before shipping a client wrapper.

Where the baked-in defaults come from

The current values in apps/api/src/config/index.ts fasspay are sourced from:

  1. Soft Space SDK release v3.6.5.0 (fasstap-mpos-sdk-release-v3.6.5.0-worldlinenz). The demo app's app/build.gradle ships the exact UAT host URLs, cert pinning hash, keyloading CA PEM, and Soft Space's own Google Play project number that the SDK is configured against. Treat that file as the ground truth when the SDK version bumps.
  2. packages/worldline-sdk/src/types/config.ts — our TypeScript wrapper's ATTESTATION_HOSTS / KEYLOADING_HOSTS maps mirror the same values for client-side use.

App-ID whitelist: Soft Space whitelisted io.sagio.merchant as our merchant app's Android applicationId. The Sagio merchant module already declares this in apps/terminal/app/build.gradle.kts (applicationId = "io.sagio.merchant"). If we ever ship a separate flavor (e.g. staging build), we need Soft Space to whitelist that ID too — Play Integrity rejects unknown package names.

SDK version pin: apps/terminal/app/libs/ssmobile-mpos-sdk-release.aar is v3.6.5.0, which requires compileSdk = 35, JDK 17, and com.google.android.play:integrity:1.4.0+ (per the v3.6.5.0 release note + the demo's app/build.gradle). The Sagio merchant module pins the JDK via a Gradle toolchain (jvmToolchain(17) plus org.gradle.toolchains.foojay-resolver-convention in settings.gradle.kts), so devs don't need to install JDK 17 manually — Gradle auto-downloads it on first build.

API contract

GET /api/merchant-app/fasspay-config

Auth: Bearer token from POST /api/merchant-app/auth (issued from a merchant app key/secret pair, see mintMerchantKey.ts).

Required-field validation (server-side): rejects if any of accessKey, secretKey, uniqueId, developerId is blank. Returns the missing field names in the error message:

{ "success": false, "error": "Fasstap configuration missing: uniqueId, developerId" }

Success response:

{
  "success": true,
  "data": {
    "accessKey": "...",
    "secretKey": "...",
    "attestationHost": "https://mpos-uat.fasspay.com:9001/v2",
    "attestationCertPinning": "",
    "keyloadingHost": "https://mpos-uat.fasspay.com:9002/key/api/v3",
    "keyloadingCertPinning": "",
    "keyloadingCACert": "",
    "googlePlayProjectNumber": "",
    "uniqueId": "...",
    "developerId": "...",
    "environment": "UAT"
  }
}

Empty strings are valid for the optional fields — the Android client filters them out so SDK defaults apply (see SettingsRepository.fetchRemoteConfig, which only writes non-blank values).

Merchant Settings UX

Three sections, no Worldline data entry:

  1. Merchant Info — display name (local label only).
  2. SDK Credentials
  3. Sagio API credentials status row (built-in build values vs. user-saved override).
  4. Worldline SDK credentials status row (READY once all four required fields are loaded).
  5. Refresh SDK Credentials button → calls SettingsViewModel.refreshConfig() → hits the API endpoint above and persists the response.
  6. Readout: Environment: UAT after a successful sync.
  7. Sagio API (Optional) — base URL override for non-prod testing.

There is no Save-gate on Worldline credentials. The Save button only persists merchant name and Sagio API URL. Worldline values flow in automatically.

Provisioning the Sagio API key/secret into the merchant app

The merchant app talks to the Sagio API (not Soft Space directly) to fetch its FASSTAP config. It needs a Sagio-issued API key/secret pair for that. We use a build-time provisioning model: no device-side data entry, no runbook for operators.

1. Mint the credentials via apps/api/src/scripts/mintMerchantKey.ts:

cd apps/api && bun run src/scripts/mintMerchantKey.ts <merchantId>
# → prints API_KEY=... and API_SECRET=...

2. Pass them to the Gradle build. apps/terminal/app/build.gradle.kts:33-34 reads SAGIO_API_KEY and SAGIO_API_SECRET from either local.properties or the environment, and emits them as BuildConfig.SAGIO_API_KEY / BuildConfig.SAGIO_API_SECRET:

buildConfigField("String", "SAGIO_API_KEY",
    "\"${localProperties["SAGIO_API_KEY"] ?: System.getenv("SAGIO_API_KEY") ?: ""}\"")
buildConfigField("String", "SAGIO_API_SECRET",
    "\"${localProperties["SAGIO_API_SECRET"] ?: System.getenv("SAGIO_API_SECRET") ?: ""}\"")

Local dev — drop into apps/terminal/local.properties:

SAGIO_API_KEY=sk_live_...
SAGIO_API_SECRET=...

CI / release — set the env vars before invoking Gradle. The release pipeline already has keystore.properties for signing; co-locate API credentials there if convenient.

3. The app reads them at runtime via SettingsRepository.getSagioApiKey() / getSagioApiSecret(), which prefer BuildConfig over the user-overridable encrypted prefs. First launch → app authenticates against the Sagio API → fetches FASSTAP config → Worldline credential status flips to READY without the operator touching anything.

Demo bring-up checklist

  1. Set the four required env vars on the API (e.g. apps/api/.env.local for local, secrets manager for deployed):
FASSTAP_ACCESS_KEY=...        # integrator credentials from Soft Space (not in merchant portal)
FASSTAP_SECRET_KEY=...
FASSTAP_UNIQUE_ID=nydtubnqssautomationtester4   # MUID from merchant portal
FASSTAP_DEVELOPER_ID=yjOItT3r3MKqRZE              # company SSO ID from merchant portal
FASSTAP_ENVIRONMENT=UAT

Hosts, cert pinning, keyloading CA cert, and Soft Space's Google Play project number are all baked in for UAT — nothing else needs to be set unless Soft Space rotates a value.

If our backend will later hit Worldline REST endpoints (settlement, reporting), also set FASSTAP_SSO_ID=yjOItT3r3MKqRZE for the request header.

  1. Mint a merchant-app API key/secret pair with mintMerchantKey.ts and pass them to the Gradle build via local.properties or env vars — see "Provisioning the Sagio API key/secret" above.

  2. Install the merchant APK on the target device. On first launch the app authenticates against the Sagio API and auto-fetches the FASSTAP config in SettingsViewModel.init { ... refreshConfig(showErrors = false) }.

  3. Verify by opening Settings: the "Worldline SDK credentials" row should read READY, and Environment: UAT should appear under the help text. Tapping Refresh should re-pull successfully.

  4. Trigger an SDK init from the home screen and confirm FasstapManager.initialize() doesn't throw on missing fields. (SSMPOSSDK.init will surface server-side rejection separately if the credentials themselves are wrong.)

Launch checklist

One-time ops (not in git)

  1. Run migration apps/api/src/migrations/add_merchant_fasstap_columns.sql on Neon.
  2. Deploy Fasstap secrets: cd apps/api && bun run deploy:fasstap-secrets -- .env.local sagio-api-dev
  3. Mint merchant terminal key: bun run mint-merchant-key -- --email <merchant@example.com>
  4. Build APK with SAGIO_API_KEY / SAGIO_API_SECRET in apps/terminal/local.properties
  5. Send Soft Space your release (and debug) signing SHA-256 for Play Integrity
  6. Settings → Kitchen Printer → enter Epson/network printer IP (port 9100)

Terminal features (native app)

Feature Entry point
Amount + Card / Cash / SAGIO pay Home (Payment) screen
POS menu → cart → checkout Menu icon → product picker
Settlement / lookup ⋮ menu on Payment screen
SAGIO QR fallback Processing screen while waiting for wallet tap

Per-merchant Fasstap portal IDs: resolved in fasstapPortalService.ts when GET /merchant-app/fasspay-config runs. The SDK setUniqueID value must come from Soft Space — Sagio does not guess MUIDs from email.

Priority Source uniqueIdSource
1 merchants.fasstap_unique_id (set via set-merchant-fasstap-ids or Worldline approve) merchant_db
2 merchant_worldline_onboarding.metadata (fasstapUniqueId, muid, …) onboarding_metadata
3 Global FASSTAP_UNIQUE_ID / FASSTAP_DEVELOPER_ID (shared UAT tester) env

Integrator keys (accessKey, secretKey) remain global env vars on the worker.

Terminal READY gate: MerchantConfig.isValid() requires accessKey, secretKey, uniqueId, developerId, attestationHost, and keyloadingHost. Missing hosts surface the Settings warning and disable Charge before the SDK can return "Unlock fail" from refreshToken().

Open questions

  • Per-merchant vs. per-developer uniqueId / developerId — portal fields are per user (MUID) and per company (SSO ID). Confirm with Soft Space whether Sagio ships one shared UAT pair for all demo devices or whether each onboarded merchant needs their own values in getFasstapConfig. If the latter, move these out of env vars into a merchants table column.
  • SSO ID transport — value matches developerId (yjOItT3r3MKqRZE for UAT); the exact request-header name and the list of endpoints that require it are not confirmed in any of the SDK docs we have. Ask Soft Space before writing the REST client wrapper.
  • setLibGoogleApiKey — the SDK builder exposes this setter and the demo's build.gradle defines a Google API key value (AIzaSy...), but it's a Google Cloud–issued key registered to Soft Space's own GCP project, not something they hand out per integrator. The demo's MainActivity.initFasstapMPOSSDK() doesn't call the setter either, so we treat it as unused. If a future SDK rev requires it, we'd register our own Google Cloud API key and pipe it in.
  • PROD cert pinning + CA cert — UAT defaults are baked in; for PROD we'll need the equivalent Soft Space-published values when we get a PROD SDK drop.
  • Signing-cert whitelist with Soft Space — Soft Space whitelisted io.sagio.merchant as the applicationId, but Play Integrity verifies against the signing cert SHA-256 too. Confirm whether they've whitelisted our debug or release cert. See keystore.properties.example for the keytool command that prints the fingerprint.
  • 001-manual-tap-overview.md — high-level "reverse tap" architecture (customer-as-reader).
  • 002-security.md — security posture for the manual-tap flow.
  • apps/api/README.md — broader API setup.
  • packages/worldline-sdk/ — TypeScript SDK wrapping the FASSTAP transaction lifecycle.