Skip to main content

Merchant Onboarding Service

1. Overview

The Merchant Onboarding Service is a dedicated microservice inside the CDE project (peakpos-cde). Its sole purpose is to bridge the gap between the gateway's TransIT credentials and physical payment devices that need those credentials. It handles TransIT MerchantActivation API calls and pushes resulting credentials to NexGO's XTMS device management system.

Why this service exists: TransIT credentials (deviceID, transactionKey) must never leave the CDE boundary. External systems (e.g., PeakPOS Management API) can only trigger provisioning workflows — they never see, receive, or transmit raw credentials.

Rename note: this page still shows legacy merchantId fields where the shipped provisioning and credential APIs use them today. Those values should be read as concrete location ids on the wire.

1.1 Rename guardrails

Safe statements:

  • provisioning requests still use legacy merchantId field names on the wire
  • those identifiers point at the concrete location/device ownership boundary
  • the surrounding tenant model remains organization/location-first

Unsafe statements:

  • that provisioning still depends on a distinct merchant-first tenant model
  • that credential or job APIs have fully renamed away from merchantId
  • that support should describe onboarding as an exception to the rename

2. Responsibilities

ResponsibilityDescription
Merchant activationCall TransIT MerchantActivation API to generate transactionKey + activate deviceID
Device credential pushPush TransIT credentials to NexGO XTMS by device serial number
Credential storageEncrypt and store TransIT credentials in CDE Spanner via Cloud KMS
Credential rotationRotate transactionKeys on demand or on schedule
Provisioning audit trailImmutable log of every provisioning action (who, what, when, outcome)
Job orchestrationManage async provisioning jobs with retry, dead-letter, and manual replay
Status reportingReturn provisioning job status to callers (without exposing credentials)

3. Architecture Context

┌──────────────────────────────────────────────┐
│ GCP Project: peakpos (OUT of CDE) │
│ │
│ Management API │
│ │ │
│ │ POST /api/v1/provisioning/jobs │
│ │ OAuth token (scope: provision:request) │
│ │ Body: { merchantId, deviceSerial } │
│ │ Response: { jobId, status } │
└────┼─────────────────────────────────────────┘

│ Cloud Run IAM-gated HTTPS

┌────┼──────────────────────────────────────────────────────────────┐
│ ▼ │
│ Merchant Onboarding Service (CDE project) │
│ │ │
│ ├──→ TransIT MerchantActivation API (get credentials) │
│ ├──→ CDE Spanner (store encrypted credentials) │
│ ├──→ Cloud KMS (encrypt/decrypt credential material) │
│ └──→ NexGO XTMS API (push credentials to device by serial) │
│ │
│ GCP Project: peakpos-cde (PCI SCOPED) │
└───────────────────────────────────────────────────────────────────┘

4. API Specification

Authentication

All endpoints require a Gateway OAuth token issued by the Gateway Auth Service:

Authorization: Bearer <gateway-oauth-token>

Tokens are scoped — callers can only access endpoints matching their granted scopes.

Operationally, this means the legacy field names in the request body or status response are compatibility details, not a separate authorization or tenancy model.

4.1 External API (called by clients like PeakPOS Management API)

Create Provisioning Job

POST /api/v1/provisioning/jobs

Required Scope: provision:request

Request:
{
"merchantId": "loc_abc123",
"deviceSerial": "N5-2024-001234",
"action": "ACTIVATE", // ACTIVATE | DEACTIVATE | ROTATE_KEY
"callbackUrl": "https://mgmt-api.peakgateway.com/webhooks/provisioning", // optional
"requestedBy": "user_xyz789", // identity of the human who initiated this
"metadata": {
"storeId": "store_456",
"terminalId": "term_789",
"notes": "New c-store install at 123 Main St"
}
}

Response (202 Accepted):
{
"jobId": "prov_job_abc123",
"status": "QUEUED",
"merchantId": "loc_abc123",
"deviceSerial": "N5-2024-001234",
"action": "ACTIVATE",
"createdAt": "2026-03-08T15:00:00Z",
"estimatedCompletionSeconds": 30
}

Note: 202 (not 200) — provisioning is async. The response contains a job ID for polling or the caller can register a callback URL.

Get Job Status

GET /api/v1/provisioning/jobs/{jobId}

Required Scope: provision:request

