PTI Web Service Re-implementation Spec (CT Market ↔ Nedbank)¶
1. Purpose¶
Re-implement the CT Market receiving endpoint for Nedbank Provisional Transaction Information (PTI) delivery via SOAP web service (TIWebDistribution), behind the existing reverse proxy infrastructure, so that Nedbank can push real-time PTI notifications again.
This document is intended to be implementation-ready: a developer can build and deploy the service using the steps below.
2. Scope¶
In scope¶
- PTI inbound delivery receiver (SOAP over HTTP).
- Correct SOAP response/acknowledgement per contract.
- Decode and persist payloads (raw SOAP + decoded content).
- Idempotency/deduplication and basic operational monitoring.
- QA + PROD endpoint separation.
- Certificate/mTLS handling at Nginx (application stays HTTP internally).
Out of scope (for initial MVP)¶
- Full reconciliation logic between PTI and FTI (can be Phase 2+).
- FTI file ingestion pipeline (can be Phase 2+).
- Mapping tables for FTI TransactionCodes → PTI channel/type strings (Phase 2+).
3. User story / motivator¶
CT Market requires a near real-time view of movements on subscribed accounts to improve customer experience and operational monitoring, integrated into LOB systems. PTI provides provisional events; where needed, FTI is used for end-of-day/final verification.
Important constraints confirmed by Nedbank:
- PTI TransactionKey and FTI TransactionCode do not map directly.
- Common reference point is the actual reference number.
- PTI is provisional and does not include items already in the final run (e.g., charges, fees, debit orders), which appear in FTI.
- TI platform supports Current and Savings accounts. Call account support is pending confirmation.
4. Known identifiers & accounts¶
Profile / instance¶
- Profile No: 4000005683
- Instance: 1
- Protocol: WEB
Accounts seen in historical PTI payloads¶
- 1498086543
- 1498086640
Call account (must be included if product supports it)¶
- 037186068324 (historical request)
NOTE: Current/Savings limitation may block Call account PTI; must be confirmed with Nedbank.
5. Architecture¶
High-level data flow¶
- Nedbank/DataPower sends SOAP POST to CT Market public endpoint.
- Exneelo reverse proxy (Nginx) terminates TLS (and optionally mTLS) and forwards to internal Python service over WireGuard.
- Python service parses SOAP, extracts header + body, decodes payload, stores it, returns SOAP ACK.
- Nginx returns ACK to Nedbank.
Components¶
- Public reverse proxy: Exneelo VM (
reverse-proxy.co.za) running Nginx + certbot + WireGuard - App runtime: Proxmox container (Linux) running Python service (FastAPI recommended)
- Storage: PostgreSQL recommended (SQLite acceptable for MVP)
6. Public endpoints (must be stable)¶
PROD¶
https://nedbank.banking.ctmarket.co.za/zoap/ctm/server
QA¶
https://nedbank.banking.ctmarket.co.za/zoap/ctm-qa/server
Optional comms check¶
https://nedbank.banking.ctmarket.co.za/test/com_check.php- Returns
OK(200) to validate routing/TLS/mTLS independently of SOAP.
7. Certificates & TLS/mTLS requirements¶
Principle¶
- TLS/mTLS handled by Nginx.
- Python service listens on HTTP internally.
Items to confirm from Nedbank (must be obtained in the new onboarding pack)¶
- Whether Nedbank requires mutual TLS (client certificate authentication).
- If yes: CA bundle for verifying Nedbank client certs.
- Whether Nedbank pins/imports our server certificate or trusts public CA chain.
- Required certificate generation rules (key type, key size, SANs, validity, EKU, CSR format).
Recommended operational posture¶
- Prefer Nedbank trusting issuer chain rather than leaf cert pinning.
- If leaf pinning is unavoidable, use longer-lived cert + explicit renewal runbook.
Monitoring (non-negotiable)¶
- Alert 30/14/7 days before cert expiry (for all relevant certs).
8. Contract: WSDL/XSD¶
Files (primary sources)¶
TIWebDistribution_2013-11-01.wsdlTIWebDistribution_2013-11-01.xsdEnterpriseContext_2008-09.wsdlEnterpriseContext_2008-09.xsd
Namespaces¶
- SOAP envelope:
http://schemas.xmlsoap.org/soap/envelope/ - EnterpriseContext:
http://contracts.it.nednet.co.za/Infrastructure/2008/09/EnterpriseContext - TIWebDistribution:
http://contracts.it.nednet.co.za/services/business-execution/2013-11-01/TIWebDistribution
Operation¶
- PortType:
ITIWebDistribution - Operation:
DistributeMsg
SOAPAction (as per WSDL)¶
http://contracts.it.nednet.co.za/services/business-execution/2013-11-01/TIWebDistribution/DistributeMsg
Request payload (body)¶
DistributeMsgRqcontainingContent:SecurityProxyType(string)DestinationKey(long)Format(string)TransformedData(string; historically base64-encoded CSV)
Response payload (body)¶
DistributeMsgRscontaining:ResultCode(string, max length 3)
SOAP header¶
- EnterpriseContext is provided in SOAP header on request and expected on response.
- Fields may be required by schema; echoing back the received header is the safest choice.
9. Request/response handling requirements¶
HTTP requirements¶
- Method:
POST - Content-Type: typically
text/xml; charset=utf-8(accept alsoapplication/soap+xmlif encountered) - SOAPAction header: forward/accept; do not rely on it exclusively.
Response requirements¶
- Return HTTP 200 with a SOAP envelope.
- Include EnterpriseContext header (echoed).
- Body includes
DistributeMsgRswith successResultCode.
ResultCode¶
- Historical example observed:
XR00. - MUST confirm with Nedbank whether success is
XR00,R00, or other.
Error behavior¶
- Prefer returning a valid SOAP response even on application-level errors (with a failure ResultCode) rather than timing out.
- Avoid 500/timeouts (DataPower will retry; uncontrolled retries cause duplication).
10. Data handling and persistence¶
What to store per message¶
Minimum fields:
received_at(UTC)environment(qa/prod)request_id(generated UUID)source_ip(from X-Forwarded-For)soap_actionenterprise_context_xml(raw header snippet)destination_keyformattransformed_data_rawtransformed_data_decoded(if decodable)raw_soap_xml(full request)fingerprint_hash(for dedupe)result_code_returned
Optional but useful:
- Correlation IDs / Activity IDs if present in headers.
Decoding TransformedData¶
- If
Format == CSV: attempt Base64 decode → UTF-8. - Store both raw and decoded; never discard the raw payload.
- Handle multi-line CSV payloads (if ever sent) by splitting on newline.
11. Idempotency / deduplication¶
Why¶
Nedbank may resend the same message on network failures or non-200 responses. The service must not double-process.
Strategy¶
- Compute
fingerprint_hashas SHA-256 over a stable concatenation, e.g.: DestinationKey | Format | TransformedData(raw) | (optional) EnterpriseContext IDs- If the same fingerprint arrives again within a dedupe window (e.g., 7 days), treat as duplicate:
- Store as duplicate event (optional) but do not re-run downstream effects.
- Return success ACK again.
Note: Nedbank indicated that repeating the same PTI transaction key usually indicates duplication; if PTI transaction key is present in decoded CSV, incorporate it.
12. Observability¶
Logs¶
Log each request with:
- request_id
- env
- remote ip
- http status
- response result_code
- fingerprint
Do NOT log secrets.
Metrics (minimal)¶
- count_received_total{env}
- count_duplicates_total{env}
- count_decode_fail_total{env}
- last_message_received_timestamp{env}
Alerts¶
- No messages received for X hours during business hours (env-specific)
- Cert expiry alerts (30/14/7 days)
13. Nginx reverse proxy requirements¶
Required header forwarding¶
SOAPActionContent-TypeHostX-Forwarded-ForX-Request-Id
Timeouts and size¶
- proxy_read_timeout: 30s
- client_max_body_size: 5m
Optional: mTLS¶
- Enable only once Nedbank confirms requirements.
- Log client cert subject/serial.
14. Implementation (Python)¶
Recommended stack¶
- FastAPI + Uvicorn
lxml(ordefusedxml+lxml) for safe XML parsing- PostgreSQL + SQLAlchemy (or SQLite for MVP)
Endpoint routes¶
POST /zoap/ctm/serverPOST /zoap/ctm-qa/serverGET /health(returns 200)
Parsing approach¶
- Parse SOAP envelope.
- Extract
Header/EnterpriseContextas raw XML snippet. - Extract
Body/DistributeMsgRq/Content/*fields. - Decode TransformedData if possible.
Response construction¶
- Construct SOAP envelope with namespaces.
- Echo EnterpriseContext header.
- Add Body/DistributeMsgRs/ResultCode.
15. Test plan¶
Local tests¶
- Unit test: parse sample DistributeMsgRq XML
- Unit test: decode base64 CSV
- Unit test: response XML matches expected structure and namespaces
Integration tests¶
- POST sample SOAP to Nginx public endpoint (from external network)
- Validate:
- HTTP 200
- ResultCode correct
- Stored payloads present
Nedbank QA tests¶
- Provide QA endpoint + IPs to Nedbank
- Validate comms check endpoint (optional)
- Nedbank sends QA test messages/files
- Confirm correct ACK and message persistence
16. Deployment¶
Container¶
- Deploy Python service in a Proxmox container.
- Bind to internal interface only.
Process manager¶
- systemd service recommended
- auto-restart on failure
Secrets¶
- DB password via env vars or secrets file
- No secrets in git
17. Runbook (operations)¶
When Nedbank reports “delivery failures”¶
- Check Nginx access logs for requests from Nedbank IPs
- Check TLS errors (handshake / client cert)
- Check Python service logs
- Verify DB connectivity
- Confirm response ResultCode and SOAP correctness
Certificate renewal¶
- Confirm certbot renewals are active.
- If Nedbank pins/imports leaf cert, send them updated cert/chain before expiry.
18. Open items / to confirm with Nedbank¶
- mTLS requirements and client cert chain
- Required certificate/CSR generation spec
- Success ResultCode and failure codes
- Current DataPower egress IPs for allowlisting
- Call account eligibility and/or alternative feed for call account
- Whether PTI EOD file delivery is required and via which protocol
19. Appendix: Minimal XML templates¶
Minimal SOAP response skeleton (structure only)¶
NOTE: ResultCode shown as XR00 based on historical evidence; confirm with Nedbank.