Card Payments (S2S)

Overview

Process secure card payments using Sayswitch’s Server-to-Server (S2S) API. This integration allows you to handle card transactions directly from your backend while keeping sensitive card data secure.

Quick Start Guide

Card payments with Sayswitch follow a simple 4-step process:

  1. Encrypt card details → Get a secure card token
  2. Initialize payment → Start the 3DS2 transaction
  3. Trigger 3DS2 authentication → Get payer challenge URL
  4. Customer completes OTP → On Sayswitch’s secure hosted page
  5. Verify transaction → Confirm payment status

Step 1: Encrypt Card Details

Before processing any payment, you must first encrypt the card details to ensure security.

Endpoint

POST https://backendapi.sayswitchgroup.com/api/s2s/test/encryption

Headers

{
  "Content-Type": "application/json",
  "Authorization": "Bearer sk_live_your_secret_key_here"
}

Request Body

5204730000002449 12/35 244

{
  "data": {
    "number": "5204730000002449",
    "expiryMonth": "12",
    "expiryYear": "35",
    "cvv": "244"
    "PIN": "1234" // This is meant for NGN only and not required for USD, GBP, and EURO
  },
  "reference": "YOUR_UNIQUE_REF_001"
}

cURL Example

curl --location 'https://backendapi.sayswitchgroup.com/api/s2s/test/encryption' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer REMOVED_SECRET' \
--data '{
    "data": {
        "number": "5204730000002449",
        "expiryMonth": "12",
        "expiryYear": "35",
        "cvv": "244"
        "PIN": "1234" // This is meant for NGN only and not required for USD, GBP, and EURO
    },
    "reference": "865436543308888877527544pw6"
}'

Response

⚠️ The response is a raw hex-encoded encrypted string — not a JSON object.

61e2eaa450602e592ce3f5733a15e1b907588060fd15cab502bf7669b88e9486fd46787bde8d9e9a05c168fa12748218d7a8d0a04e4533084a5734e5b3e9963d2c3a34b47a8cd894ec05947eaa1feab4

💡 Important: Save this full encrypted string — you’ll pass it as the card value in Step 2.


Step 2: Initialize Payment

Use the encrypted card token to start the 3DS2 payment process.

Endpoint

POST https://backendapi.sayswitchgroup.com/api/s2s/transaction/initialize

Headers

{
  "Content-Type": "application/json",
  "Authorization": "Bearer sk_live_your_secret_key_here"
}

Request Body

{
  "amount": "1",
  "card": "<encrypted_string_from_step_1>",
  "currency": "USD",
  "email": "customer@example.com",
  "reference": "YOUR_UNIQUE_TXN_REF"
}

cURL Example

curl --location 'https://backendapi.sayswitchgroup.com/api/s2s/transaction/initialize' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer REMOVED_SECRET' \
--data-raw '{
    "amount": "1",
    "card": "61e2eaa450602e592ce3f5733a15e1b907588060fd15cab502bf7669b88e9486fd46787bde8d9e9a05c168fa12748218d7a8d0a04e4533084a5734e5b3e9963d2c3a34b47a8cd894ec05947eaa1feab4",
    "currency": "USD",
    "email": "akinlabisamson15@gmail.com",
    "reference": "865436543308888877527544pw6"
}'

Response

{
  "status": true,
  "message": "Proceed authentication 3DS2",
  "data": "<script id=\"initiate-authentication-script\"></script>",
  "_links": {
    "url": "https://backendapi.sayswitchgroup.com/api/mrtyui876578a/3ds2auth",
    "method": "POST",
    "payload": ["ref"]
  }
}

📌 Next Step: POST your transaction ref to the _links.url returned above.


Step 3: Trigger 3DS2 Authentication

POST your transaction reference to the _links.url from Step 2 to initiate payer authentication.

Endpoint

POST https://backendapi.sayswitchgroup.com/api/mrtyui876578a/3ds2auth

ℹ️ Use the exact URL returned in _links.url from Step 2 — it may differ per transaction.