Response (200):
{
"jobId": "prov_job_abc123",
"status": "COMPLETED", // QUEUED | IN_PROGRESS | COMPLETED | FAILED | RETRYING
"merchantId": "loc_abc123",
"deviceSerial": "N5-2024-001234",
"action": "ACTIVATE",
"steps": [
{
"step": "TRANSIT_ACTIVATION",
"status": "COMPLETED",
"completedAt": "2026-03-08T15:00:05Z"
},
{
"step": "CREDENTIAL_STORAGE",
"status": "COMPLETED",
"completedAt": "2026-03-08T15:00:06Z"
},
{
"step": "XTMS_PUSH",
"status": "COMPLETED",
"completedAt": "2026-03-08T15:00:12Z"
}
],
"createdAt": "2026-03-08T15:00:00Z",
"completedAt": "2026-03-08T15:00:12Z",
"error": null
}

Note: Steps are visible for transparency. Credential values are NEVER included in any response.

List Jobs

GET /api/v1/provisioning/jobs?merchantId={merchantId}&status={status}&page={page}&size={size}

Required Scope: provision:request

Response (200):
{
"jobs": [ ... ],
"page": 1,
"size": 20,
"totalElements": 3
}

Cancel Job

POST /api/v1/provisioning/jobs/{jobId}/cancel

Required Scope: provision:request

Response (200):
{
"jobId": "prov_job_abc123",
"status": "CANCELLED",
"cancelledAt": "2026-03-08T15:00:03Z",
"cancelledBy": "user_xyz789"
}

Only cancellable if status is QUEUED. In-progress jobs cannot be cancelled.

Retry Failed Job

POST /api/v1/provisioning/jobs/{jobId}/retry

Required Scope: provision:request

Response (202 Accepted):
{
"jobId": "prov_job_abc123",
"status": "QUEUED",
"retryCount": 2,
"retriedAt": "2026-03-08T16:00:00Z"
}

4.2 Internal API (called by other CDE services only)

Get Merchant Credentials (Processing Service only) — legacy

Deprecated, still wired. GET /internal/merchants/{merchantId}/credentials remains callable server-side for backwards compatibility, but it is no longer surfaced in the OpenAPI sidebar and new integrations should read from the credential_profiles-backed path instead (see libs/schema/.../CredentialProfile.sq). The legacy route will be retired alongside the CredentialService rewrite tracked in ORG-14 A1b; the shape below is kept for reference only. VPC-internal only, never exposed through the API Gateway, Processing Service service account only, fully audit-logged.

GET /internal/merchants/{merchantId}/credentials (legacy)

Required: Service-to-service IAM (not OAuth — internal VPC only)
Caller: Processing Service ONLY

Response (200):
{
"merchantId": "loc_abc123",
"deviceId": "88700000123456",
"transactionKey": "****REDACTED_IN_LOGS****",
"activatedAt": "2026-03-08T15:00:05Z",
"lastRotatedAt": null
}

Rotate Credentials

POST /internal/merchants/{merchantId}/rotate

Required: Service-to-service IAM
Caller: Processing Service or scheduled job

Response (200):
{
"merchantId": "loc_abc123",
"rotatedAt": "2026-03-08T15:00:00Z",
"previousKeyDeactivated": true,
"xtmsPushStatus": "COMPLETED"
}

5. Provisioning Job State Machine

┌─────────┐
┌──────────>│ QUEUED │
│ └────┬────┘
│ │
(retry) │ worker picks up
│ ▼
│ ┌──────────────┐
├───────────│ IN_PROGRESS │
│ └──┬───────┬───┘
│ │ │
│ success failure
│ │ │
│ ▼ ▼
│ ┌───────────┐ ┌────────────┐
│ │ COMPLETED │ │ RETRYING │──── max retries exceeded ──→ FAILED
│ └───────────┘ └─────┬──────┘
│ │
└─────────────────────────┘

CANCELLED ← (only from QUEUED, via cancel endpoint)

Job Steps (executed sequentially)

StepActionRollback on failure
TRANSIT_ACTIVATIONCall TransIT MerchantActivation APINone (idempotent — can retry safely)
CREDENTIAL_STORAGEEncrypt credentials via KMS, store in CDE SpannerDelete stored record
XTMS_PUSHPush credentials to NexGO XTMS by device serialMark as pending manual intervention

Retry Policy

