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:
- Encrypt card details → Get a secure card token
- Initialize payment → Start the 3DS2 transaction
- Trigger 3DS2 authentication → Get payer challenge URL
- Customer completes OTP → On Sayswitch’s secure hosted page
- 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/encryptionHeaders
{
"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
cardvalue 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/initializeHeaders
{
"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
refto the_links.urlreturned 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.urlfrom 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:
| Field | Description |
|---|---|
data | HTML snippet with an embedded 3DS iframe challenge form you can render directly in your frontend |
alt | A direct URL to Sayswitch’s hosted OTP page — redirect your customer here |
💡 Recommended: Use the
altURL 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/865436543308888877527544pw6What Happens:
- Customer lands on Sayswitch’s secure 3DS OTP page
- Customer enters the OTP sent to their registered phone number / email
- Sayswitch processes the verification automatically
- 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/865436543308888877527544pw6Success 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
| Status | Description |
|---|---|
success | Payment completed successfully |
pending | Payment is still processing |
failed | Payment failed or was declined |
Request Parameters Reference
Card Encryption Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
data.number | string | Yes | Card number (16 digits) |
data.expiryMonth | string | Yes | Expiry month (MM format) |
data.expiryYear | string | Yes | Expiry year (YY format) |
data.cvv | string | Yes | Card security code |
reference | string | Yes | Unique encryption reference |
Payment Initialization Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | string | Yes | Payment amount |
card | string | Yes | Raw encrypted string from Step 1 |
currency | string | Yes | Currency code (NGN, USD, etc.) |
email | string | Yes | Customer email address |
reference | string | Yes | Unique transaction reference |
Need Help?
- Support: support@sayswitch.com
- Documentation: API Reference
- Test Cards: Use
5204730000002449for testing
🔒 Security Note: Never store or log actual card details. Always use the encryption endpoint first.