Skip to main content

Processing Service

1. Overview

The Processing Microservice is the core payment engine. It handles all transaction-related communication with the TSYS TransIT Multipass API. All payment operations (sale, auth, capture, void, refund) flow through this service. It also handles settlement reconciliation and transaction lifecycle management. Note: the Merchant Onboarding Service also communicates with TransIT for MerchantActivation (credential generation) — see the Merchant Onboarding Service spec.

Rename note: the current tenant model is Organization -> Location. This page still shows legacy merchantId request/response fields where they match the shipped wire contract; in those cases, read the value as the concrete location record, not as a separate top-level tenant type.

1.1 Rename guardrails

Safe statements:

  • the processing runtime is location-scoped for concrete transaction execution
  • legacy merchantId fields in requests and responses identify the concrete location on the wire
  • organization-level defaults such as credentials or tax configuration can still influence runtime behavior upstream of the payment call

Unsafe statements:

  • that merchantId here implies a separate tenant model from Location
  • that every processing API field has already been renamed to locationId
  • that support should describe the processing surface as merchant-first for new integrations

2. Responsibilities

ResponsibilityDescription
Transaction processingExecute sale, auth, capture, void, refund against TransIT
Transaction routingRoute requests to correct TransIT credentials per merchant
IdempotencyPrevent duplicate transactions via Spanner-backed idempotency keys
Settlement reconciliationReconcile daily TransIT settlement batches against our records
Transaction loggingPersist all transaction attempts, responses, and state transitions
Retry handlingRetry failed TransIT calls with exponential backoff
Tip adjustmentAdjust tip amounts on authorized transactions before settlement
L2/L3 enrichmentAuto-enrich transactions with Level 2/3 data post-auth to reduce interchange fees
Pin debit detectionIdentify pin debit transactions to skip adjustment operations
Pricing calculationApply cash discount, surcharge amounts at transaction time
Batch settlement configManage timed and scheduled batch settlement (default 10:30 PM)
Recurring billingExecute scheduled recurring charges using stored Guardian tokens
Subscription lifecycleManage subscription creation, billing cycles, retries, pausing, and cancellation
Event publishingPublish transaction events to Pub/Sub for async consumers

3. API Specification

Base URL

  • Internal: https://processing-service.internal:8080
  • Not exposed publicly — only called by Management and Online Txn services

Endpoints

Process Sale

POST /api/v1/transactions/sale

Request:
{
"merchantId": "loc_abc123",
"amount": 2500, // cents
"currency": "USD",
"cardDataSource": "MANUAL", // MANUAL | SWIPE | EMV | CONTACTLESS | TOKEN | INTERNET
"cardData": {
"cardNumber": "4111111111111111", // or tokenId for TOKEN source
"expirationDate": "1226",
"cvv": "123",
"cardHolderName": "John Doe"
},
"billing": {
"zip": "28202"
},
"industryType": "RE", // RE | EC | RS
"externalReferenceId": "uuid-for-idempotency",
"metadata": {}
}

Response (200):
{
"transactionId": "txn_abc123",
"transitTransactionId": "123456789",
"status": "APPROVED",
"approvalCode": "TAS123",
"responseCode": "A0000",
"processedAmount": 2500,
"tokenId": "tok_xyz789",
"avsResult": "Y",
"cvvResult": "M",
"createdAt": "2026-03-03T15:00:00Z"
}

Response (200 - Declined):
{
"transactionId": "txn_abc124",
"status": "DECLINED",
"responseCode": "D1220",
"responseMessage": "Insufficient funds",
"createdAt": "2026-03-03T15:00:01Z"
}

Process Authorization

POST /api/v1/transactions/auth
// Same request body as sale
// Response includes transactionId for later capture

Capture Authorization

POST /api/v1/transactions/{transactionId}/capture

Request:
{
"amount": 2500 // can be less than or equal to auth amount
}

Response (200):
{
"transactionId": "txn_abc123",
"status": "CAPTURED",
"capturedAmount": 2500,
"remainingAuthorizedAmount": 0,
"capturedAt": "2026-03-03T15:05:00Z"
}

