HTTP Client
Call customer-specific REST services through the managed, thread-safe HTTP client.
The SDK HTTP client allows Groovy scripts to communicate with customer-specific non-FHIR REST services without exposing unrestricted networking APIs to the script runtime. Typical uses include external configuration, workflow APIs, audit/event services, operational notifications, and JSON integrations.
Use FhirClient for FHIR server operations. Use ScriptHttpClient for customer-specific non-FHIR REST integrations.
Injection and client lifecycle
ScriptHttpClient instances are thread-safe. Create each client once during onStartup() and reuse it across concurrent hook executions rather than creating a new client for every message.
@ScriptProperty("configHttp")
Map<String, Object> configHttpProps
@InjectHttp
ScriptHttpClientFactory httpClientFactory
ScriptHttpClient configClient
@Override
void onStartup() {
configClient = httpClientFactory.create(
"config",
configHttpProps
)
}
The client name is used for diagnostics, logs, and exception messages. Prefer stable names such as config, notifications, audit, or workflow-api.
Configuration
Property-based configuration is recommended for normal deployments because endpoint, authentication, and timeout settings remain outside the Groovy script.
configHttp=[baseUrl:"https://config.example.org", authType:"bearer", token:"${CONFIG_API_TOKEN}", connectTimeoutMs:3000, requestTimeoutMs:10000, maxResponseBytes:1048576]
| Property | Meaning |
|---|---|
baseUrl / url / server | Target REST service base URL. |
authType | none, basic, or bearer. |
username / login | Basic authentication username. |
password / pass | Basic authentication password. |
token / bearerToken | Bearer token. |
connectTimeoutMs | Connection timeout in milliseconds. |
requestTimeoutMs | Complete request timeout in milliseconds. |
maxResponseBytes | Maximum accepted response body size in bytes. |
The factory validates configuration when the client is created. Missing base URLs or credentials required by the selected authentication type result in ScriptHttpException. A programmatic configuration builder is also available when values must be derived during script initialization.
Synchronous GET
Use synchronous GET when the current workflow depends on data returned by the external service. Request paths are resolved relative to the configured base URL.
def response = configClient.get(
"/api/config",
[
hospital: "HOSPITAL_A",
environment: "production"
]
)
if (!response.isSuccessful()) {
LOG.warn("HTTP {}: {}", response.getStatusCode(), response.getBody())
}
Query parameters are URL-encoded automatically. Iterable and array values are sent as repeated query parameters.
Synchronous JSON POST
Use postJson() when the current script execution depends on the result of an external REST operation. The payload is serialized to JSON by the platform.
def response = configClient.postJson(
"/api/workflow/events",
[
messageId: envelope.getId(),
eventType: "${envelope.getMessageCode()}^${envelope.getTriggerEvent()}"
]
)
if (!response.isSuccessful()) {
LOG.warn("Workflow service returned HTTP {}", response.getStatusCode())
}
Synchronous requests block the current script execution until the response is received or the configured request timeout is reached.
Asynchronous notifications
Use postJsonAsync() for best-effort notifications that should not block message processing—for example audit events, operational notifications, processing diagnostics, or non-critical workflow events.
configClient.postJsonAsync(
"/api/notifications",
payload
) { response, error ->
if (error != null) {
LOG.warn("Notification failed", error)
} else if (!response.isSuccessful()) {
LOG.warn("Notification returned HTTP {}", response.getStatusCode())
}
}
The original script flow continues before an asynchronous callback completes. Callback failures do not change the original processing result, and callbacks may run concurrently with other message processing. Keep callback logic limited to lightweight logging, metrics, or independent notification-result handling.
Authentication
# No authentication
configHttp=[baseUrl:"https://service.example.org", authType:"none"]
# Basic authentication
configHttp=[baseUrl:"https://service.example.org", authType:"basic", username:"integration-user", password:"${SERVICE_PASSWORD}"]
# Bearer authentication
configHttp=[baseUrl:"https://service.example.org", authType:"bearer", token:"${SERVICE_TOKEN}"]
Use environment-variable placeholders or the deployment environment's secret-management mechanism for credentials. Do not hardcode passwords or tokens in Groovy scripts or commit them to source control.
Response handling
A completed HTTP exchange returns ScriptHttpResponse, including HTTP 4xx and 5xx responses. Always inspect the status explicitly when service behavior matters.
| Method | Purpose |
|---|---|
getStatusCode() | Returns the HTTP status code. |
getBody() | Returns the response body decoded as UTF-8. |
getHeaders() | Returns read-only response headers. |
isSuccessful() | Returns true for HTTP 200–299. |
Error handling
HTTP 4xx and 5xx responses are completed exchanges and do not raise ScriptHttpException. The exception is used for failures that prevent a usable response from being returned.
| Condition | Recommended handling |
|---|---|
| HTTP 2xx | Normal success path. |
| HTTP 4xx | Usually a request, authorization, or business-rule problem; handle explicitly and normally do not retry automatically. |
| HTTP 5xx | External service failure; retry only when the operation is safe to repeat. |
| Connection failure / timeout | Raises ScriptHttpException; may be retryable depending on workflow semantics. |
| Invalid configuration / JSON serialization / request construction | Correct the problem; do not retry until fixed. |
Response exceeds maxResponseBytes | Raises ScriptHttpException; review service response and configured limit. |
Retry
Each HTTP client method performs exactly one request attempt. Apply retries explicitly through ScriptSupport and only when the operation is safe to repeat.
def response = support.withRetries(
"load-external-config",
[2000L, 2000L]
) {
configClient.get(
"/api/config",
[environment: "production"]
)
}
Best practices
- Use synchronous calls only when the current workflow requires the response.
- Keep connection and request timeouts bounded; long blocking calls reduce worker throughput.
- Set
maxResponseBytesaccording to expected response size. - Do not retry most HTTP 4xx responses or non-idempotent operations unless the external API explicitly supports safe retries.
- Never log passwords, bearer tokens, or complete authentication headers.
- Reuse named clients created during
onStartup(); do not construct a new client per message. - Avoid dynamic diagnostic client names containing message IDs, timestamps, or patient identifiers.