Failure typeRetry strategy
TransIT API timeout/5xxExponential backoff: 5s, 15s, 45s (max 3 retries)
TransIT API 4xxNo retry (invalid request — fail immediately)
XTMS API timeout/5xxExponential backoff: 10s, 30s, 90s (max 3 retries)
XTMS API 4xxNo retry — fail and alert
KMS/Spanner errorsExponential backoff: 2s, 4s, 8s (max 3 retries)
Max retries exceededMove to dead-letter queue, alert on-call, status = FAILED

6. IAM & Service Account Matrix

Service Accounts

Service AccountGCP ProjectPurpose
merchant-onboarding-sa@peakpos-cdepeakpos-cdeRuns the Merchant Onboarding Service
processing-sa@peakpos-cdepeakpos-cdeProcessing Service — calls internal credential endpoint
gateway-auth-sa@peakpos-cdepeakpos-cdeGateway Auth Service — validates OAuth tokens
mgmt-api-sa@peakpospeakposManagement API — calls external provisioning API

IAM Role Bindings

PrincipalRoleResourcePurpose
merchant-onboarding-saroles/secretmanager.secretAccessorTransIT developer credentials secretRead TransIT developerID/developerKey
merchant-onboarding-saroles/cloudkms.cryptoKeyEncrypterDecrypterCredential encryption keyEncrypt/decrypt merchant transactionKeys
merchant-onboarding-saroles/spanner.databaseUserCDE Spanner instanceRead/write provisioning tables
merchant-onboarding-saroles/pubsub.publisherprovisioning-events topicPublish provisioning audit events
processing-saroles/run.invokerMerchant Onboarding Service (internal)Call internal credential endpoint
mgmt-api-sa@peakposroles/run.invokerMerchant Onboarding Service (external)Call external provisioning API
mgmt-api-sa@peakpos(none on secrets/KMS/Spanner)Cannot access credentials, keys, or CDE data

OAuth Scope Matrix

ScopeWho gets itWhat it allows
provision:requestPeakPOS Management API, Gateway Portal usersCreate/view/cancel/retry provisioning jobs
provision:adminGateway Portal admins onlyView audit logs, manual replay, configuration
merchant:activatePeakPOS Management API, Gateway Portal usersTrigger merchant activation in TransIT
credentials:readProcessing Service only (via IAM, not OAuth)Read raw TransIT credentials
credentials:rotateProcessing Service, scheduled jobs (via IAM)Rotate TransIT credentials

7. Data Model (CDE Spanner)

provisioning_jobs

CREATE TABLE provisioning_jobs (
job_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
merchant_id VARCHAR(50) NOT NULL,
device_serial VARCHAR(100) NOT NULL,
action VARCHAR(20) NOT NULL, -- ACTIVATE, DEACTIVATE, ROTATE_KEY
status VARCHAR(20) NOT NULL, -- QUEUED, IN_PROGRESS, COMPLETED, FAILED, RETRYING, CANCELLED
requested_by VARCHAR(100) NOT NULL, -- OAuth subject (user ID)
client_id VARCHAR(100) NOT NULL, -- OAuth client (e.g., "peakpos-mgmt-api")
callback_url VARCHAR(500),
metadata JSONB DEFAULT '{}',
current_step VARCHAR(30), -- Current step being executed
retry_count INTEGER DEFAULT 0,
error_message TEXT,
error_code VARCHAR(20),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
cancelled_at TIMESTAMPTZ
);

CREATE INDEX idx_prov_jobs_merchant ON provisioning_jobs (merchant_id);
CREATE INDEX idx_prov_jobs_status ON provisioning_jobs (status);
CREATE INDEX idx_prov_jobs_client ON provisioning_jobs (client_id);

merchant_credentials (encrypted) — legacy

Legacy per-merchant credential table. The authoritative credential surface is now credential_profiles (see libs/schema/.../CredentialProfile.sq), which scopes credentials to an organization and is assignable to merchants and locations. merchant_credentials is retained server-side for backwards compatibility and will be migrated onto credential_profiles in ORG-14 A1b.

