Loading...
Loading...
Implement V2 end-to-end encryption for Paywize API requests using AES-256-GCM authenticated encryption with the official merchant SDK.
Last updated: 2026-03-16
Paywize V2 encryption uses AES-256-GCM (Galois/Counter Mode) authenticated encryption, replacing the V1 AES-256-CBC scheme. AES-256-GCM provides both confidentiality and integrity verification in a single pass, ensuring that encrypted data cannot be tampered with in transit.
V2 encryption is the current recommended encryption standard for all new Paywize API integrations. It uses the official Paywize Merchant SDK for encryption and decryption, removing the need for manual cryptographic implementations. If you are starting a new integration, use V2.
| Attribute | Details |
|---|---|
| Encryption Algorithm | AES-256-GCM |
| Key Derivation | SHA-256 hash of Secret Key (32 bytes) |
| Nonce | 12 bytes, randomly generated per request |
| Auth Tag | 16 bytes (128-bit) |
| Output Format | base64( nonce || ciphertext || authTag ) |
| Data Format | JSON before encryption |
| SDK Required | Yes -- official Paywize Merchant Crypto SDK |
| Security Level | Authenticated encryption (AEAD) |
Install the official Paywize Merchant Crypto SDK for your platform:
npm install @paywize/merchant-crypto
pip install paywize-crypto
Add the following dependency to your pom.xml:
<dependency>
<groupId>com.paywize</groupId>
<artifactId>merchant-crypto</artifactId>
<version>1.0.0</version>
</dependency>
Or for Gradle:
implementation 'com.paywize:merchant-crypto:1.0.0'
The V2 encrypted API flow consists of three steps: obtain an access token, encrypt and send your payload, then decrypt the response.
Authenticate with the Paywize API by sending your apiKey and secretKey to the token endpoint. The response data field contains an encrypted JWT -- you must decrypt it using the SDK before use.
Authentication Required: None (this endpoint issues the token)
Content-Type: application/json
{
"apiKey": "your_api_key_here",
"secretKey": "your_secret_key_here"
}
The /auth/client-token endpoint accepts the raw (unencrypted) apiKey and secretKey in the request body. The response data field is encrypted and must be decrypted to retrieve the JWT.
{
"respCode": 2000,
"respMessage": "Token generated successfully",
"data": "nqiNrwBEk770qbQ3tyn1MY2QzYSa0N8qmGSp/W0AtOs8...",
"expiresIn": 300,
"tokenType": "Bearer"
}
The data field is an encrypted string. Use the SDK to decrypt it and extract the JWT:
import PaywizeCrypto from '@paywize/merchant-crypto';
const crypto = new PaywizeCrypto('YOUR_SECRET_KEY');
async function getAccessToken(apiKey, secretKey) {
const response = await fetch('https://merchant.paywize.in/v1/auth/client-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ apiKey, secretKey })
});
const result = await response.json();
if (result.respCode === 2000) {
// Decrypt the token from the response data
const decrypted = crypto.decryptJSON(result.data);
return decrypted.token;
}
throw new Error(`Auth failed: ${result.respMessage}`);
}
import requests
from paywize_crypto import PaywizeCrypto
crypto = PaywizeCrypto("YOUR_SECRET_KEY")
def get_access_token(api_key, secret_key):
response = requests.post(
"https://merchant.paywize.in/v1/auth/client-token",
headers={"Content-Type": "application/json"},
json={"apiKey": api_key, "secretKey": secret_key}
)
result = response.json()
if result["respCode"] == 2000:
# Decrypt the token from the response data
decrypted = crypto.decrypt_json(result["data"])
return decrypted["token"]
raise Exception(f"Auth failed: {result['respMessage']}")
import com.paywize.crypto.PaywizeCrypto;
import java.net.http.*;
import java.net.URI;
PaywizeCrypto crypto = new PaywizeCrypto("YOUR_SECRET_KEY");
public String getAccessToken(String apiKey, String secretKey) throws Exception {
String body = String.format(
"{\"apiKey\":\"%s\",\"secretKey\":\"%s\"}", apiKey, secretKey
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://merchant.paywize.in/v1/auth/client-token"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
// Parse response and decrypt the data field
// Using your preferred JSON library (Jackson, Gson, etc.)
JSONObject result = new JSONObject(response.body());
if (result.getInt("respCode") == 2000) {
String decrypted = crypto.decrypt(result.getString("data"));
JSONObject tokenData = new JSONObject(decrypted);
return tokenData.getString("token");
}
throw new Exception("Auth failed: " + result.getString("respMessage"));
}
Encrypt your request payload using the SDK, then send it as a payload field with a Bearer token.
All authenticated API requests must follow this structure:
POST /your/api/endpoint HTTP/1.1
Content-Type: application/json
Authorization: Bearer <jwt_token>
{
"payload": "<encrypted_string>"
}
import PaywizeCrypto from '@paywize/merchant-crypto';
const crypto = new PaywizeCrypto('YOUR_SECRET_KEY');
async function sendEncryptedRequest(endpoint, data, token) {
// Encrypt the payload
const encryptedPayload = crypto.encryptJSON(data);
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ payload: encryptedPayload })
});
return response.json();
}
// Example: Initiate a payout
const payoutData = {
sender_id: "unique_txn_id_001",
wallet_id: "PAYWIZE12345679",
payment_mode: "IMPS",
beneficiary: {
beneficiary_name: "Jane Doe",
beneficiary_ifsc: "HDFC0123456",
beneficiary_acc_number: "123456789012"
},
amount: "1000.00",
remarks: "Payment",
callback_url: "https://your-website.com/webhook/payout"
};
const result = await sendEncryptedRequest(
'https://merchant.paywize.in/payout/v1/initiate',
payoutData,
accessToken
);
from paywize_crypto import PaywizeCrypto
import requests
crypto = PaywizeCrypto("YOUR_SECRET_KEY")
def send_encrypted_request(endpoint, data, token):
# Encrypt the payload
encrypted_payload = crypto.encrypt_json(data)
response = requests.post(
endpoint,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {token}"
},
json={"payload": encrypted_payload}
)
return response.json()
# Example: Initiate a payout
payout_data = {
"sender_id": "unique_txn_id_001",
"wallet_id": "PAYWIZE12345679",
"payment_mode": "IMPS",
"beneficiary": {
"beneficiary_name": "Jane Doe",
"beneficiary_ifsc": "HDFC0123456",
"beneficiary_acc_number": "123456789012"
},
"amount": "1000.00",
"remarks": "Payment",
"callback_url": "https://your-website.com/webhook/payout"
}
result = send_encrypted_request(
"https://merchant.paywize.in/payout/v1/initiate",
payout_data,
access_token
)
import com.paywize.crypto.PaywizeCrypto;
PaywizeCrypto crypto = new PaywizeCrypto("YOUR_SECRET_KEY");
public String sendEncryptedRequest(String endpoint, String jsonData, String token)
throws Exception {
// Encrypt the payload
String encryptedPayload = crypto.encrypt(jsonData);
String body = String.format("{\"payload\":\"%s\"}", encryptedPayload);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token)
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
return response.body();
}
API responses return an encrypted data field. Use the SDK to decrypt it.
{
"respCode": 2000,
"respMessage": "Success",
"data": "base64_encrypted_response_string_here..."
}
// Decrypt the response data field
function handleResponse(apiResponse) {
if (apiResponse.respCode === 2000) {
const decryptedData = crypto.decryptJSON(apiResponse.data);
console.log('Decrypted response:', decryptedData);
return decryptedData;
}
throw new Error(`API Error ${apiResponse.respCode}: ${apiResponse.respMessage}`);
}
# Decrypt the response data field
def handle_response(api_response):
if api_response["respCode"] == 2000:
decrypted_data = crypto.decrypt_json(api_response["data"])
print("Decrypted response:", decrypted_data)
return decrypted_data
raise Exception(
f"API Error {api_response['respCode']}: {api_response['respMessage']}"
)
// Decrypt the response data field
public JSONObject handleResponse(String responseBody) throws Exception {
JSONObject apiResponse = new JSONObject(responseBody);
if (apiResponse.getInt("respCode") == 2000) {
String decrypted = crypto.decrypt(apiResponse.getString("data"));
return new JSONObject(decrypted);
}
throw new Exception(String.format(
"API Error %d: %s",
apiResponse.getInt("respCode"),
apiResponse.getString("respMessage")
));
}
Full end-to-end integration examples for each language.
import PaywizeCrypto from '@paywize/merchant-crypto';
// Configuration
const API_KEY = process.env.PAYWIZE_API_KEY;
const SECRET_KEY = process.env.PAYWIZE_SECRET_KEY;
const BASE_URL = 'https://merchant.paywize.in';
// Initialize the SDK
const crypto = new PaywizeCrypto(SECRET_KEY);
// Step 1: Get Access Token
async function getAccessToken() {
const response = await fetch(`${BASE_URL}/v1/auth/client-token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ apiKey: API_KEY, secretKey: SECRET_KEY })
});
const result = await response.json();
if (result.respCode !== 2000) {
throw new Error(`Auth failed: ${result.respMessage}`);
}
const tokenData = crypto.decryptJSON(result.data);
return tokenData.token;
}
// Step 2: Encrypt & Send Request
async function makeApiRequest(endpoint, data, token) {
const encryptedPayload = crypto.encryptJSON(data);
const response = await fetch(`${BASE_URL}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ payload: encryptedPayload })
});
const result = await response.json();
// Step 3: Decrypt Response
if (result.respCode === 2000 && result.data) {
return crypto.decryptJSON(result.data);
}
throw new Error(`API Error ${result.respCode}: ${result.respMessage}`);
}
// Full Integration Example
async function main() {
try {
// Authenticate
const token = await getAccessToken();
console.log('Authenticated successfully');
// Initiate a payout
const payoutData = {
sender_id: "TXN_" + Date.now(),
wallet_id: "PAYWIZE12345679",
payment_mode: "IMPS",
beneficiary: {
beneficiary_name: "Jane Doe",
beneficiary_ifsc: "HDFC0123456",
beneficiary_acc_number: "123456789012"
},
amount: "500.00",
remarks: "Payment",
callback_url: "https://your-website.com/webhook/payout"
};
const result = await makeApiRequest('/payout/v1/initiate', payoutData, token);
console.log('Payout initiated:', result.transaction_id);
console.log('Status:', result.status);
} catch (error) {
console.error('Error:', error.message);
}
}
main();
import os
import requests
from paywize_crypto import PaywizeCrypto
# Configuration
API_KEY = os.environ["PAYWIZE_API_KEY"]
SECRET_KEY = os.environ["PAYWIZE_SECRET_KEY"]
BASE_URL = "https://merchant.paywize.in"
# Initialize the SDK
crypto = PaywizeCrypto(SECRET_KEY)
# Step 1: Get Access Token
def get_access_token():
response = requests.post(
f"{BASE_URL}/v1/auth/client-token",
headers={"Content-Type": "application/json"},
json={"apiKey": API_KEY, "secretKey": SECRET_KEY}
)
result = response.json()
if result["respCode"] != 2000:
raise Exception(f"Auth failed: {result['respMessage']}")
token_data = crypto.decrypt_json(result["data"])
return token_data["token"]
# Step 2: Encrypt & Send Request
def make_api_request(endpoint, data, token):
encrypted_payload = crypto.encrypt_json(data)
response = requests.post(
f"{BASE_URL}{endpoint}",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {token}"
},
json={"payload": encrypted_payload}
)
result = response.json()
# Step 3: Decrypt Response
if result["respCode"] == 2000 and "data" in result:
return crypto.decrypt_json(result["data"])
raise Exception(f"API Error {result['respCode']}: {result['respMessage']}")
# Full Integration Example
def main():
try:
# Authenticate
token = get_access_token()
print("Authenticated successfully")
# Initiate a payout
payout_data = {
"sender_id": f"TXN_{int(__import__('time').time())}",
"wallet_id": "PAYWIZE12345679",
"payment_mode": "IMPS",
"beneficiary": {
"beneficiary_name": "Jane Doe",
"beneficiary_ifsc": "HDFC0123456",
"beneficiary_acc_number": "123456789012"
},
"amount": "500.00",
"remarks": "Payment",
"callback_url": "https://your-website.com/webhook/payout"
}
result = make_api_request("/payout/v1/initiate", payout_data, token)
print(f"Payout initiated: {result['transaction_id']}")
print(f"Status: {result['status']}")
except Exception as error:
print(f"Error: {error}")
if __name__ == "__main__":
main()
import com.paywize.crypto.PaywizeCrypto;
import java.net.URI;
import java.net.http.*;
import org.json.JSONObject;
public class PaywizeIntegration {
private static final String API_KEY = System.getenv("PAYWIZE_API_KEY");
private static final String SECRET_KEY = System.getenv("PAYWIZE_SECRET_KEY");
private static final String BASE_URL = "https://merchant.paywize.in";
// Initialize the SDK
private static final PaywizeCrypto crypto = new PaywizeCrypto(SECRET_KEY);
private static final HttpClient httpClient = HttpClient.newHttpClient();
// Step 1: Get Access Token
public static String getAccessToken() throws Exception {
String body = new JSONObject()
.put("apiKey", API_KEY)
.put("secretKey", SECRET_KEY)
.toString();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/v1/auth/client-token"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = httpClient.send(
request, HttpResponse.BodyHandlers.ofString()
);
JSONObject result = new JSONObject(response.body());
if (result.getInt("respCode") != 2000) {
throw new Exception("Auth failed: " + result.getString("respMessage"));
}
String decrypted = crypto.decrypt(result.getString("data"));
return new JSONObject(decrypted).getString("token");
}
// Step 2: Encrypt & Send Request
public static JSONObject makeApiRequest(String endpoint, JSONObject data, String token)
throws Exception {
String encryptedPayload = crypto.encrypt(data.toString());
String body = new JSONObject()
.put("payload", encryptedPayload)
.toString();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + endpoint))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token)
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = httpClient.send(
request, HttpResponse.BodyHandlers.ofString()
);
JSONObject result = new JSONObject(response.body());
// Step 3: Decrypt Response
if (result.getInt("respCode") == 2000 && result.has("data")) {
String decryptedData = crypto.decrypt(result.getString("data"));
return new JSONObject(decryptedData);
}
throw new Exception(String.format(
"API Error %d: %s",
result.getInt("respCode"),
result.getString("respMessage")
));
}
// Full Integration Example
public static void main(String[] args) {
try {
// Authenticate
String token = getAccessToken();
System.out.println("Authenticated successfully");
// Initiate a payout
JSONObject beneficiary = new JSONObject()
.put("beneficiary_name", "Jane Doe")
.put("beneficiary_ifsc", "HDFC0123456")
.put("beneficiary_acc_number", "123456789012");
JSONObject payoutData = new JSONObject()
.put("sender_id", "TXN_" + System.currentTimeMillis())
.put("wallet_id", "PAYWIZE12345679")
.put("payment_mode", "IMPS")
.put("beneficiary", beneficiary)
.put("amount", "500.00")
.put("remarks", "Payment")
.put("callback_url", "https://your-website.com/webhook/payout");
JSONObject result = makeApiRequest("/payout/v1/initiate", payoutData, token);
System.out.println("Payout initiated: " + result.getString("transaction_id"));
System.out.println("Status: " + result.getString("status"));
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
}
| Error | Cause | Solution |
|---|---|---|
| Ciphertext too short | Truncated or malformed encrypted string | Verify the full base64 string is being transmitted. Check for truncation by proxies, gateways, or string length limits. |
| Auth tag verification failed | Data was modified in transit or encrypted with a different key | Re-encrypt the payload. Check for proxy or WAF modifications to the request body. |
secretKey is required | SDK initialized without a secret key | Verify your credentials are loaded correctly from environment variables. |
| Decryption returns garbled text | Decryption attempted with the wrong secret key | Confirm you are using the correct secret key that matches your API key. |
| Error | Cause | Solution |
|---|---|---|
4001 | Unauthorized access | Invalid API key or secret key. Verify your credentials. |
| Error | Cause | Solution |
|---|---|---|
4014 | Decryption failed on server | The server could not decrypt your payload. Verify you are using the correct secret key and that the SDK version is up to date. |
@paywize/merchant-crypto, paywize-crypto, or com.paywize:merchant-cryptoconst crypto = new PaywizeCrypto('YOUR_SECRET_KEY');
const original = { test: "hello" };
const encrypted = crypto.encryptJSON(original);
const decrypted = crypto.decryptJSON(encrypted);
console.assert(
JSON.stringify(original) === JSON.stringify(decrypted),
'Roundtrip test failed'
);
| Language | Syntax | Description |
|---|---|---|
| JavaScript | new PaywizeCrypto(secretKey) | Initialize with your Paywize secret key |
| Python | PaywizeCrypto(secret_key) | Initialize with your Paywize secret key |
| Java | new PaywizeCrypto(secretKey) | Initialize with your Paywize secret key |
The constructor takes your secret key and derives the 32-byte AES-256 encryption key internally using SHA-256.
| Method (JS) | Method (Python) | Method (Java) | Description |
|---|---|---|---|
encrypt(plaintext) | encrypt(plaintext) | encrypt(plaintext) | Encrypt a string. Returns a base64-encoded string containing nonce || ciphertext || authTag. |
decrypt(ciphertext) | decrypt(ciphertext) | decrypt(ciphertext) | Decrypt a base64-encoded string. Returns the original plaintext string. |
encryptJSON(object) | encrypt_json(dict) | encryptJSON(jsonString) | Serialize a JSON object/dict to string and encrypt it. Returns a base64-encoded string. |
decryptJSON(ciphertext) | decrypt_json(ciphertext) | decryptJSON(ciphertext) | Decrypt a base64-encoded string and parse the result as JSON. Returns an object/dict. |
const crypto = new PaywizeCrypto('SECRET_KEY');
// String encryption
const encrypted = crypto.encrypt('plaintext string');
const decrypted = crypto.decrypt(encrypted);
// JSON encryption (serializes and deserializes automatically)
const encryptedJson = crypto.encryptJSON({ key: 'value' });
const decryptedJson = crypto.decryptJSON(encryptedJson);
// decryptedJson => { key: 'value' }
crypto = PaywizeCrypto("SECRET_KEY")
# String encryption
encrypted = crypto.encrypt("plaintext string")
decrypted = crypto.decrypt(encrypted)
# JSON encryption (serializes and deserializes automatically)
encrypted_json = crypto.encrypt_json({"key": "value"})
decrypted_json = crypto.decrypt_json(encrypted_json)
# decrypted_json => {"key": "value"}
PaywizeCrypto crypto = new PaywizeCrypto("SECRET_KEY");
// String encryption
String encrypted = crypto.encrypt("plaintext string");
String decrypted = crypto.decrypt(encrypted);
// JSON encryption
String encryptedJson = crypto.encryptJSON("{\"key\":\"value\"}");
String decryptedJson = crypto.decryptJSON(encryptedJson);
// decryptedJson => "{\"key\":\"value\"}"
If you are migrating from V1 encryption, note the following key differences:
crypto / Crypto.Cipher implementations. Install the SDK for your platform and remove your custom encryption code.SHA-256(secretKey) and generates a random 12-byte nonce per request./v1/auth/client-token (singular). Verify your token endpoint URL.base64(ciphertext) to base64(nonce || ciphertext || authTag). The SDK handles this automatically.For V1 documentation, see Encryption V1 (AES-256-CBC).