Sandbox Test Mode
SaySwitch collection endpoints use sandbox behavior when you authenticate with a test secret key:
Authorization: Bearer sk_test_...Sandbox requests still pass normal request validation. Send every field required by the payment channel, including a valid encrypted card payload for S2S card payments.
Force a Sandbox Status
Supported collection requests accept an optional status field:
| Field | Type | Allowed values | Required |
|---|---|---|---|
status | string | success, failed, pending | No |
- If
statusis omitted, the channel’s normal sandbox simulator decides the result. For card payments, an approved simulator card succeeds and an unrecognized card is declined. - If
statusissuccess,failed, orpending, the requested outcome is applied only in the test environment. - An invalid non-empty
statusvalue is rejected by request validation with HTTP400; it is not silently ignored. - A valid
statusvalue has no forcing effect in live mode. The live payment route processes the transaction normally.
Supported Endpoints
Use v1 for new S2S integrations.
| Channel | Current endpoint |
|---|---|
| Card | POST /api/s2s/v1/transaction/initialize |
| Bank Transfer | POST /api/s2s/v1/banktransfer/initialize |
| Bank Transfer with Wallet | POST /api/s2s/v1/banktransfer/initialize/wallet |
| USSD | POST /api/s2s/v1/ussd/initialize |
| USSD with Wallet | POST /api/s2s/v1/ussd/initialize/wallet |
| Mobile Money | POST /api/s2s/v1/momo/payment_request |
The legacy /api/s2s/... collection routes remain available for existing integrations. Do not mix legacy and v1 endpoints within one payment.
Status Behavior
success
The sandbox transaction is marked successful through the normal success-processing event and a successful-payment webhook is sent to your test webhook URL.
failed
The transaction is marked failed and a failed-payment webhook is sent to your test webhook URL.
For the S2S card simulator, the initialize response is:
{
"status": false,
"message": "Test payment declined (simulated)"
}pending
The transaction is created with pending status and no immediate success webhook is sent. A queued job schedules it to resolve successfully after the configured sandbox delay, which defaults to about 20 seconds.
The job only resolves a transaction that is still in the test environment and still pending when it runs. Queue-processing delays or a later status change can affect when or whether that transition occurs, so verify the transaction rather than relying on a timer.
The S2S card initialize response is:
{
"status": true,
"message": "Payment Processing"
}Built-In S2S Sandbox Cards
Use the normal NGN cards listed on Sandbox Test Cards. Recurring-payment cards use a separate hosted checkout flow and must not be used with the S2S card endpoint.
Card Example
Card details must be encrypted even when you force a status.
1. Encrypt the Sandbox Card
curl --request POST \
--url https://backendapi.sayswitchgroup.com/api/s2s/v1/test/encryption \
--header "Authorization: Bearer YOUR_TEST_SECRET_KEY" \
--header "Content-Type: application/json" \
--data '{
"data": {
"number": "4005555555000009",
"expiryMonth": "05",
"expiryYear": "37",
"cvv": "000"
},
"reference": "SANDBOX_CARD_2026_0001"
}'The response body is the encrypted hexadecimal value directly. It is not a JSON object.
2. Initialize With the Same Reference
curl --request POST \
--url https://backendapi.sayswitchgroup.com/api/s2s/v1/transaction/initialize \
--header "Authorization: Bearer YOUR_TEST_SECRET_KEY" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data '{
"email": "customer@example.com",
"amount": "1000.00",
"currency": "NGN",
"reference": "SANDBOX_CARD_2026_0001",
"card": "ENCRYPTED_CARD_HEX_STRING",
"status": "failed"
}'The exact same reference must be used for encryption and initialization. The initialize controller decrypts and validates the card before applying the forced result.
Bank Transfer Example
curl --request POST \
--url https://backendapi.sayswitchgroup.com/api/s2s/v1/banktransfer/initialize \
--header "Authorization: Bearer YOUR_TEST_SECRET_KEY" \
--header "Content-Type: application/json" \
--data '{
"email": "customer@example.com",
"amount": "1000.00",
"currency": "NGN",
"reference": "SANDBOX_BANK_2026_0002",
"status": "pending"
}'The virtual-account response remains the normal channel response. The forced status controls the later sandbox outcome and webhook behavior.
Mobile Money Example
curl --request POST \
--url https://backendapi.sayswitchgroup.com/api/s2s/v1/momo/payment_request \
--header "Authorization: Bearer YOUR_TEST_SECRET_KEY" \
--header "Content-Type: application/json" \
--data '{
"amount": "1000.00",
"currency": "KES",
"bankName": "Mpesa",
"phone": "0722123456",
"description": "Sandbox status test",
"reference": "SANDBOX_MOMO_2026_0003",
"status": "failed"
}'Invalid Status Response
An unsupported status value fails controller validation with HTTP 400:
{
"status": false,
"message": "The selected status is invalid.",
"error": {
"status": [
"The selected status is invalid."
]
}
}The exact message can include other validation errors when more than one request field is invalid.
Notes
- Sandbox webhooks use your test webhook configuration.
- The
statusfield is a testing convenience and cannot force a live transaction result. - Continue to verify the final transaction status through the matching verification endpoint.
- Never use real card details in sandbox requests.