Migrating from S2S to the Orchestration API
Field-by-field guide for merchants moving from the form-urlencoded S2S API to the JSON Orchestration API.
This guide is for merchants currently integrating directly with the S2S (server-to-server) API — the form-urlencoded endpoint at eu-test.oppwa.com / sandbox-card.peachpayments.com — and moving to the Orchestration API.
Endpoint mapping
| S2S | Orchestration API | Notes |
|---|---|---|
POST /v1/payments (paymentType=DB/PA) | POST /payments | Authorise + capture (DB) or auth-only (PA — set capture_method: "manual") |
POST /v1/payments/{id} (paymentType=CP) | POST /payments/{id}/capture | Capture a previously authorised PA |
POST /v1/payments/{id} (paymentType=RV) | POST /payments/{id}/cancel | Void (only works pre-capture) |
POST /v1/payments/{id} (paymentType=RF) | POST /refunds | Refund a captured payment |
GET /v1/payments/{id} | GET /payments/{id}?force_sync=true | Sync / retrieve |
POST /v1/threeDSecure | not user-facing | Orchestration handles standalone 3DS internally when authentication_type: "three_ds" |
GET /v1/threeDSecure/{id} | not user-facing | Same — handled by Orchestration |
Authentication mapping
| S2S | Orchestration API |
|---|---|
Authorization: Bearer <token> | api-key: <merchant_api_key> (per-merchant, not per-entity) |
entityId=<entity-id> (form param) | Configured on the merchant connector account (MCA): connector_account_details.key1 |
| Bearer token | Configured on the MCA: connector_account_details.api_key, auth_type: "BodyKey" |
You provision one MCA per S2S entity. If you have multiple entities (e.g. one per acquirer), create one MCA per entity and route between them with Orchestration's routing rules.
Field-by-field request mapping
Basic transaction fields
| S2S | Orchestration API | Notes |
|---|---|---|
amount=92.00 (major units, two decimals) | "amount": 9200 (minor units, integer) | Orchestration converts to S2S's major-unit string automatically |
currency=ZAR | "currency": "ZAR" | |
paymentBrand=VISA | derived from card BIN, or "payment_method_type": "credit"/"debit" | Orchestration picks the brand from the card number; payment_method_type controls credit vs debit routing |
paymentType=DB | "capture_method": "automatic" (default) | DB = authorise + capture |
paymentType=PA | "capture_method": "manual" | Authorise only; capture later |
merchantTransactionId=ord_123 | "connector_request_reference_id": "ord_123" (or payment_id) | Orchestration truncates to S2S's 16-char limit |
descriptor=Coffee | "statement_descriptor_name": "Coffee" | |
testMode=EXTERNAL | MCA connector_meta_data: { "test_mode": "EXTERNAL" } | Opt-in. Default (absent) → S2S's INTERNAL default. |
Card details
| S2S | Orchestration API |
|---|---|
card.number=4111111111111111 | "payment_method_data.card.card_number": "4111111111111111" |
card.holder=Jane Jones | "payment_method_data.card.card_holder_name": "Jane Jones" |
card.expiryMonth=05 | "payment_method_data.card.card_exp_month": "05" |
card.expiryYear=2034 | "payment_method_data.card.card_exp_year": "34" (or "2034") |
card.cvv=123 | "payment_method_data.card.card_cvc": "123" |
payment_method is "card", payment_method_type is "credit" or "debit".
Wallets
| S2S | Orchestration API |
|---|---|
applePay.paymentToken=... | "payment_method_data.wallet.apple_pay_redirect": {} (browser SDK) or predecrypt token via Apple Pay decryption flow |
googlePay.paymentToken=... | "payment_method_data.wallet.google_pay_redirect": {} |
samsungPay.paymentToken=... | "payment_method_data.wallet.samsung_pay": { "token": "..." } |
paymentBrand=APPLEPAY | "payment_method_type": "apple_pay" |
paymentBrand=GOOGLEPAY | "payment_method_type": "google_pay" |
paymentBrand=SAMSUNGPAY | "payment_method_type": "samsung_pay" |
Customer
| S2S | Orchestration API |
|---|---|
customer.email=jane@x.com | "email": "jane@x.com" |
customer.givenName=Jane | "billing.address.first_name": "Jane" |
customer.surname=Jones | "billing.address.last_name": "Jones" |
customer.merchantCustomerId=cust_42 | "customer_id": "cust_42" |
customer.ip=1.2.3.4 | Auto-set by Orchestration from the request IP. Override with "browser_info.ip_address": "1.2.3.4" if needed |
customer.phone=+27115551234 | "phone": "+27115551234" |
Browser info (3DS 2.x)
| S2S | Orchestration API (browser_info) |
|---|---|
customer.browser.acceptHeader | accept_header |
customer.browser.language | language |
customer.browser.screenHeight | screen_height |
customer.browser.screenWidth | screen_width |
customer.browser.timezone | time_zone |
customer.browser.userAgent | user_agent |
customer.browser.javascriptEnabled | java_script_enabled |
customer.browser.javaEnabled | java_enabled |
customer.browser.screenColorDepth | color_depth / screen_color_depth |
customer.browser.challengeWindow | (Orchestration sets internally; S2S receives 03 for full-screen) |
Billing / shipping address
| S2S | Orchestration API |
|---|---|
billing.street1 | billing.address.line1 |
billing.street2 | billing.address.line2 |
billing.city | billing.address.city |
billing.state | billing.address.state |
billing.postcode | billing.address.zip |
billing.country=US | billing.address.country: "US" |
shipping.* | shipping.address.* (same field names as billing) |
Use-case examples
S2S's standingInstruction.*, createRegistration, and threeDSecure.* fields are derived by Orchestration from the shape of your request — you don't set them directly. Pick a use case below to see the before/after.
Save a card during a customer-present payment so it can be charged later.
Orchestration sends createRegistration=true and the correct standingInstruction.* fields automatically.
Response (excerpt)
Store mandate_id for subsequent MITs. connector_mandate_id is S2S's registrationId, and network_transaction_id is the CITI / traceId for Nedbank acquirers — both are stored by Orchestration automatically.
Custom parameters / metadata
S2S accepts arbitrary customParameters[key]=value form fields.
| S2S | Orchestration API | What Orchestration forwards |
|---|---|---|
customParameters[paymentId]=pay_xxx | (set automatically by Orchestration) | |
customParameters[your_key]=your_value | "metadata": { "your_key": "your_value" } (top-level) | not yet auto-forwarded — see below |
Response field mapping
| S2S response field | Orchestration API response field |
|---|---|
id | connector_transaction_id |
result.code | classified into status (succeeded / processing / failed) + error_code |
result.description | error_message |
result.cvvResponse | payment_method_data.card.payment_checks (CVV part of the JSON) |
registrationId | connector_mandate_id |
paymentBrand | payment_method_data.card.card_network |
card.binCountry | included in connector response data |
resultDetails.AuthCode (or ApprovalCode fallback) | payment_method_data.card.auth_code |
resultDetails.AcquirerResponse | payment_method_data.card.payment_checks (acquirer part) |
resultDetails.MerchantAdviceCode | exposed in connector response data |
resultDetails.ConnectorTxID2 (Nedbank) → RRN at position 2 | connector_response_reference_id |
resultDetails.ConnectorTxID3 (Nedbank) → CITI at position 4 | network_transaction_id |
STAN / originalTransactionId parsed from ConnectorTxIDs | payment_method_data.card.payment_checks |
risk.score | included in connector response data |
threeDSecure.eci / verificationId / dsTransactionId / acsTransactionId / version / flow | authentication_data |
standingInstruction.agreementId | persisted in mandate_metadata for replay on next MIT |
What you stop doing
You no longer need to:
- track
registrationId,agreementId, orinitialTransactionIdyourself — Orchestration persists them in the mandate - compute and send
standingInstruction.mode/source/type/initiator/transactionType— Orchestration derives them from the flow - maintain entity-specific routing logic — set up an MCA per entity and let Orchestration routing rules decide
- handle 3DS redirects manually for the standard flow — Orchestration exposes a single
next_action.redirect_to_urland a single redirect-back URL - parse
result.codeto classify success/pending/failure — Orchestration maps codes tostatusanderror_code/error_message
You still need to:
- decide
capture_method(auto vs manual) per transaction - decide
mit_categoryfor subsequent MITs - pass
setup_future_usage: "off_session"+customer_acceptanceon the initial CIT to mint a mandate - pass external-3DS data via
three_ds_dataif you do your own 3DS - store
payment_idandmandate_idreturned by Orchestration
Minimum testing checklist
- No-3DS card payment, auto-capture — issues a successful payment and gets a
connector_transaction_id. - Manual capture —
capture_method: "manual"→requires_capture→POST /payments/{id}/capture. - Refund —
POST /refundsagainst a succeeded payment. - Initial CIT (off-session) —
setup_future_usage: "off_session"+customer_acceptancereturns amandate_id+connector_mandate_id+network_transaction_id. - Subsequent MIT —
recurring_details.mandate_idreturns succeeded. - 3DS challenge —
authentication_type: "three_ds"returnsrequires_customer_actionwithnext_action.redirect_to_url; complete the redirect; payment becomessucceeded. - External 3DS — pre-authenticated payment with
three_ds_datasucceeds without further user action. - MIT after CIT — verify the second MIT against a CIT succeeds (proves
agreementId, traceIdreplay).