Request Body

{
  "ref": "YOUR_UNIQUE_TXN_REF"
}

cURL Example

curl --location 'https://backendapi.sayswitchgroup.com/api/mrtyui876578a/3ds2auth' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer REMOVED_SECRET' \
--data '{
"ref":"865436543308888877527544pw6"
}'

Response

{
  "status": true,
  "message": "Proceed payer authentication",
  "data": "<div id=\"threedsChallengeRedirect\" xmlns=\"http://www.w3.org/1999/html\" style=\" height: 100vh\"> <form id =\"threedsChallengeRedirectForm\" method=\"POST\" action=\"https://acs.up-ng.com\" target=\"challengeFrame\"> <input type=\"hidden\" name=\"creq\" value=\"eyJ0aHJlZURTU2VydmVyVHJhbnNJRCI6ImI5OWJkZWYwLTJkYWEtNGU0ZC1hYWU5LTI4ZTg4OTM1YTQ1MyIsImFjc1RyYW5zSUQiOiI3MjZlYjZkZi00ZTZlLTQxZjctYTFiNC1mM2I3NDgyOWFiYjUiLCJjaGFsbGVuZ2VXaW5kb3dTaXplIjoiMDUiLCJtZXNzYWdlVHlwZSI6IkNSZXEiLCJtZXNzYWdlVmVyc2lvbiI6IjIuMi4wIn0\" /> </form> <iframe id=\"challengeFrame\" name=\"challengeFrame\" width=\"100%\" height=\"100%\" ></iframe> <script id=\"authenticate-payer-script\"> var e=document.getElementById(\"threedsChallengeRedirectForm\"); if (e) { e.submit(); if (e.parentNode !== null) { e.parentNode.removeChild(e); } } </script> </div>",
  "alt": "https://backendapi.sayswitchgroup.com/mrtyui876578a-payment/authpayer/865436543308888877527544pw6"
}

Understanding the Response

Two options are returned for the 3DS challenge:

FieldDescription
dataHTML snippet with an embedded 3DS iframe challenge form you can render directly in your frontend
altA direct URL to Sayswitch’s hosted OTP page — redirect your customer here

💡 Recommended: Use the alt URL to redirect the customer to Sayswitch’s hosted 3DS page for simplicity.


Step 4: Customer Completes OTP Verification

Option one: Inject the data HTML snippet directly into your frontend to handle the challenge via an iframe without leaving your site.

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3DS Challenge Authentication</title>
    <style>
        body, html {
            margin: 0;
            padding: 0;
            height: 100%;
            overflow: hidden;
        }
        #challenge-container {
            width: 100%;
            height: 100vh;
        }
    </style>
</head>
<body>
    <div id="challenge-container">
        <div id="threedsChallengeRedirect" style="height: 100vh">
            <form id="threedsChallengeRedirectForm" method="POST" action="https://acs.up-ng.com" target="challengeFrame">
                <input type="hidden" name="creq" value="eyJ0aHJlZURTU2VydmVyVHJhbnNJRCI6ImI5OWJkZWYwLTJkYWEtNGU0ZC1hYWU5LTI4ZTg4OTM1YTQ1MyIsImFjc1RyYW5zSUQiOiI3MjZlYjZkZi00ZTZlLTQxZjctYTFiNC1mM2I3NDgyOWFiYjUiLCJjaGFsbGVuZ2VXaW5kb3dTaXplIjoiMDUiLCJtZXNzYWdlVHlwZSI6IkNSZXEiLCJtZXNzYWdlVmVyc2lvbiI6IjIuMi4wIn0" />
            </form>
 
            <iframe id="challengeFrame" name="challengeFrame" width="100%" height="100%" frameborder="0"></iframe>
 
            <script id="authenticate-payer-script">
                (function() {
                    var e = document.getElementById("threedsChallengeRedirectForm");
                    if (e) {
                        e.submit();
                        // Removes the form from the DOM after submission for a cleaner tree
                        if (e.parentNode !== null) {
                            e.parentNode.removeChild(e);
                        }
                    }
                })();
            </script>
        </div>
    </div>
 