Partial captures keep the authorization capturable until remainingAuthorizedAmount reaches zero. Merchant Dashboard and Admin Portal transaction details both expose the captured and remaining authorized amounts and allow operators to capture a specific remaining amount.

Increment Authorization

POST /api/v1/transactions/{transactionId}/increment

Request:
{
"amount": 1500
}

Response (200):
{
"transactionId": "txn_abc123",
"status": "APPROVED",
"amount": 4000,
"remainingAuthorizedAmount": 4000
}

Use incremental authorization before capture when the final expected amount increases, for example hospitality, rental, or delayed-fulfillment orders. The Admin Portal and Merchant Dashboard action panels accept operator-entered increment amounts and refresh the transaction after success.

Void Transaction

POST /api/v1/transactions/{transactionId}/void

Response (200):
{
"transactionId": "txn_abc123",
"status": "VOIDED",
"voidedAt": "2026-03-03T15:10:00Z"
}

Refund Transaction

POST /api/v1/transactions/{transactionId}/refund

Request:
{
"amount": 1500 // partial refund supported
}

Response (200):
{
"transactionId": "txn_abc123",
"refundTransactionId": "txn_ref456",
"status": "REFUNDED",
"refundedAmount": 1500,
"refundedAt": "2026-03-03T15:15:00Z"
}

Get Transaction

GET /api/v1/transactions/{transactionId}

Response (200):
{
"transactionId": "txn_abc123",
"merchantId": "loc_abc123",
"type": "SALE",
"status": "SETTLED",
"amount": 2500,
"processedAmount": 2500,
"cardBrand": "VISA",
"cardLast4": "1111",
"approvalCode": "TAS123",
"responseCode": "A0000",
"avsResult": "Y",
"cvvResult": "M",
"tokenId": "tok_xyz789",
"externalReferenceId": "uuid",
"createdAt": "2026-03-03T15:00:00Z",
"settledAt": "2026-03-04T06:00:00Z"
}

Search Transactions

GET /api/v1/transactions?merchantId=loc_abc123&status=APPROVED&from=2026-03-01&to=2026-03-03&limit=50&offset=0

Response (200):
{
"transactions": [...],
"total": 1234,
"limit": 50,
"offset": 0
}

Get Settlement Batch

GET /api/v1/settlements?date=2026-03-03&merchantId=loc_abc123

Response (200):
{
"batchId": "batch_20260303",
"date": "2026-03-03",
"merchantId": "loc_abc123",
"totalSales": 150000,
"totalRefunds": 5000,
"netAmount": 145000,
"transactionCount": 47,
"status": "RECONCILED",
"reconciledAt": "2026-03-04T07:00:00Z"
}

Tip Adjustment

POST /api/v1/transactions/{transactionId}/tip-adjust

Request:
{
"tipAmount": 500
}

Response (200):
{
"transactionId": "txn_abc123",
"originalAmount": 2500,
"tipAmount": 500,
"totalAmount": 3000,
"status": "TIP_ADJUSTED",
"adjustedAt": "2026-03-03T15:20:00Z"
}

Validates: transaction is approved/captured, batch not yet settled. Relays adjustment to TransIT.

L2/L3 Data Enrichment

POST /api/v1/transactions/{transactionId}/enrich

Request:
{
"level2": {
"taxAmount": 200,
"customerCode": "PO-12345",
"taxIndicator": "Y"
},
"level3": {
"lineItems": [
{
"description": "Widget A",
"quantity": 2,
"unitCost": 1000,
"commodityCode": "123",
"discountAmount": 0
}
],
"freightAmount": 500,
"dutyAmount": 0
}
}

Response (200):
{
"transactionId": "txn_abc123",
"enrichmentStatus": "APPLIED",
"enrichedAt": "2026-03-03T15:25:00Z"
}
  • Auto-triggered immediately after authorization approval
  • Also supports end-of-day batch enrichment
  • Pin debit detection: returns enrichmentStatus: "SKIPPED_PIN_DEBIT" if pin debit

Partial Void

POST /api/v1/transactions/{transactionId}/void

