USD Card Payments

USD card payments use the current S2S v1 card endpoint. Set currency to USD; the initialize response tells your backend whether the payment succeeded, failed, or requires OTP or 3DS authentication.

All requests must come from your secure backend. Never expose your SaySwitch secret key in browser JavaScript.

Requirements

  • USD collection must be enabled for your business.
  • Your business must have an active USD wallet in the correct test or live environment.
  • The current international-card flow supports Visa and Mastercard.
  • Use a unique transaction reference containing 16 to 33 characters.
  • Provide a merchant-owned return URL when no callback URL is already configured for your account.

Endpoints

ActionMethodEndpoint
Encrypt card detailsPOSThttps://backendapi.sayswitchgroup.com/api/s2s/v1/test/encryption
Initialize paymentPOSThttps://backendapi.sayswitchgroup.com/api/s2s/v1/transaction/initialize
Complete OTPPOSTReturned in _links.url; normally the v1 OTP continuation endpoint
Complete 3DSPOSTReturned in _links.url; normally the v1 3DS continuation endpoint
Verify transactionGEThttps://backendapi.sayswitchgroup.com/api/s2s/v1/transaction/verify/{reference}

Use the exact same reference for encryption, initialization, OTP or 3DS continuation, and verification. The reference is used to derive the card-encryption initialization vector, so changing it prevents decryption.

The examples use USD_CARD_2026_0001 throughout.

1. Encrypt Card Details

curl --request POST \
  --url https://backendapi.sayswitchgroup.com/api/s2s/v1/test/encryption \
  --header "Authorization: Bearer YOUR_SECRET_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "data": {
      "number": "4005555555000009",
      "expiryMonth": "05",
      "expiryYear": "37",
      "cvv": "000"
    },
    "reference": "USD_CARD_2026_0001"
  }'
FieldTypeRequiredDescription
data.numberstringYesVisa or Mastercard card number.
data.expiryMonthstringYesTwo-digit expiry month.
data.expiryYearstringYesTwo-digit expiry year.
data.cvvstringYesThree- or four-digit card security code.
referencestringYesThe same 16–33 character reference used for initialization.

The response is the encrypted hexadecimal string itself, not JSON:

8f2a...illustrative_hexadecimal_value...91bc

Read the response body as text. The framework may return text/html; charset=UTF-8 for this raw text response.

The encrypted card value is transaction-specific, not a reusable token. Keep it only while the payment and any required 3DS continuation are active. Never log or permanently store raw card details or the encrypted payload.

Do not add PIN inside the encrypted card object for initialization. The current outer S2S controller does not forward an encrypted inner PIN to the international-card flow.

2. Initialize the USD Payment

curl --request POST \
  --url https://backendapi.sayswitchgroup.com/api/s2s/v1/transaction/initialize \
  --header "Authorization: Bearer YOUR_SECRET_KEY" \
  --header "Accept: application/json" \
  --header "Content-Type: application/json" \
  --data '{
    "amount": "10.00",
    "card": "ENCRYPTED_CARD_HEX_STRING",
    "currency": "USD",
    "email": "customer@example.com",
    "reference": "USD_CARD_2026_0001",
    "callbackUrl": "https://merchant.example.com/payment/return"
  }'

Initialize Fields

FieldTypeRequiredDescription
amountstringYesUSD amount, at least 1 and within your configured limits.
cardstringYesRaw hexadecimal response from the card encryption endpoint.
currencystringYesUse USD.
emailstringYesValid customer email address.
referencestringYesUnique 16–33 character reference used during encryption.
callbackUrlURLConditionalYour return URL. Required when your merchant account does not already have a callback URL configured.
pinstringNoTop-level PIN, four to six characters, when required by the card flow.

The backend also recognizes returnUrl, return_url, and callback_url, but callbackUrl is recommended for consistency.

callbackUrl belongs to the merchant. It is used to return the customer’s browser after authentication and has the transaction reference appended as a query parameter. A return to this URL is not proof of payment; always verify the transaction.

If a PIN is needed, send it only as the top-level pin field. Do not claim or depend on PIN inside the encrypted card object.

3. Handle the Initialize Response

Inspect the actual response. Do not select OTP or 3DS yourself.

Immediate Success

{
  "status": true,
  "message": "Payment Successful"
}

Verify the transaction before fulfilling the order.

Failure

{
  "status": false,
  "message": "Unable to process card payment"
}

The message is replaced by the actual validation, configuration, or decline message when one is available.

OTP Required

The international-card flow normalizes an OTP action into this structure. Placeholder values are illustrative.

{
  "status": true,
  "message": "OTP authorization required",
  "data": {
    "auth": "otp",
    "otp": {
      "message": "Please enter the OTP sent to the cardholder",
      "tokenId": "TOKEN_ID_RETURNED_BY_SAYSWITCH"
    }
  },
  "_links": {
    "url": "https://backendapi.sayswitchgroup.com/api/s2s/v1/transaction/card/otp",
    "method": "POST",
    "payload": ["ref", "otp"]
  }
}

The _links object is top-level in this response. Other card routes can place continuation links elsewhere, so do not merge response formats. Follow the exact _links.url, _links.method, and payload returned for the transaction.

Ask the customer for the OTP, then submit it from your backend:

curl --request POST \
  --url https://backendapi.sayswitchgroup.com/api/s2s/v1/transaction/card/otp \
  --header "Authorization: Bearer YOUR_SECRET_KEY" \
  --header "Accept: application/json" \
  --header "Content-Type: application/json" \
  --data '{
    "ref": "USD_CARD_2026_0001",
    "otp": "CUSTOMER_OTP"
  }'

The backend retains the authorization token ID returned during initialization. Merchants normally send only ref and otp, as specified by _links.payload.

