Skip to content

fhir-sfdc-wrapper-lambda

Status: Current | Last reviewed: 2026-09-01

FHIR R4 façade over Salesforce Life Sciences / Health Cloud clinical objects, built for the adverse-event-reporting and appointment-management capabilities (capability packs). The capability speaks standard OAuth 2.0 + FHIR to this API; every Salesforce fact (connected app, JWT Bearer, object/field names) is the Lambda's concern only.

Source

apps/fhir-sfdc-wrapper-lambda

Runtime

Aspect Value
Language Python 3.12 (deps: PyJWT + cryptography for the SF JWT Bearer assertion; everything else stdlib)
Salesforce REST API v67.0 (matches the sandbox; required for current ServiceAppointment fields)
Trigger API Gateway v2 HTTP API ($default route, AWS_PROXY 2.0)
Deploy bash deploy.sh — pure AWS CLI, no Terraform (deliberate)

Endpoints

Route Maps to
POST /oauth/token client-credentials → 15-min HS256 JWT (self-issued and self-verified)
GET /fhir/Patient?name&birthdate&phone Person Account SOQL → FHIR searchset Bundle (id = Account id). phone is an optional format-tolerant filter (digits-tail SOQL narrow + Python suffix compare, so E.164 matches a free-form org copy); the appointment capability uses verified name + birthdate and Customer Authentication remains the access gate
GET /fhir/Appointment?patient&date&status ServiceAppointment SOQL → FHIR searchset Bundle. patient is a Salesforce Account/Contact reference; date supports FHIR eq, ge, gt, le, and lt prefixes; status accepts FHIR Appointment status values
GET /fhir/Appointment/{id} One ServiceAppointment → FHIR Appointment
POST /fhir/AppointmentResponse Validates the patient actor and updates ServiceAppointment status/comments; accepted confirms the appointment and maps to Scheduled
POST /fhir/PaymentNotice Idempotently writes the processor transaction and copay summary to ServiceAppointment, plus a completed Task on the patient timeline
POST /fhir/AdverseEvent AdverseEventEntry + AdverseEventCause (Medication find-or-create) + narrative Task
POST /fhir/AllergyIntolerance AllergyIntolerance + PatientHealthReaction children
POST /fhir/Condition HealthCondition
POST /fhir/Observation CareObservation
POST /fhir/Communication Salesforce Task (decision: no Communication object mapping)
POST /fhir/DocumentReference ContentVersion + ContentDocumentLink. Accepts inline FHIR base64 or an allowlisted s3:// conversation-image reference and can link the same document to both patient and appointment
GET /healthz unauthenticated liveness

Mapping modules are describe-driven: the org's sf sobject describe output is the truth, and each fhir_mapping/*.py docstring records where it diverges from the generic FHIR↔SFDC tables (e.g. AdverseEventEntry has no outcome/narrative field, so outcome + note are preserved as one completed Task — GVP verbatim must not be dropped; PatientHealthReaction requires CausativeSubstanceId, which falls back to the allergy's own code bundle). The Appointment timezone extension uses ServiceAppointment TimeZone, with the deployment's APPOINTMENT_TIMEZONE (currently America/New_York) as a fallback. Appointment start and end remain valid FHIR instants but are rendered with that zone's date-specific UTC offset (11:00:00-04:00, not a separate 15:00:00Z plus an instruction asking the model to convert it). Date-only Appointment searches use midnight boundaries in the same local zone before converting the SOQL filter to UTC, including daylight-saving changes.

Secrets (Secrets Manager, us-west-2)

Secret Holds
fhir-sfdc-wrapper/salesforce {client_id, username, login_url, private_key} for the FHIR SFDC Wrapper connected app in the hlsorg sandbox (JWT Bearer)
fhir-sfdc-wrapper/auth {signing_secret, clients} — the /oauth/token client registry