Request:
{
"amount": 1000 // optional — omit for full void, include for partial
}

Full void: omit amount. Partial void: include amount. Only valid before batch settlement.

Batch Settlement Configuration

POST /api/v1/settlements/configure

Request:
{
"merchantId": "loc_abc123",
"settlementMode": "SCHEDULED",
"scheduledTime": "22:30",
"timezone": "America/New_York"
}

Response (200):
{
"merchantId": "loc_abc123",
"settlementMode": "SCHEDULED",
"scheduledTime": "22:30",
"timezone": "America/New_York",
"updatedAt": "2026-03-03T15:30:00Z"
}

Default dev batch closing: 10:30 PM. Supports both scheduled and on-demand modes.

Create Subscription

POST /api/v1/subscriptions

Request:
{
"merchantId": "loc_abc123",
"customerId": "cust_001",
"tokenId": "tok_xyz789",
"planName": "Monthly Service",
"amount": 4999,
"currency": "USD",
"interval": "MONTHLY",
"intervalCount": 1,
"startDate": "2026-04-01",
"endDate": null,
"maxCycles": null,
"metadata": { "planId": "plan_premium" }
}

Response (201):
{
"subscriptionId": "sub_abc123",
"merchantId": "loc_abc123",
"customerId": "cust_001",
"status": "ACTIVE",
"currentCycle": 0,
"nextBillingDate": "2026-04-01",
"createdAt": "2026-03-07T10:00:00Z"
}
  • interval: DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY
  • intervalCount: multiplier (e.g., interval=WEEKLY + intervalCount=2 = biweekly)
  • endDate / maxCycles: optional; omit both for indefinite billing
  • Charges executed via stored Guardian token (tokenId)

Get Subscription

GET /api/v1/subscriptions/{subscriptionId}

Response (200):
{
"subscriptionId": "sub_abc123",
"merchantId": "loc_abc123",
"customerId": "cust_001",
"tokenId": "tok_xyz789",
"planName": "Monthly Service",
"amount": 4999,
"currency": "USD",
"interval": "MONTHLY",
"intervalCount": 1,
"status": "ACTIVE",
"currentCycle": 3,
"nextBillingDate": "2026-07-01",
"lastBillingDate": "2026-06-01",
"lastBillingStatus": "SUCCESS",
"startDate": "2026-04-01",
"endDate": null,
"maxCycles": null,
"failedAttempts": 0,
"metadata": { "planId": "plan_premium" },
"createdAt": "2026-03-07T10:00:00Z",
"updatedAt": "2026-06-01T06:00:00Z"
}

List Subscriptions

GET /api/v1/subscriptions?merchantId=loc_abc123&customerId=cust_001&status=ACTIVE&limit=50&offset=0

Response (200):
{
"subscriptions": [...],
"total": 12,
"limit": 50,
"offset": 0
}

Update Subscription

PATCH /api/v1/subscriptions/{subscriptionId}

Request:
{
"amount": 5999,
"tokenId": "tok_new456",
"planName": "Premium Service"
}

Response (200):
{
"subscriptionId": "sub_abc123",
"status": "ACTIVE",
"amount": 5999,
"updatedAt": "2026-03-07T12:00:00Z"
}

Only amount, tokenId, planName, endDate, maxCycles, and metadata are updatable. Interval changes require cancel + recreate.

3.1 Docs-side closeout checklist

Treat this page as closure-grade for the rename only if readers can infer all of the following without code archaeology:

  • merchantId examples refer to concrete location ids on the wire
  • organization/location remains the preferred tenant model everywhere outside the legacy field names
  • support can reuse these examples without implying that the old merchant-first naming is still the desired long-term model

Pause Subscription

POST /api/v1/subscriptions/{subscriptionId}/pause

Request:
{
"resumeDate": "2026-05-01"
}

Response (200):
{
"subscriptionId": "sub_abc123",
"status": "PAUSED",
"pausedAt": "2026-03-07T12:00:00Z",
"scheduledResumeDate": "2026-05-01"
}

Resume Subscription

POST /api/v1/subscriptions/{subscriptionId}/resume