A successful continuation returns:

{
  "status": true,
  "message": "Payment Successful"
}

An invalid OTP returns status: false with the message returned for that attempt.

3DS Required

{
  "status": true,
  "message": "3DS authorization required",
  "data": {
    "auth": "3ds",
    "threed": {
      "id": "RETURNED_3DS_ID",
      "redirectUrl": "RETURNED_REDIRECT_URL",
      "acsUrl": "RETURNED_ACS_URL",
      "method": "POST",
      "payload": {
        "creq": "RETURNED_CREQ_VALUE",
        "TermUrl": "RETURNED_TERM_URL"
      },
      "termUrl": "RETURNED_TERM_URL",
      "callBackUrl": "RETURNED_CALLBACK_URL",
      "orderId": "RETURNED_ORDER_ID",
      "transactionId": "RETURNED_TRANSACTION_ID",
      "paymentId": "RETURNED_PAYMENT_ID"
    }
  },
  "_links": {
    "url": "https://backendapi.sayswitchgroup.com/api/s2s/v1/transaction/card/3ds",
    "method": "POST",
    "payload": ["ref", "card"]
  }
}

The backend returns all data.threed keys shown above. A value can be null when it was not supplied for that authentication attempt. method defaults to GET if no method is returned.

Present the cardholder challenge using data.threed.acsUrl or data.threed.redirectUrl, the returned method, and every value in data.threed.payload. After the customer returns to your callbackUrl, continue using the top-level _links contract.

The current 3DS continuation requires the same encrypted card payload:

curl --request POST \
  --url https://backendapi.sayswitchgroup.com/api/s2s/v1/transaction/card/3ds \
  --header "Authorization: Bearer YOUR_SECRET_KEY" \
  --header "Accept: application/json" \
  --header "Content-Type: application/json" \
  --data '{
    "ref": "USD_CARD_2026_0001",
    "card": "ENCRYPTED_CARD_HEX_STRING"
  }'

Use the returned URL rather than constructing it yourself. Keep the encrypted payload only until this continuation has finished.

Missing stored 3DS authorization data returns HTTP 400:

{
  "status": false,
  "message": "3DS authorization data not found"
}

4. Verify the Transaction

curl --request GET \
  --url https://backendapi.sayswitchgroup.com/api/s2s/v1/transaction/verify/USD_CARD_2026_0001 \
  --header "Authorization: Bearer YOUR_SECRET_KEY" \
  --header "Accept: application/json"

The verification response uses the same structure documented for NGN card verification, with data.currency set to USD.

Only fulfil the order when the verified data.status is success. An initialize response, OTP result, 3DS browser return, callback URL, or webhook is not a substitute for server-side verification.

If the reference does not exist for the authenticated merchant and environment, the endpoint returns HTTP 401:

{
  "success": false,
  "message": "Reference code does not exist"
}

Sandbox Testing

The cards on Sandbox Test Cards are documented for the normal NGN S2S simulator. For USD testing, use the sandbox details issued for your USD configuration.

See Sandbox Test Mode for the validated status override behavior.

Errors

ConditionHTTP behaviorResponse message or shape
Missing or invalid merchant authorization401Merchant Authorization is required or Invalid Secret Key
Invalid initialize fields or reference length400status: false, validation message, and error object
Invalid hexadecimal card value or mismatched reference200 with status: falseKindly check your encrypted value
Incomplete decrypted card fields400Incomplete card request
Unsupported card brand400USD international card payments currently support Visa and Mastercard only
Duplicate merchant reference200 with status: falseReference Already Exist
USD collection disabled403USD collection is not enabled for this business
No active USD wallet in the environment400Unauthorized Merchant Currency provided
Transaction amount limit exceeded400Transaction limit exceeded. Maximum allowed amount for USD is ... per transaction.
Collection amount outside configured limits400Minimum or maximum collection-limit message
Missing callback URL and no configured merchant callback400callbackUrl is required for USD S2S card payments when no merchant callback URL is configured
Missing OTP authorization data400OTP authorization data not found
Missing 3DS authorization data4003DS authorization data not found
Invalid OTP or failed authentication200 with status: falseMessage returned for the authentication attempt
Transaction reference not found during verification401Reference code does not exist

Some card-route failures return HTTP 200 with status: false. Inspect both the HTTP status and JSON body.

Full API Payload Encryption

There are two independent encryption layers:

  1. Card-field encryption produces the hexadecimal value sent as card.
  2. Merchants configured for full S2S payload encryption must encrypt the entire request into the outer data envelope and decrypt the encrypted response body.

The examples above show the standard request format. See API Payload Encryption if the second layer is enabled for your account.

Security Notes

  • Keep secret keys and card processing on your secure backend.
  • Never expose secret keys in frontend JavaScript.
  • Never log or permanently store PAN, CVV, PIN, or encrypted card payloads.
  • Keep the encrypted payload only for the active transaction and a continuation step that requests it.
  • Verify data.status before providing goods or services.

Legacy Endpoints

Existing /api/s2s/... integrations can continue using the legacy family. New integrations should use /api/s2s/v1/....

ActionLegacy endpoint
Encrypt card detailsPOST /api/s2s/test/encryption
Initialize paymentPOST /api/s2s/transaction/initialize
Complete OTPPOST /api/s2s/transaction/card/otp
Complete 3DSPOST /api/s2s/transaction/card/3ds
Verify transactionGET /api/s2s/transaction/verify/{reference}

Do not mix legacy and v1 endpoints during one transaction. Encryption, initialization, authentication continuation, and verification must use the same endpoint family. Always prefer the continuation URL returned by the transaction. There is no public /api/s2s/v2/... endpoint.