The Lambda role reads exactly these two ARNs and has s3:GetObject only for conversations/*/images/* in the configured attachment bucket. ATTACHMENT_BUCKETS is the comma-separated source allowlist. Env fallback (SF_CLIENT_ID/SF_USERNAME/SF_LOGIN_URL/SF_PRIVATE_KEY(_PATH), WRAPPER_SIGNING_SECRET/WRAPPER_CLIENTS) serves the local runner.

Vanguard

cd apps/fhir-sfdc-wrapper-lambda
python3 -m pytest tests -q            # SFDC_MODE=mock, no AWS/SF needed
SFDC_MODE=mock WRAPPER_SIGNING_SECRET=dev WRAPPER_CLIENTS=dev:dev \
  python3 handler.py --serve 8790     # local HTTP bridge

Production / dev deployment

Deployed 2026-08-28 to account 465572510980 (us-west-2): https://6v89u1v0r6.execute-api.us-west-2.amazonaws.com. The platform side is one oauth2_api integration (base_url + token_url + client id/secret) that the capability's config references — see capability packs § findings.

Appointment confirmation workflow

Agent 3032 uses the appointment-management capability. After customer authentication it resolves the patient, queries real appointments, records the FHIR AppointmentResponse, sends the captured photo's object-storage reference to DocumentReference, collects the copay, and sends the successful processor transaction to PaymentNotice. The model receives only the compact attachment reference. Conversation-api stores the photo in its trusted recordings bucket and returns the object key, bucket, s3:// URI, and S3 ARN; the capability passes that exact URI to the allowlisted Lambda. Image bytes travel directly from S3 to the Lambda and Salesforce.

The capability manifest and topic prompt are source-controlled beside the Lambda. The seeded demo appointment remains pending until the call so that the confirmation is the state-changing test, not deployment verification. The photo-capture step accepts the catalog's current submit_photo action as well as photos_captured and the legacy upload_headshot name; all three must resolve to the object-store metadata before DocumentReference is called. For agent 3032, the draft also has an Agent Logic tool_result rule on request_salesforce_approval that calls stream_transcript_to_supervisor. This makes the configured Human Supervision stream deterministic rather than depending on a second model-selected tool call.

The agent-specific runtime overlay is source-controlled as agent-3032-runtime-overlay.json. For this dedicated Erik Golden demo draft it uses a variable_changed rule on the authoritative customer_auth_status variable to chain the Patient and Appointment reads immediately when passkey verification completes. This covers out-of-band verification without depending on the model to call get_customer_auth_status again. It captures the returned appointment fields into session variables and prevents the general A2UI tool from rendering an appointment card while the FHIR Appointment id is empty. A native-voice response guard also interrupts any spoken appointment claim made before that id exists. This is intentionally stronger than prompt wording because Nova copied example values from the AppointmentSummary component during the 2026-08-31 21:21 UTC test without calling either FHIR read.

Capability tools execute through the manual-API runtime, whose result shape is {status, data}. Tool-logic captures therefore address $.data... (for example $.data.entry[0].resource.id), not the raw FHIR body at $.entry.... Using the raw-body path lets the Patient request succeed but leaves the bound Patient id empty, correctly preventing the Appointment request from running.

Seeded hlsorg demo records

Record Salesforce id Test state
Erik Golden Patient Account 001Va0000131yJFIAY Person Account with shadow Contact 003Va00000s2KefIAE
ServiceAppointment SA-0001 08pVa0000014sAvIAI pending; 2026-09-03 11:00–11:30 America/New_York; 25 USD copay

Do not pre-confirm or attach a placeholder image to this record: the end-to-end call is expected to create those state changes. PaymentNotice is idempotent on the processor transaction id so a successful tool retry does not duplicate the patient timeline Task.

devsdo Case live view (follow-on)

The wrapper's read endpoints are ready for a Case-page LWC, but that cross-org UI is not deployed as part of this Lambda. The safe shape is an Apex callout via a Salesforce Named/External Credential, not a browser call carrying an OAuth client secret. Store only the FHIR Patient and Appointment references on the Case; the Apex controller should return a minimal appointment DTO and the LWC should fetch on demand. Do not copy headshot bytes, card data, or broader clinical history into the service org. This preserves devsdo as the call/Case system while hlsorg remains the clinical system of record.

See also