Response (200):
{
"subscriptionId": "sub_abc123",
"status": "ACTIVE",
"nextBillingDate": "2026-05-01",
"resumedAt": "2026-03-07T12:00:00Z"
}

Cancel Subscription

POST /api/v1/subscriptions/{subscriptionId}/cancel

Request:
{
"cancelAt": "END_OF_CYCLE",
"reason": "Customer requested"
}

Response (200):
{
"subscriptionId": "sub_abc123",
"status": "CANCELLING",
"cancelAt": "2026-04-01",
"cancelledAt": "2026-03-07T12:00:00Z",
"cancellationReason": "Customer requested"
}
  • cancelAt: IMMEDIATELY or END_OF_CYCLE (default)
  • IMMEDIATELY sets status to CANCELLED and stops billing
  • END_OF_CYCLE sets status to CANCELLING — final charge runs at next billing date, then status transitions to CANCELLED

Subscription Billing History

GET /api/v1/subscriptions/{subscriptionId}/billing-history?limit=50&offset=0

Response (200):
{
"billingEvents": [
{
"cycle": 3,
"transactionId": "txn_rec789",
"amount": 4999,
"status": "SUCCESS",
"billedAt": "2026-06-01T06:00:00Z"
},
{
"cycle": 2,
"transactionId": "txn_rec456",
"amount": 4999,
"status": "SUCCESS",
"billedAt": "2026-05-01T06:00:00Z"
}
],
"total": 3,
"limit": 50,
"offset": 0
}

Recurring Billing Engine

The subscription billing engine runs as a scheduled job within the Processing Microservice:

  1. Scheduler (Cloud Scheduler → Pub/Sub): Triggers billing job every hour
  2. Billing Job: Queries subscriptions where next_billing_date <= NOW() and status = ACTIVE
  3. Per-subscription: Executes a TOKEN sale via TransIT using the stored Guardian token
  4. On success: Updates current_cycle, next_billing_date, last_billing_date, publishes subscription.billed event
  5. On decline: Increments failed_attempts, schedules retry per dunning policy
  6. Dunning policy: Retry at 1 day, 3 days, 7 days. After 3 failed attempts → status becomes PAST_DUE. After 7 days past due → SUSPENDED. Merchant configurable.
┌──────────────────┐ ┌─────────────────┐ ┌──────────────┐
│ Cloud Scheduler │────>│ Billing Job │────>│ Processing │
│ (hourly trigger) │ │ (query due subs)│ │ (TOKEN sale) │
└──────────────────┘ └────────┬────────┘ └──────┬───────┘
│ │
│ ┌──────────────────┘
│ │
v v
┌─────────────────┐
│ Update sub + │
│ Publish event │
└─────────────────┘

4. Transaction State Machine

┌─────────────┐
│ PENDING │
└──────┬──────┘

┌────────────┼────────────┐
│ │ │
v v v
┌──────────┐ ┌───────────┐ ┌──────────┐
│ APPROVED │ │ DECLINED │ │ ERROR │
└────┬─────┘ └───────────┘ └──────────┘

┌──────┼──────┬────────────┐
│ │ │ │
v │ v v
┌────────┐ │ ┌─────────┐ ┌─────────────┐
│ VOIDED │ │ │CAPTURED │ │TIP_ADJUSTED │
│(full/ │ │ └────┬────┘ └──────┬──────┘
│partial)│ │ │ │
└────────┘ │ │ ┌──────┘
│ │ │
v v v
┌───────────────────────┐
│ ENRICHED (L2/L3) │ ← optional (skipped for pin debit)
└───────────┬───────────┘

v
┌───────────────┐
│ SETTLED │
└───────┬───────┘

v
┌───────────────┐
│ REFUNDED │
│ (full/partial)│
└───────────────┘

Subscription State Machine

