Skip to main content
Technical Preview

Overview

ServiceMax (part of PTC) is field service management for equipment makers and operators. ServiceMax Core is a managed package (SVMXC__ objects) and Asset 360 (SVMXA360__) builds on Salesforce Field Service; both run inside your Salesforce org. This connector lets the broker open a ServiceMax work order when a machine raises an alarm, put run hours and other telemetry on the installed product, and show each technician’s open work orders, so service teams see what the machine sees. The template has two connectors:
  • ServiceMaxApi (ServicemaxRestApi.lot), a client connector on your org’s My Domain with 26 events: 10 writes, 14 reads and 2 token events. It has no DELETE events. Events starting with A360 need Asset 360; the others need ServiceMax Core.
  • ServiceMaxNotificationReceiver (cmms_servicemax_webhook.lot), an optional server connector on port 9011 that receives JSON callouts from a Flow or Apex in your org and publishes them on MQTT.
ServiceMax publishes no OpenAPI or Postman file of its own: its objects are ordinary Salesforce sObjects, read and written with the Salesforce REST API. The events were picked by hand from the PTC data dictionaries (Work Order, Installed Product, Technician/Equipment) and the Salesforce REST API Developer Guide.

Events

Replies arrive on the same topic with /response appended. The SOQL behind the fixed query events:
  • ListOpenWorkOrdersByTechnician:
  • ListOpenWorkOrdersByInstalledProduct:
  • FindInstalledProductBySerial:
  • ListTechnicians:
  • A360ListOpenWorkOrdersByAsset:

Prerequisites

  • A Salesforce org with ServiceMax Core installed (and Asset 360 for the A360 events).
  • An External Client App (or Connected App) with OAuth enabled. For server-to-server use enable the client credentials flow and set a Run As integration user that has the ServiceMax permission sets and access to the objects and fields you write.
  • Your org’s My Domain URL, for example https://acme.my.salesforce.com (sandbox https://acme--uat.sandbox.my.salesforce.com). Change BASE_ADDRESS to it. login.salesforce.com and test.salesforce.com only issue tokens; REST calls on them fail with URL_NOT_RESET.
  • Outbound HTTPS from the broker to that host.
  • For notifications only: a public HTTPS URL that forwards to the broker’s port 9011.

Authentication

The consumer secret and every access token act as the Run As user. Store the consumer key and secret in the Hub secrets SALESFORCE_CLIENT_ID and SALESFORCE_CLIENT_SECRET, read them with GET SECRET, and never put them or a token in a connector file or a retained topic.
GetToken posts a form body to /services/oauth2/token on your My Domain. With the client credentials flow:
The reply carries access_token and instance_url. Every other event sends Authorization: Bearer {value.json.access_token}, so each payload carries the token:
The same event works for the JWT bearer flow (grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<signed JWT>) and refresh tokens. Tokens expire with the org’s session timeout; on 401 INVALID_SESSION_ID call GetToken again. The example actions refresh it every hour. Answers seen from the real Salesforce hosts without valid credentials: a fake client returns 400 {"error":"invalid_client_id"}, a fake JWT 400 invalid_grant, and any REST call with a fake token 401 [{"errorCode":"INVALID_SESSION_ID"}].

Payload conventions

  • access_token: the token from GetToken.
  • id: an 18-character Salesforce record Id (work order a2x..., installed product, technician, Asset), or the query locator for QueryMore.
  • query: fields=Id,Name,... for the get events, or "" for all fields.
  • q: a whole SOQL statement, URL-encoded (spaces as +).
  • technician_id, installed_product_id, serial, asset_id: values the fixed queries put inside SOQL quotes.
  • body: the JSON record, sent unchanged on POST and PATCH events.
Creates answer 201 with {"id": "...", "success": true, "errors": []}; updates answer an empty 204. Errors are JSON arrays such as [{"errorCode": "REQUIRED_FIELD_MISSING", "message": "...", "fields": [...]}].
technician_id, installed_product_id, serial and asset_id are inserted into SOQL as they are. Pass Salesforce Ids or serial numbers that come from your own system, never raw user text with quotes in it.
Create a work order from an alarm. SVMXC__Component__c is the installed product, SVMXC__Company__c the account:
Close it. SVMXC__Order_Status__c, SVMXC__Priority__c and SVMXC__Order_Type__c are picklists that each org configures, so read the allowed values with DescribeWorkOrder first:
To avoid one work order per repeated alarm, add a custom External ID field (for example Coreflux_Alarm_Id__c) to the work order and use UpsertWorkOrderByExternalId, or check ListOpenWorkOrdersByInstalledProduct first.

