Postman pre-request framework / controller for delivering documents asynchronously to SAP Cloud Integration Suite (CPI), considering different integration aspects for pushing interfaces / clients:
- OAuth token handling
- Full request snapshot queue
- Idempotency via execution_history
- Retry processing
- Error logging
- Mutex protection against rapid duplicate execution
- Postman variable substitution in URL and body
- Generated message GUID, SAP GUID, and timestamp variables
- Canonical payload MD5 hashing with configurable dynamic-field masking
- Duplicate suppression for successful and pending payloads
- Manual cancellation of retry messages
- Forced delivery mode that bypasses quality-of-service checks
- OAuth token caching, expiry handling, and diagnostics
- Retry counters and queue-size tracking
The controller is intended for a client-to-CPI integration where CPI accepts a document, places it on a JMS queue, and returns an HTTP status code, usually 202. It adds client-side delivery protection and diagnostics around the CPI endpoint.
Client / Postman
|
| OAuth client-credentials token
v
SAP CPI HTTPS endpoint
|
| Validate, correlate, enqueue
v
CPI JMS queue / integration flow
|
v
SAP ERP processing
The Postman controller is src/pre_request.js.
GitHub Copilot repository instructions are in .github/copilot-instructions.md. Extended AI-agent guidance is in AGENTS.md.
The current development server is:
https://<cpi-rt-host>
The resource path is:
/http/<path>
Together, the request URL is:
https://<cpi-rt-host>/http/<path>
The API is asynchronous. A successful 202 Accepted means CPI accepted the message for queue processing; it does not mean that the downstream ERP posting has completed.
The framework currently implements a client-side ExactlyOnceInOrder policy. It uses one FIFO pending_messages queue, processes one queued message at a time, retries in queue order, and suppresses duplicates using the message GUID and canonical payload MD5.
Every CPI delivery includes:
SapQualityOfService: ExactlyOnceInOrder
This is a delivery contract between the client and CPI, not proof of end-to-end EOIO. SAP CPI does not currently provide full EOIO semantics for this integration. The framework cannot guarantee mathematical end-to-end exactly-once processing by itself: CPI/JMS and SAP ERP must honor the header, preserve correlation, and apply idempotent business processing. A lost response after CPI acceptance remains an ambiguity that must be handled by CPI/ERP idempotency.
- Create or import the CPI POST request in Postman.
- Copy the complete contents of
src/pre_request.jsinto the request, folder, or collection Pre-request Script tab. - Configure the active Postman environment with the variables below.
- Set the request body to the JSON payload expected by CPI.
- Use
pm_log_level=historyfor normal operation orpm_log_level=verbosewhile diagnosing a delivery.
| Variable | Purpose |
|---|---|
baseUrl |
CPI host, for example https://<cpi-rt-host> |
token_url |
OAuth token endpoint |
client_id |
OAuth client ID |
client_secret |
OAuth client secret |
The request URL should include the complete CPI path, for example:
{{baseUrl}}/http/<path>
| Variable | Default / example | Purpose |
|---|---|---|
pm_log_level |
history |
history keeps output compact; verbose prints request/response diagnostics |
dynamic_json_paths |
MessageHeader.UUID, Record_Data.item.alloc_nmbr |
JSON paths masked before the idempotency hash is calculated |
cpi_host |
<cpihost> |
CPI monitoring-link host |
erp_host |
<erphost> |
ERP monitoring-link host |
erp_client |
<erpclient> |
ERP client used in the monitoring link |
manual_cancel_msgid |
empty | Cancels a currently queued retry message by GUID |
manual_forced_sent |
false |
Sends the current message while bypassing duplicate/QoS checks |
The controller generates these local variables for the request body:
| Variable | Meaning |
|---|---|
my_guid |
Generated UUID with hyphens |
sap_guid |
Generated UUID without hyphens, uppercase |
ts |
Current UTC ISO timestamp |
Example body references:
{
"MessageHeader": {
"UUID": "{{my_guid}}"
},
"Record_Data": [
{
"item": [
{
"alloc_nmbr": "{{ts}}"
}
]
}
]
}The controller performs these operations for each execution:
- Resolves Postman variables in the URL and raw request body.
- Generates a message GUID, SAP GUID, and timestamp.
- Gets or refreshes an OAuth client-credentials token.
- Canonicalizes the JSON payload and calculates an MD5 idempotency hash.
- Processes queued messages before the current main request.
- Sends the current request through
pm.sendRequest(). - Stores failed deliveries as retryable queue entries.
- Records execution history in the Postman environment.
- Prints a compact execution table and CPI/ERP monitoring links.
- Registers successful, failed, and skipped Postman test results.
History is stored in the environment variable execution_history as JSON. Supported status types are:
SUCCESS: final successful delivery; the same hash is skipped later.RETRY: delivery failed and the message is in the retry queue.ERROR: treated as a retryable blocking failure.CANCELLED: a queued retry was manually cancelled.SKIPPED: duplicate or otherwise suppressed delivery.
The pending queue is stored in pending_messages as JSON. Each queued item contains the message GUID, payload hash, retry position, queue size, request method, headers, body, and URL metadata.
The actual URL used for a main delivery or retry is resolved from the current Postman request at send time. This allows a changed baseUrl or CPI path configuration to affect retries; the URL in the snapshot is retained as historical metadata.
The controller sorts JSON object keys and masks dynamic fields before hashing. By default it masks:
MessageHeader.UUID
Record_Data.item.alloc_nmbr
This prevents a new GUID or timestamp from making an otherwise identical payload look new. Add or replace paths with dynamic_json_paths when other fields are generated dynamically.
Duplicate handling is intentionally conservative:
- A successful hash is skipped.
- A hash already pending in the queue is skipped.
- A retry or error remains blocking until it succeeds or is manually cancelled.
- A forced send bypasses these quality-of-service checks for the current message.
Set manual_cancel_msgid to the GUID of a message currently in pending_messages. Only messages with RETRY or ERROR history can be cancelled. Invalid cancellation attempts throw an error rather than silently changing state.
Set manual_forced_sent=true for one execution. The flag is cleared by the script at startup. The current message bypasses duplicate and queue checks, and its success description records that it was forced. A forced failure is still queued for later retry.
Normal logging prints the execution history table. Set pm_log_level=verbose to print:
- Outgoing URL, method, headers, and body.
- HTTP status, response headers, and response body.
- Transport errors.
- Retry position and queue size.
The controller uses pm.test() and pm.test.skip() directly. It does not rewrite pm.response: requests initiated with pm.sendRequest() do not populate Postman’s native response panel. This is required to catch both HTTP failures and transport failures before queuing them.
- Treat
202 Acceptedas queue acceptance, not business completion. - Preserve the request GUID through CPI and ERP for end-to-end correlation.
- Return the correlation header
SapMessageIdExwhere available. - Keep CPI endpoint paths stable and environment-specific through
baseUrl. - Ensure OAuth scopes allow the CPI integration flow and keep client secrets out of collections and source control.
- Make CPI validation errors distinguishable from transport or platform errors.
- Configure CPI/JMS retry and dead-letter behavior separately from this client-side retry queue.
- Avoid creating duplicate business documents in CPI when a client retry repeats an accepted message.
- Monitor CPI message processing and ERP posting independently; an HTTP success does not prove ERP success.
- Align timeout, retry delay, queue retention, and idempotency policy with CPI operational limits.
Validate the JavaScript syntax with:
node --check src/pre_request.jsDo not commit real OAuth client secrets, access tokens, environment exports, execution history, or pending queue contents. Use Postman environments, secret variables, a CI secret store, or a vault for credentials.