┌──────────┐
│ ACTIVE │◄──────────────────────────┐
└────┬─────┘ │
│ │
┌───────────────┼───────────────┐ │
│ │ │ │
v v v │
┌──────────┐ ┌───────────┐ ┌─────────────┐ ┌───────────┐
│ PAUSED │ │ PAST_DUE │ │ CANCELLING │ │ resume │
└────┬─────┘ └─────┬─────┘ └──────┬──────┘ └───────────┘
│ │ │ ^
│ v v │
│ ┌────────────┐ ┌────────────┐ │
│ │ SUSPENDED │ │ CANCELLED │ │
│ └─────┬──────┘ └────────────┘ │
│ │ │
│ v │
│ ┌────────────┐ │
│ │ CANCELLED │ │
│ └────────────┘ │
│ │
└──────────────────────────────────────────────────┘

Transitions:
ACTIVE → PAUSED (merchant/customer pause request)
ACTIVE → PAST_DUE (3 consecutive billing failures)
ACTIVE → CANCELLING (cancel at end of cycle)
ACTIVE → CANCELLED (cancel immediately)
ACTIVE → COMPLETED (max_cycles reached or end_date passed)
PAUSED → ACTIVE (resume — manual or scheduled_resume_date)
PAST_DUE → ACTIVE (successful retry payment)
PAST_DUE → SUSPENDED (7 days past due with no successful payment)
SUSPENDED → CANCELLED (auto or manual)
CANCELLING → CANCELLED (after final billing cycle)

5. Technical Stack

ComponentTechnology
LanguageKotlin (JVM 25)
FrameworkSpring Boot 4.0.2
HTTP ClientSpring WebClient (non-blocking) for TransIT calls
DatabaseCloud Spanner (PostgreSQL dialect) — transactions schema
MessagingCloud Pub/Sub — transaction events
SecretsCloud KMS-encrypted in CDE Spanner (see Merchant Onboarding Service)
BuildBazel
TestingJUnit 5, Testcontainers, WireMock (TransIT mock)

6. Database Schema

CREATE TABLE transactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
merchant_id VARCHAR(64) NOT NULL,
type VARCHAR(20) NOT NULL, -- SALE, AUTH, CAPTURE, VOID, REFUND, RECURRING
status VARCHAR(20) NOT NULL, -- PENDING, APPROVED, DECLINED, etc.
amount BIGINT NOT NULL, -- cents
processed_amount BIGINT,
currency VARCHAR(3) DEFAULT 'USD',
card_brand VARCHAR(20),
card_last4 VARCHAR(4),
card_data_source VARCHAR(20),
industry_type VARCHAR(4),
approval_code VARCHAR(20),
response_code VARCHAR(10),
response_message VARCHAR(255),
transit_txn_id VARCHAR(64),
token_id VARCHAR(64),
avs_result VARCHAR(4),
cvv_result VARCHAR(4),
external_ref_id VARCHAR(64),
parent_txn_id UUID REFERENCES transactions(id), -- for captures, refunds
metadata JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
tip_amount BIGINT DEFAULT 0,
surcharge_amount BIGINT DEFAULT 0,
cash_discount_amount BIGINT DEFAULT 0,
l2_data JSONB,
l3_data JSONB,
enrichment_status VARCHAR(20), -- PENDING, APPLIED, SKIPPED_PIN_DEBIT, FAILED
is_pin_debit BOOLEAN DEFAULT false,
settled_at TIMESTAMPTZ
);

CREATE INDEX idx_transactions_merchant ON transactions(merchant_id, created_at DESC);
CREATE INDEX idx_transactions_status ON transactions(status);
CREATE INDEX idx_transactions_external_ref ON transactions(external_ref_id);
CREATE INDEX idx_transactions_transit_txn ON transactions(transit_txn_id);
CREATE INDEX idx_transactions_enrichment ON transactions(enrichment_status) WHERE enrichment_status = 'PENDING';