Telemetry

ServiceMax Core has no standard telemetry field on SVMXC__Installed_Product__c. Add the custom fields you need (for example Run_Hours__c and Last_Telemetry_At__c) and write them with UpdateInstalledProduct, or for many machines at once with UpdateRecordsBatch (up to 200 records per call). Map a machine’s serial number to its record with FindInstalledProductBySerial.
On Asset 360, send readings as technical attributes instead. A360SaveTechnicalAttributes creates missing attributes, records the time of each reading and finds the Asset by Id, name or an identifier field (200 entries per call):

Notification receiver

Deploy cmms_servicemax_webhook.lot only if you want the org to push changes to the broker. It opens HTTP port 9011. Salesforce has no built-in JSON webhook. Build a record-triggered Flow with an HTTP Callout action (or an Apex callout) on SVMXC__Service_Order__c or SVMXC__Installed_Product__c, point a Named Credential at the public URL of the matching path, and send the fields you need as JSON.
The receiver connector does not authenticate callers. Put a reverse proxy with a public TLS certificate in front of port 9011 and check a shared secret header that the Named Credential sends. Outbound Messages are SOAP and expect an <Ack>true</Ack> reply, which this connector does not send, so they are not supported.

Connector

Notification receiver (optional):

Example actions

These actions refresh the token every hour, open a work order when a compressor alarm is raised, write run hours to the installed product, and poll one technician’s open work orders.

Notes

  • The connector pins API v65.0. Change the version in the endpoints to move up; ListApiVersions shows what your org serves.
  • “Open” in the fixed queries means SVMXC__Closed_On__c is empty. If your org cancels work orders without a close date, use Query with your own status filter.
  • Every call counts against the org’s rolling 24-hour API allocation (GetLimits shows DailyApiRequests). Over it, Salesforce answers 403 REQUEST_LIMIT_EXCEEDED. Poll every few minutes, not per message.
  • Queries return up to 2000 rows per page; when done is false, pass the end of nextRecordsUrl to QueryMore.
  • Writes fire the org’s ServiceMax triggers, flows, SLA clocks and dispatch rules.
  • PublishPlatformEvent needs a platform event you define (for example Coreflux_Telemetry__e) that Flows or Apex triggers subscribe to.

Not covered

Deletes, work detail lines and parts, ServiceMax SFM and dispatch services, Outbound Messages, Pub/Sub API and streaming subscriptions, Bulk API, files, and Asset 360 maintenance plans, warranties and service contracts. All are reachable through the Salesforce APIs and can be added to a fork.

Status

Technical preview, blocked. The template passes 363 of 363 smoke checks: static checks of both connectors against the PTC data dictionaries and Salesforce REST docs, every event against a local mock of the Salesforce REST API, negative controls, and calls to real Salesforce hosts without valid credentials (the token endpoint rejects fake clients, and every packaged call with a fake token stops at 401 INVALID_SESSION_ID). Salesforce checks the token before it connectors a request, so those calls cannot prove that the ServiceMax objects exist in an org. No call has run yet against a real ServiceMax org. To finish testing, use a sandbox with ServiceMax Core and an External Client App with the client credentials flow: set SALESFORCE_INSTANCE_URL, SALESFORCE_CLIENT_ID, SALESFORCE_CLIENT_SECRET and SERVICEMAX_TEST_INSTALLED_PRODUCT (and optionally SERVICEMAX_TEST_IP_FIELD), then run SERVICEMAX_ALLOW_WRITE=1 python3 smoke_servicemax.py. It reads limits, both describes and the technicians, creates a work order called “Coreflux smoke test (safe to delete)”, reads it back, lists open work orders on the installed product and writes one installed product field. Then deploy the connector on a broker and do an MQTT round trip.
Last modified on October 21, 2026