</body>
</html>

Option Two: Redirect your customer to the alt URL returned in Step 3. The customer will complete the 3DS2 OTP challenge on Sayswitch’s secure hosted page.

Redirect URL

https://backendapi.sayswitchgroup.com/mrtyui876578a-payment/authpayer/{reference}

Example

https://backendapi.sayswitchgroup.com/mrtyui876578a-payment/authpayer/865436543308888877527544pw6

What Happens:

  1. Customer lands on Sayswitch’s secure 3DS OTP page
  2. Customer enters the OTP sent to their registered phone number / email
  3. Sayswitch processes the verification automatically
  4. Customer is redirected back to your defined callback URL

Step 5: Verify Transaction Status

After OTP verification, check the final transaction status using your reference.

Endpoint

GET https://backendapi.sayswitchgroup.com/api/s2s/v1/transaction/verify/{reference}

Headers

{
  "Authorization": "Bearer sk_live_your_secret_key_here",
  "Content-Type": "application/json"
}

Example Request

GET https://backendapi.sayswitchgroup.com/api/s2s/v1/transaction/verify/865436543308888877527544pw6

Success Response

{
  "success": true,
  "message": "Verification successful",
  "data": {
    "amount": "1",
    "currency": "USD",
    "status": "success",
    "transaction_date": "2026-02-19 08:37:00",
    "reference": "865436543308888877527544pw6",
    "domain": "live",
    "gateway_response": null,
    "channel": "card",
    "ip_address": null,
    "originator_name": "",
    "originator_account_number": "",
    "fees": "0.038",
    "plan": null,
    "requested_amount": "1"
  },
  "customer": {
    "id": 1582,
    "customer_code": "CUS_f0f1shiyd0jn2fg",
    "first_name": null,
    "last_name": null,
    "email": "johndoe@gmail.com"
  },
  "card": {
    "first6Digits": "416549",
    "last4Digits": "2839",
    "expiry": "0530",
    "type": "",
    "token": null
  },
  "log": {
    "time_spent": null,
    "attempts": null,
    "authentication": null,
    "errors": 2,
    "success": true,
    "channel": "card",
    "history": [
      {
        "id": 10683,
        "type": "TRANSACTION RECORDED",
        "message": "success",
        "time": null,
        "reference": "865436543308888877527544pw6",
        "channel": "S2S",
        "ip_address": "10.178.205.70",
        "device": "PostmanRuntime/7.51.1"
      },
      {
        "id": 10684,
        "type": "INITIATE_CARD",
        "message": "{\"data\":{\"paymentDetail\":{\"redirectUrl\":\"https://backendapi.sayswitchgroup.com/ap-mpgs/api/v2/starttx?txref=ALPYCO08841666581771486603292\",\"recipientAccount\":null,\"bankName\":null,\"paymentReference\":",
        "time": null,
        "reference": "865436543308888877527544pw6",
        "channel": "card",
        "ip_address": "",
        "device": null
      }
    ]
  }
}

Transaction Statuses

StatusDescription
successPayment completed successfully
pendingPayment is still processing
failedPayment failed or was declined

Request Parameters Reference

Card Encryption Parameters

ParameterTypeRequiredDescription
data.numberstringYesCard number (16 digits)
data.expiryMonthstringYesExpiry month (MM format)
data.expiryYearstringYesExpiry year (YY format)
data.cvvstringYesCard security code
referencestringYesUnique encryption reference

Payment Initialization Parameters

ParameterTypeRequiredDescription
amountstringYesPayment amount
cardstringYesRaw encrypted string from Step 1
currencystringYesCurrency code (NGN, USD, etc.)
emailstringYesCustomer email address
referencestringYesUnique transaction reference

Need Help?

🔒 Security Note: Never store or log actual card details. Always use the encryption endpoint first.