CREATE TABLE batch_settlement_config (
merchant_id VARCHAR(64) PRIMARY KEY,
settlement_mode VARCHAR(20) NOT NULL DEFAULT 'SCHEDULED',
scheduled_time TIME NOT NULL DEFAULT '22:30',
timezone VARCHAR(50) NOT NULL DEFAULT 'America/New_York',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE subscriptions (
id VARCHAR(64) PRIMARY KEY,
merchant_id VARCHAR(64) NOT NULL,
customer_id VARCHAR(128) NOT NULL,
token_id VARCHAR(64) NOT NULL,
plan_name VARCHAR(255),
amount BIGINT NOT NULL,
currency VARCHAR(3) DEFAULT 'USD',
interval VARCHAR(20) NOT NULL, -- DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY
interval_count INTEGER NOT NULL DEFAULT 1,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
-- ACTIVE, PAUSED, PAST_DUE, SUSPENDED, CANCELLING, CANCELLED, COMPLETED
current_cycle INTEGER NOT NULL DEFAULT 0,
next_billing_date DATE NOT NULL,
last_billing_date DATE,
last_billing_status VARCHAR(20), -- SUCCESS, DECLINED, ERROR
start_date DATE NOT NULL,
end_date DATE,
max_cycles INTEGER,
failed_attempts INTEGER NOT NULL DEFAULT 0,
cancellation_reason VARCHAR(500),
scheduled_resume_date DATE,
metadata JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_subscriptions_merchant ON subscriptions(merchant_id);
CREATE INDEX idx_subscriptions_customer ON subscriptions(merchant_id, customer_id);
CREATE INDEX idx_subscriptions_billing ON subscriptions(status, next_billing_date)
WHERE status IN ('ACTIVE', 'PAST_DUE');

CREATE TABLE subscription_billing_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
subscription_id VARCHAR(64) NOT NULL REFERENCES subscriptions(id),
cycle INTEGER NOT NULL,
transaction_id VARCHAR(64),
amount BIGINT NOT NULL,
status VARCHAR(20) NOT NULL, -- SUCCESS, DECLINED, ERROR, RETRY_SCHEDULED
failure_reason VARCHAR(500),
retry_count INTEGER NOT NULL DEFAULT 0,
next_retry_at TIMESTAMPTZ,
billed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_billing_events_sub ON subscription_billing_events(subscription_id, cycle DESC);

CREATE TABLE settlement_batches (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
merchant_id VARCHAR(64) NOT NULL,
batch_date DATE NOT NULL,
total_sales BIGINT NOT NULL DEFAULT 0,
total_refunds BIGINT NOT NULL DEFAULT 0,
net_amount BIGINT NOT NULL DEFAULT 0,
txn_count INTEGER NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL, -- PENDING, RECONCILED, DISCREPANCY
reconciled_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

7. Pub/Sub Events

TopicEventPayload
transaction-eventstransaction.approvedFull transaction object
transaction-eventstransaction.declinedFull transaction object
transaction-eventstransaction.voidedTransaction ID, voided timestamp
transaction-eventstransaction.refundedTransaction ID, refund amount
transaction-eventstransaction.settledBatch ID, settlement details
subscription-eventssubscription.createdSubscription object
subscription-eventssubscription.billedSubscription ID, cycle, transaction ID
subscription-eventssubscription.payment_failedSubscription ID, cycle, failure reason
subscription-eventssubscription.past_dueSubscription ID, failed attempts count
subscription-eventssubscription.suspendedSubscription ID, suspension reason
subscription-eventssubscription.cancelledSubscription ID, cancellation reason
subscription-eventssubscription.pausedSubscription ID, resume date
subscription-eventssubscription.resumedSubscription ID, next billing date
webhook-deliverywebhook.sendWebhook URL, payload, merchant config

8. Configuration

# application.yml
spring:
application:
name: processing-service
datasource:
url: jdbc:postgresql://${DB_HOST}:5432/gateway
username: ${DB_USER}
password: ${DB_PASSWORD}

transit:
base-url: ${TRANSIT_BASE_URL}
device-id: ${TRANSIT_DEVICE_ID} # from Secret Manager
transaction-key: ${TRANSIT_TXN_KEY} # from Secret Manager
timeout-ms: 30000
retry:
max-attempts: 3
initial-delay-ms: 1000
multiplier: 2.0

server:
port: 8080

9. Health Check

GET /actuator/health

Response:
{
"status": "UP",
"components": {
"db": { "status": "UP" },
"transitApi": { "status": "UP", "latency": "120ms" }
}
}