Switch Wallet Merchant API Documentation
Becoming a Merchant
Welcome to the Switch Wallet Merchant API. Integrating with our powerful RESTful API and webhook service is the first step to leveraging our platform. The process begins with completing your business verification and setting up your account.
Onboarding Steps
- Complete KYB: Finalize the "Know Your Business" (KYB) process with our team.
- Provide Details: Submit your business account and SUPER admin profile information as structured below.
Merchant Data Structure
You will need to provide the following data. The table below illustrates the required fields and their validation rules.
Merchant Company
| Field | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | Yes | Unique identifier for the merchant company |
| name | string | Yes | Company name |
| tagline | string | Yes | Company tagline or slogan |
| companyEmail | string (email) | Yes | Company email address (lowercase) |
| countryCode | string (2 chars) | Yes | ISO country code (2 characters) |
| logoUrl | string (URI) | No | URL to company logo |
| websiteUrl | string (URI) | No | Company website URL |
Merchant Account
| Field | Type | Required | Description |
|---|---|---|---|
| string (email) | Yes | Account email address (lowercase) | |
| firstName | string | Yes | First name of the account holder |
| lastName | string | Yes | Last name of the account holder |
| phoneNumber | string | Yes | Phone number |
| countryCode | string (2 chars) | Yes | ISO country code (2 characters) |
General API Response
All API responses follow a standardized JSON structure. This ensures consistency and predictability when you interact with our endpoints.
Response Object Structure
{
"data": {},
"status": "success",
"statusCode": 200,
"message": "Action Completed",
"action": null,
"messageLanguageCode": "app_001",
"details": null,
"cacheTTL": 20
}
Field Descriptions
| Field | Description |
|---|---|
| data | The main content returned by the API. Can be an object, array, or null. |
| status | Indicates the outcome of the request. Possible values are 'success' or 'failed'. |
| statusCode | The standard HTTP response status code. |
| message | A human-readable message to be displayed to the end-user (e.g., "Registration completed"). |
| messageLanguageCode | A unique code for the message, which can be used by frontend clients for translations. |
| action | A specific action the frontend should take, e.g., 'load_2fa_screen'. See action documentation for details. |
| details | Contains detailed debug messages or error logs for the developer. This field is not returned in production environments. |
| cacheTTL | Time-To-Live in seconds. Indicates how long the response is cached on the server. You may use this to manage your own caching logic. |
Account Setup & Configuration
[Registration & Activation] After KYB, we create your account and send an activation link to your `merchantAccount.email`. Follow it to access the Switch Wallet Dashboard, complete registration, and set up 2FA.
[Webhook URL] In the dashboard, set your webhook URL. We send notifications here and often require acknowledgements.
[RSA Public Key] For direct server-to-server API calls, you must generate an RSA key pair. In the Switch Wallet Dashboard, upload your public key. This allows our servers to verify requests signed with your corresponding private key.
API Authentication
Our API uses RSA signature authentication for all direct server-to-server communication. This is a stateless method ideal for automated operations that uses a public/private key pair and does not require a login session.
RSA Signature (For Direct Merchant API)
This is a stateless method ideal for automated, server-to-server communication. It uses a public/private key pair and does not require a login session. All direct Merchant API calls (e.g., transfers, queries) must use RSA signatures.
RSA Signature (Stateless Merchant API)
For direct server-to-server API calls, you must use RSA signing. This method is stateless and does not require a login session. It proves the request is from you and that the payload has not been tampered with.
Note: Your Merchant Company App Id can be found in your merchant dashboard under the API Keys section. This is the identifier you'll use in the Authorization header for all API requests.
RSA Authentication Flow
- Get Your Merchant Company App Id and API Key: Navigate to your merchant dashboard and go to the API Keys section. Copy your Merchant Company App Id and your API Key (x-api-key).
- Generate Key Pair: You must generate a standard RSA key pair. Keep your private key secret. See the section below for code samples.
- Upload Public Key: Upload your public key to the Switch Wallet merchant dashboard.
- Construct String-to-Sign: Create a canonical string by sorting the keys in your JSON request payload and joining them (e.g., `amount=5000&destinationAccount=...`).
- Sign with Private Key: Use your private key to sign the string from the previous step using the `RSA-SHA256` algorithm.
- Send Headers: Make your API request with the following headers:
Authorization: `Bearer` (Get this from your merchant dashboard API Keys section) x-api-key: Your global API key (Get this from your merchant dashboard API Keys section)x-rsa-signature: The Base64-encoded signature you just generated.x-request-timestamp: Current timestamp in milliseconds.x-nonce-string: A random 32-character string.
Generating Your RSA Key Pair
To get started with RSA authentication, you first need to generate a 2048-bit RSA key pair. Run one of the scripts below to create private_key.pem, public_key.pem, and bootstrap.env (containing base64-encoded keys). Keep the private key secure and upload the contents of the public key to your merchant dashboard.
// Save as generateKeys.js and run `node generateKeys.js`
const { generateKeyPair } = require('crypto');
const fs = require('fs');
generateKeyPair('rsa', {
modulusLength: 2048,
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
}, (err, publicKey, privateKey) => {
if (err) { return console.error(err); }
// Write PEM files
fs.writeFileSync('public_key.pem', publicKey);
fs.writeFileSync('private_key.pem', privateKey);
// Convert to base64 and create bootstrap.env
const privateB64 = Buffer.from(privateKey).toString('base64').replace(/\n/g, '');
const publicB64 = Buffer.from(publicKey).toString('base64').replace(/\n/g, '');
const bootstrapEnv = `# Base64-encoded key material (generated ${new Date().toISOString()})
RSA_MERCHANT_PRIVATE_KEY_B64=\"${privateB64}\"
RSA_MERCHANT_PUBLIC_KEY_B64=\"${publicB64}\"
`;
fs.writeFileSync('bootstrap.env', bootstrapEnv);
console.log('✓ Public and private key files generated!');
console.log('✓ bootstrap.env created with base64-encoded keys');
});RSA Signature Generation
⚠️ Important: Nested Object Handling
When your payload contains nested objects (like proxyKYCDetails),
you must convert them to JSON strings during signature generation. The server expects the exact same string format
for verification. See the examples below for proper implementation.
💡 Pro Tip: Include All Fields
Always include all required fields in your payload (even if they're false or null).
The server may add default values during validation, which will cause signature verification to fail if not included in your original payload.
💡 Using Base64 Keys from .env
You can load your private key from base64 format stored in your .env file (from bootstrap.env).
This is more secure and convenient for production environments.
// Node.js Example for RSA Signature
const crypto = require('crypto');
const fs = require('fs');
require('dotenv').config(); // Load .env file
// Load private key from base64 in .env file
const privateKeyB64 = process.env.RSA_MERCHANT_PRIVATE_KEY_B64;
const privateKey = Buffer.from(privateKeyB64, 'base64').toString('utf-8');
// Alternative: Load from PEM file (if not using .env)
// const privateKey = fs.readFileSync('path/to/your_private_key.pem', 'utf-8');
function generateRsaSignature(privateKey, payload) {
// Create the exact same sorted string from the payload.
// Filter out null/undefined values and handle nested objects.
const stringToSign = Object.keys(payload)
.filter(key => payload[key] != null && payload[key] !== '')
.sort()
.map(key => {
const value = payload[key];
// Handle nested objects by converting them to JSON strings
if (typeof value === 'object' && value !== null) {
return `${key}=${JSON.stringify(value)}`;
}
return `${key}=${value}`;
})
.join('&');
// Sign the string with your private key.
const signature = crypto
.createSign('RSA-SHA256')
.update(stringToSign)
.sign(privateKey, 'base64');
return signature;
}
// Example Usage:
const requestPayload = {
firstName: 'John',
lastName: 'Doe',
email: '[email protected]',
countryCode: 'NG',
phoneNumber: '08012345678',
proxyKYCDetails: {
idCardType: 'PASSPORT',
idCardNumber: 'A12345678',
countryOfIssue: 'NG'
}
};
// Generate nonce for header
const nonceStr = crypto.randomBytes(16).toString('hex');
const rsaSignature = generateRsaSignature(privateKey, requestPayload);
// Send this rsaSignature in the 'x-rsa-signature' header.
Verifying Webhooks
To ensure the security and integrity of data sent to your webhook URL, all outgoing requests from Switch Wallet are signed. We include the X-Webhook-Secret and X-Webhook-Signature headers on every webhook request. You must verify this signature using the Switch Wallet Public Key, which is available for you to copy from your merchant dashboard.
This process guarantees that the webhook was sent by Switch Wallet and that its content has not been altered. The secret header allows you to quickly reject any request that does not match the value configured in your dashboard.
Webhook Verification Flow
- Get Public Key: Copy the Switch Wallet Public Key from your merchant dashboard.
- Receive Webhook: Your server receives a POST request at your configured webhook URL with JSON body and headers
X-Webhook-Secret,X-Webhook-Signature,X-Webhook-Event-Type, andX-Webhook-Timestamp. - Validate Secret: Compare the
X-Webhook-Secretheader with the webhook secret configured in your dashboard. If it does not match, immediately reject the request. - Extract Signature & Body: Read the raw request body bytes (before JSON parsing) and get the base64-encoded signature from the
X-Webhook-Signatureheader. - Verify Signature: Use the Switch Wallet Public Key to verify that the signature matches the raw request body using RSA-SHA256. If it's valid, process the webhook. Otherwise, discard the request.
Important: Use Raw Body for Verification
⚠️ Critical for Webhook Verification:
When validating the RSA signature, you must verify against the exact raw request body bytes that Switch Wallet sent, not a re-serialized JSON string. Most frameworks provide a way to access the raw body (e.g. request.rawBody, request.get_data(), or php://input).
Switch Wallet signs the raw JSON payload with RSA-SHA256 using our private key; you should verify that signature with the public key exposed in your dashboard.
Webhook Verification Examples
// Node.js Example for Webhook Verification
const crypto = require('crypto');
function verifyWebhookSignature(publicKey, payload) {
const { signature, ...dataToVerify } = payload;
if (!signature) return false;
const stringToVerify = Object.keys(dataToVerify)
.filter(key => dataToVerify[key] != null && dataToVerify[key] !== '')
.sort()
.map(key => {
const value = dataToVerify[key];
if (typeof value === 'object' && value !== null) {
return `${key}=${JSON.stringify(value)}`;
}
return `${key}=${value}`;
})
.join('&');
const isVerified = crypto
.createVerify('RSA-SHA256')
.update(stringToVerify)
.verify(publicKey, signature, 'base64');
return isVerified;
}
// Example Usage in an Express.js route
app.post('/webhook', (req, res) => {
const switchPublicKey = '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----'; // From your dashboard
const isValid = verifyWebhookSignature(switchPublicKey, req.body);
if (isValid) {
console.log('Webhook verified successfully.');
res.status(200).send('OK');
} else {
console.error('Webhook verification failed.');
res.status(400).send('Invalid signature');
}
});Webhook Events
Switch Wallet sends webhooks to your configured webhook URL for various events. All webhooks are signed with RSA-SHA256 and include authentication headers. Below are all the webhook events you can expect to receive.
Wallet.ManualRemittanceSweepCreated
Sent when a manual remittance sweep request is created. This occurs when an admin or merchant creates a sweep list item with a transaction hash.
When it's sent:
- When a sweep list item is created with a valid transaction hash
- Only if
notifyMerchantis set totrue - After successful validation of the transaction hash
Wallet.RemittanceSweepStatusUpdated
Sent when a remittance sweep status is updated (e.g., when a sweep is completed or fails). This webhook is sent automatically by AsoRock when processing sweeps.
When it's sent:
- When a sweep is completed successfully (status: COMPLETED)
- When a sweep fails (status: FAILED)
- Includes detailed error information if the sweep failed
HTTPS Request Samples
Below are sample code examples showing how to make authenticated HTTPS requests to the Merchant API. These examples demonstrate the proper headers and authentication flow required for all merchant endpoints using RSA signatures.
Required Headers
All merchant API requests require the following headers for authentication and security:
| Header | Type | Description | Example |
|---|---|---|---|
| Accept | string | Content type for the response | application/json |
| x-api-key | string | API key for general authorization | your_api_key_here |
| Authorization | string | Bearer token containing your Merchant Company App Id (found in merchant dashboard API Keys section) | Bearer YOUR_MERCHANT_COMPANY_APP_ID |
| x-rsa-signature | string | Base64-encoded RSA signature for request integrity | generated_rsa_signature |
Request Sample Code
Here's a complete example of how to make authenticated requests to the Merchant API using RSA signatures:
// Node.js Example - Merchant API Request with RSA Signature
const crypto = require('crypto');
const fs = require('fs');
const axios = require('axios');
const generateRsaSignature = (privateKey, payload) => {
// Create the exact same sorted string from the payload
const stringToSign = Object.keys(payload)
.filter(key => payload[key] != null && payload[key] !== '')
.sort()
.map(key => `${key}=${payload[key]}`)
.join('&');
// Sign the string with your private key
const signature = crypto
.createSign('RSA-SHA256')
.update(stringToSign)
.sign(privateKey, 'base64');
return signature;
};
const makeMerchantRequest = async (
baseUrl,
method,
url,
payload,
merchantCompanyAppId,
apiKey,
privateKey
) => {
try {
const rsaSignature = generateRsaSignature(privateKey, payload);
const headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'x-api-key': apiKey,
'Authorization': `Bearer ${merchantCompanyAppId}`,
'x-rsa-signature': rsaSignature,
};
const config = {
method: method.toLowerCase(),
url: `${baseUrl}${url}`,
headers: headers,
data: method.toLowerCase() !== 'get' ? payload : undefined
};
const response = await axios(config);
return response.data;
} catch (error) {
console.error(`Error during request to ${url}:`, error.response?.data || error.message);
throw error;
}
};
// Example usage
const exampleRequest = async () => {
const privateKey = fs.readFileSync('path/to/your_private_key.pem', 'utf-8');
const merchantId = 'your_merchant_id';
const apiKey = 'your_api_key';
const baseUrl = 'https://api-solid-staging.switchwallet.io';
// Example: Get merchant profile
const payload = {
requestTime: Date.now()
};
// Generate nonce for header
const nonceStr = crypto.randomBytes(16).toString('hex');
const response = await makeMerchantRequest(
baseUrl,
'GET',
'/api/Merchant/profile',
payload,
merchantId,
apiKey,
privateKey,
'NG'
);
console.log('Response:', response);
};
Header Explanations
Authentication Headers
- x-api-key: General API authorization key required for all requests
- Authorization: Bearer token containing your Merchant Company App Id (found in merchant dashboard API Keys section)
- x-rsa-signature: Base64-encoded RSA signature for request integrity verification
- x-request-timestamp: Current timestamp in milliseconds for request freshness
- x-nonce-string: Unique random string for each request to prevent replay attacks
Security & Context Headers
- Accept: Specifies the expected response format (application/json)
- Content-Type: Specifies the request body format (application/json)
Pagination
Endpoints that return a list of items are paginated. You can control the pagination using the following parameters in your request payload.
| Parameter | Type | Description |
|---|---|---|
| pageId | number | The page number you want to retrieve. Starts at 1. |
| perPage | number | The number of items to return per page. |
| sort | string | The sort order. Accepts ASC for ascending or DESC for descending. |
API Documentation
For a complete and interactive API reference, including all endpoints, request/response models, and the ability to try out API calls directly, please visit our API documentation.
for swagger lovers (link to the swagger docs)