CREATE TABLE merchant_credentials (
credential_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
merchant_id VARCHAR(50) NOT NULL UNIQUE,
transit_device_id BYTEA NOT NULL, -- KMS-encrypted
transaction_key BYTEA NOT NULL, -- KMS-encrypted
transit_merchant_id VARCHAR(50) NOT NULL, -- Not sensitive (returned in SmartConnect responses)
key_version INTEGER NOT NULL DEFAULT 1,
kms_key_name VARCHAR(200) NOT NULL, -- Full KMS key resource path
status VARCHAR(20) NOT NULL, -- ACTIVE, ROTATING, DEACTIVATED
activated_at TIMESTAMPTZ NOT NULL,
last_rotated_at TIMESTAMPTZ,
deactivated_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

provisioning_audit_log (append-only)

CREATE TABLE provisioning_audit_log (
log_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
job_id UUID NOT NULL,
event_type VARCHAR(50) NOT NULL, -- See audit events below
actor VARCHAR(100) NOT NULL, -- User ID or service account
actor_type VARCHAR(20) NOT NULL, -- USER, SERVICE, SYSTEM
client_id VARCHAR(100), -- OAuth client ID
merchant_id VARCHAR(50) NOT NULL,
device_serial VARCHAR(100),
details JSONB DEFAULT '{}', -- Step-specific details (NEVER contains credentials)
ip_address VARCHAR(45),
timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_prov_audit_merchant ON provisioning_audit_log (merchant_id);
CREATE INDEX idx_prov_audit_job ON provisioning_audit_log (job_id);
CREATE INDEX idx_prov_audit_time ON provisioning_audit_log (timestamp);

8. Audit Events

Every action produces an immutable audit log entry. Credential values are NEVER logged.

Event TypeTriggerDetails captured
PROVISION_REQUESTEDJob created via external APImerchantId, deviceSerial, action, requestedBy, clientId, IP
PROVISION_STARTEDWorker picks up jobjobId, step
TRANSIT_ACTIVATION_SUCCESSTransIT MerchantActivation returns 200merchantId, transitMerchantId (not keys), responseCode
TRANSIT_ACTIVATION_FAILEDTransIT returns errormerchantId, errorCode, errorMessage, retryCount
CREDENTIAL_STOREDEncrypted credential written to SpannermerchantId, keyVersion, kmsKeyName
CREDENTIAL_ROTATEDKey rotation completedmerchantId, previousKeyVersion, newKeyVersion
CREDENTIAL_ACCESSEDProcessing Service reads credentialsmerchantId, accessedBy (service account), purpose
XTMS_PUSH_SUCCESSXTMS accepts credential pushmerchantId, deviceSerial, xtmsResponseCode
XTMS_PUSH_FAILEDXTMS rejects or times outmerchantId, deviceSerial, errorCode, retryCount
PROVISION_COMPLETEDAll steps finished successfullyjobId, totalDurationMs
PROVISION_FAILEDMax retries exceededjobId, failedStep, errorCode, errorMessage
PROVISION_CANCELLEDJob cancelled by userjobId, cancelledBy, IP
PROVISION_RETRIEDManual retry triggeredjobId, retriedBy, retryCount
CREDENTIAL_DEACTIVATEDMerchant deactivatedmerchantId, deactivatedBy, reason

9. Security Controls

Credential Handling

  • Encryption at rest: All TransIT credentials encrypted via Cloud KMS (AES-256-GCM) before Spanner write
  • Encryption in transit: TLS 1.2+ on all connections (internal and external)
  • Ephemeral in memory: Credentials decrypted only for the duration of the XTMS push call, then zeroed
  • No logging of secrets: Credential values excluded from all application logs, audit logs, and error messages
  • No credential return: External API never returns credential values — only job status

Network Isolation

  • Service runs on Cloud Run in the CDE VPC
  • External API reachable only through Gateway API Gateway (Cloud Endpoints) with OAuth validation
  • Internal API reachable only via VPC-internal Cloud Run URL (no public endpoint)
  • Egress restricted to: TransIT API, XTMS API, CDE Spanner, Cloud KMS, Pub/Sub

Rate Limiting

EndpointLimitPer
POST /api/v1/provisioning/jobs10/minclient_id
GET /api/v1/provisioning/jobs/{jobId}60/minclient_id
GET /internal/merchants/{merchantId}/credentials (legacy, see section 4.2)100/minservice account

10. Pub/Sub Events

The service publishes events to the provisioning-events topic for async consumers.

{
"eventType": "PROVISION_COMPLETED",
"jobId": "prov_job_abc123",
"merchantId": "loc_abc123",
"deviceSerial": "N5-2024-001234",
"action": "ACTIVATE",
"timestamp": "2026-03-08T15:00:12Z",
"steps": [
{ "step": "TRANSIT_ACTIVATION", "status": "COMPLETED", "durationMs": 5000 },
{ "step": "CREDENTIAL_STORAGE", "status": "COMPLETED", "durationMs": 1000 },
{ "step": "XTMS_PUSH", "status": "COMPLETED", "durationMs": 6000 }
]
}

Subscribers

SubscriberEvent typesPurpose
Gateway PortalAllReal-time status updates in UI
Callback deliveryPROVISION_COMPLETED, PROVISION_FAILEDPOST to caller's callbackUrl
AlertingPROVISION_FAILED, XTMS_PUSH_FAILEDPagerDuty alert for on-call

4.3 Docs-side closeout checklist

Treat this page as closure-grade from the repo side only if readers can infer all of the following:

  • merchantId in provisioning payloads means the concrete location id on the wire
  • CDE boundary and credential-handling rules are explicit
  • support can reuse these examples without implying the old merchant-first tenant story is still normative

11. XTMS API Integration

The NexGO XTMS Cloud Open API (V1.9) provides full programmatic access for device provisioning.

Authentication

All XTMS API calls use SHA-256 signature-based authentication:

  1. Collect all request parameters (excluding signature)
  2. Sort by parameter name in ASCII/lexicographical order
  3. Concatenate as URL key-value pairs: key1=value1&key2=value2...
  4. Append the API key: stringA + key
  5. SHA-256 hash → uppercase hex string

Required per-request fields:

FieldDescription
appIdDistributor identifier (provided by NexGO)
keyAPI secret key (provided by NexGO)
signatureMethodAlways SHA-256
signatureNonce13-digit timestamp (unique per request)
timestampUTC time: yyyy-MM-dd'T'HH:mm:ssZ
versionAlways v1

Blocker: We need XTMS appId + key credentials from NexGO. Contact NexGO to enable API access for our distributor account.

Provisioning Flow (XTMS_PUSH step detail)

The XTMS_PUSH job step executes the following XTMS API calls in sequence:

Step 1: Create XTMS Merchant

POST /openapi/merchant/add

Request:
{
"merchantName": "Store ABC",
"merchantNo": "MID_12345", // TransIT merchantId
"terminalNos": ["TID_001", "TID_002"] // TransIT terminal IDs
}

Response:
{ "status": 200, "detail": 10086 } // XTMS merchant ID

Error codes: 1005001 (merchant already exists), 1005003 (TID duplicated)

Step 2: Bind Device to Terminal

POST /openapi/terminal/bindMachine

Request:
{
"merchantNo": "MID_12345",
"sn": "N5-2024-001234", // Physical device serial
"terminalNo": "TID_001"
}

Response:
{ "status": 200, "detail": null }

Rules: One device → one terminal. Device must belong to our distributor account. Error codes: 1008003 (terminal already bound), 1008004 (device already bound), 1007 (SN not owned)

Step 3: Create Parameter File with TransIT Credentials

POST /openapi/param/addParamFile

Request:
{
"fileName": "transit_creds_MID_12345_TID_001",
"remark": "TransIT credentials for merchant MID_12345",
"spcParam": "{\"TRANSIT_DEVICE_ID\":\"88700000123456\",\"TRANSIT_TXN_KEY\":\"encrypted_key_value\"}"
}

Response:
{
"status": 200,
"detail": {
"id": 695,
"fileName": "transit_creds_MID_12345_TID_001",
"pkey": "ebf6deaaa3933c1ffffdd7eba20883da" // Used for subsequent updates
}
}

SPC = Specific Parameter Configuration. Custom key-value pairs delivered to the SmartConnect app. The isEncryption: 1 flag on parameter items hides values in the XTMS UI.

Step 4: Push Parameters to Device

POST /openapi/paramPush/pushBySN

Request:
{
"snList": ["N5-2024-001234"],
"appPackageName": "com.nexgo.smartconnect", // SmartConnect package name
"paramId": 695 // Parameter file ID from step 3
}

Response:
{ "status": 200, "detail": null }

Rules: Max 5000 SNs per push. Creates async task — device must be online to receive.

Step 5: Monitor Push Status

POST /openapi/paramPush/getPushDetail

Request:
{
"pushId": 2,
"sn": "N5-2024-001234",
"page": 1,
"pageSize": 10
}

Response:
{
"status": 200,
"detail": {
"processingMachineCount": 0,
"successMachineCount": 1,
"failMachineCount": 0,
"pageData": {
"rows": [
{
"sn": "N5-2024-001234",
"state": 3, // 3 = Success
"updateTime": "2026-03-08T15:00:12Z"
}
]
}
}
}

Push states: 0 Not requested, 1 Requesting, 2 Downloading, 3 Success, 4 Failed, 5 Canceled, 9 Ignored

Additional XTMS Endpoints Used

EndpointMethodPurpose
/openapi/merchant/updateInfoPOSTUpdate merchant name/contact info
/openapi/merchant/detailPOSTGet merchant details by ID
/openapi/terminal/unboundMachinePOSTUnbind device from terminal (deprovisioning)
/openapi/terminal/getListPOSTList terminals and binding status
/openapi/param/specific/updatePOSTUpdate SPC parameters (credential rotation)
/openapi/paramPush/cancelBySnPOSTCancel pending push for a device
/openapi/paramPush/closeByIdPOSTClose a completed push task
/openapi/paramPush/getPushListBySNPOSTGet all push tasks for a specific device

XTMS Status Codes

CodeDescription
200Success
1001General failure
1005Distributor does not exist or lacks API permissions
1006Signature verification failed
1007SN does not match distributor ownership
1008No permission for this interface
1005001Merchant already exists
1005002Merchant does not belong to distributor
1005003TID is duplicated
1005004Terminal already bound to a device
1008003Terminal already bound to another merchant terminal
1008004Merchant terminal already bound to another device
1008006No authority to bind device to this terminal
9999Unknown error
-1System exception

Easy Deploy (Alternative for Bulk Provisioning)

XTMS Easy Deploy (V1.8+) bundles APP + PARAM + OTA + RKI tasks into a single template. Useful for fleet provisioning:

POST /openapi/easydpl/template/create // Create template (max 10 tasks)
POST /openapi/easydpl/push/create // Push template to devices by SN
POST /openapi/easydpl/push/list // List push tasks
POST /openapi/easydpl/push/detail // Track per-device status

Consider using Easy Deploy for initial fleet rollout (SmartConnect app + TransIT credentials + firmware in one push).


12. Blockers & Open Questions

ItemStatusImpact
XTMS REST API availability✅ RESOLVEDXTMS Cloud Open API V1.9 provides 60+ endpoints. Full parameter push, merchant management, and device binding available. See §11 above.
XTMS API authentication✅ RESOLVEDSHA-256 signature with appId + key. See §11 Authentication above.
XTMS API credentials❌ BLOCKEDNeed appId + key from NexGO to authenticate XTMS API calls. Must contact NexGO to enable API access for our distributor account.
TransIT MerchantActivation API access❌ BLOCKEDNeed developer portal access + sandbox credentials
Credential rotation impact❓ UNKNOWNDoes rotating a transactionKey in TransIT immediately invalidate the old one? If so, must coordinate with XTMS push timing to avoid downtime.
Multi-device per merchant✅ RESOLVEDXTMS supports multiple TIDs per merchant. Each TID binds to exactly one device SN. Our model supports this via the device_serial + metadata.terminalId fields.
SmartConnect parameter key names❓ UNKNOWNNeed to confirm the exact SPC parameter keys that SmartConnect expects for TransIT credentials (e.g., TRANSIT_DEVICE_ID, TRANSIT_TXN_KEY). May need NexGO's help or reverse-engineering from SmartConnect sample app.

13. Fallback: Manual XTMS Mode (Degraded)

If XTMS API is unreachable or credentials are unavailable, the XTMS_PUSH step falls back to a manual workflow:

  1. Provisioning job completes TRANSIT_ACTIVATION and CREDENTIAL_STORAGE steps
  2. Job status becomes PENDING_MANUAL_PUSH
  3. Gateway Portal displays the credentials (masked, copy-to-clipboard) to authorized admin
  4. Admin logs into XTMS web portal, configures device manually
  5. Admin clicks "Confirm Push" in Gateway Portal
  6. Job status becomes COMPLETED
  7. Audit log records: who viewed credentials, when, and when manual push was confirmed

This preserves the audit trail and keeps credentials within the CDE boundary. This mode is a degraded fallback, not the primary path — the XTMS API integration (§11) is the production flow.