Skip to content

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

  1. Nedbank/DataPower sends SOAP POST to CT Market public endpoint.
  2. Exneelo reverse proxy (Nginx) terminates TLS (and optionally mTLS) and forwards to internal Python service over WireGuard.
  3. Python service parses SOAP, extracts header + body, decodes payload, stores it, returns SOAP ACK.
  4. 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).
  • 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.wsdl
  • TIWebDistribution_2013-11-01.xsd
  • EnterpriseContext_2008-09.wsdl
  • EnterpriseContext_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)

  • DistributeMsgRq containing Content:
  • SecurityProxyType (string)
  • DestinationKey (long)
  • Format (string)
  • TransformedData (string; historically base64-encoded CSV)

Response payload (body)

  • DistributeMsgRs containing:
  • 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 also application/soap+xml if 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 DistributeMsgRs with success ResultCode.

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_action
  • enterprise_context_xml (raw header snippet)
  • destination_key
  • format
  • transformed_data_raw
  • transformed_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_hash as 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

  • SOAPAction
  • Content-Type
  • Host
  • X-Forwarded-For
  • X-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)

  • FastAPI + Uvicorn
  • lxml (or defusedxml + lxml) for safe XML parsing
  • PostgreSQL + SQLAlchemy (or SQLite for MVP)

Endpoint routes

  • POST /zoap/ctm/server
  • POST /zoap/ctm-qa/server
  • GET /health (returns 200)

Parsing approach

  • Parse SOAP envelope.
  • Extract Header/EnterpriseContext as 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”

  1. Check Nginx access logs for requests from Nedbank IPs
  2. Check TLS errors (handshake / client cert)
  3. Check Python service logs
  4. Verify DB connectivity
  5. 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)

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ent="http://contracts.it.nednet.co.za/Infrastructure/2008/09/EnterpriseContext" xmlns:tiw="http://contracts.it.nednet.co.za/services/business-execution/2013-11-01/TIWebDistribution">
  <soapenv:Header>
    <!-- echo EnterpriseContext as received -->
  </soapenv:Header>
  <soapenv:Body>
    <tiw:DistributeMsgRs>
      <tiw:ResultCode>XR00</tiw:ResultCode>
    </tiw:DistributeMsgRs>
  </soapenv:Body>
</soapenv:Envelope>

NOTE: ResultCode shown as XR00 based on historical evidence; confirm with Nedbank.