Digitally sign your API requests to ensure authenticity and data integrity
Every request sent to the SingleView B2B API Suite must include a digital signature. The signature verifies that the request originated from an authorized client and that the request payload has not been modified during transmission.
The signature is generated by hashing the request payload using the SHA-256 algorithm and encrypting the hash with the client's RSA private key. When the request is received, the SingleView platform validates the signature using the corresponding public key associated with the client's certificate.
The signature generation process consists of the following steps:
Signature field of the request payload.Only requests with a valid signature are processed by the SingleView platform.
Important information
Property | Value |
|---|---|
Algorithm | SHA256withRSA |
Character Encoding | UTF-8 |
Signature Format | Base64 |
Signed Content | Message object only |
Keystore Format |
|
Verification | Performed using the corresponding public key from the certificate |
Only the contents of the Message object should be digitally signed. Do not sign the entire request payload or include the Signature field when generating the signature.
The following example shows a request before the signature has been generated.
{
"Message": {
"OSVBeneficiaryBankDetailsRequest": {
"OSVBeneficiaryBankDetails": [
{
"NationalId": "1231512323",
"IBANAccount": "SA2012345678972123456789"
}
]
}
},
"Signature": ""
}
In this example, only the Message object is used to generate the signature.
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Enumeration;
import java.util.Locale;
import java.util.logging.Logger;
// INPUTS
String jsonPayload = """
{
"OSVBeneficiaryBankDetailsRequest": {
"OSVBeneficiaryBankDetails": [
{
"NationalId": "1231512323",
"IBANAccount": "SA2012345678972123456789"
}
]
}
}
"""; // Pass payload as a string
String keyPath = "path of file"; // .p12 | .pfx | .key | .pem
String password = "password"; // only for p12/pfx
// VALIDATION
if (jsonPayload == null || jsonPayload.isBlank()) {
throw new IllegalArgumentException("jsonPayload must not be empty");
}
if (keyPath == null || keyPath.isBlank()) {
throw new IllegalArgumentException("keyPath must not be empty");
}
// JSON PARSE + CANONICALIZE
ObjectMapper mapper = new ObjectMapper();
Object payloadObject = mapper.readValue(jsonPayload, Object.class);
ObjectMapper canonicalMapper = new ObjectMapper();
canonicalMapper.configure(
SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS,
true
);
String dataToSign = canonicalMapper.writeValueAsString(payloadObject);
// KEY LOADER (AUTO DETECT)
PrivateKey privateKey;
String lowerPath = keyPath.toLowerCase(Locale.ROOT);
if (lowerPath.endsWith(".p12") || lowerPath.endsWith(".pfx")) {
if (password == null || password.isBlank()) {
throw new IllegalArgumentException("password is required for p12/pfx");
}
KeyStore keyStore = KeyStore.getInstance("PKCS12");
char[] passwordChars = password.toCharArray();
try (FileInputStream fis = new FileInputStream(keyPath)) {
keyStore.load(fis, passwordChars);
}
privateKey = null;
Enumeration<String> aliases = keyStore.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
if (keyStore.isKeyEntry(alias)) {
privateKey = (PrivateKey) keyStore.getKey(alias, passwordChars);
break;
}
}
if (privateKey == null) {
throw new IllegalArgumentException("No private key found in keystore");
}
} else if (lowerPath.endsWith(".key") || lowerPath.endsWith(".pem")) {
String pem = Files.readString(Path.of(keyPath), StandardCharsets.UTF_8);
// Optional: PEM may include CERTIFICATE block(s)
boolean hasCert = pem.contains("-----BEGIN CERTIFICATE-----");
boolean hasPkcs8Key = pem.contains("-----BEGIN PRIVATE KEY-----");
boolean hasPkcs1Key = pem.contains("-----BEGIN RSA PRIVATE KEY-----");
if (hasCert && !hasPkcs8Key && !hasPkcs1Key) {
throw new IllegalArgumentException(
"PEM contains CERTIFICATE only. Signing requires a PRIVATE KEY."
);
}
if (hasPkcs1Key) {
throw new IllegalArgumentException(
"BEGIN RSA PRIVATE KEY (PKCS#1) is not supported by this code. Convert to PKCS#8: BEGIN PRIVATE KEY."
);
}
if (!hasPkcs8Key) {
throw new IllegalArgumentException("No supported private key block found in PEM");
}
String key = pem
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
// remove certificate blocks if present
.replaceAll("-----BEGIN CERTIFICATE-----[\\s\\S]*?-----END CERTIFICATE-----", "")
.replaceAll("\\s", "");
byte[] decoded = Base64.getDecoder().decode(key);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
privateKey = KeyFactory.getInstance("RSA").generatePrivate(spec);
} else {
throw new IllegalArgumentException("Unsupported key format");
}
// SIGN
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(privateKey);
signer.update(dataToSign.getBytes(StandardCharsets.UTF_8));
String signature = Base64.getEncoder().encodeToString(signer.sign());After generating the signature, include it in the request payload before sending the request.
{
"Message": {
...
},
"Signature": "<Base64 Encoded Signature>"
}When the request reaches the SingleView platform, the following validation steps are performed:
Message object is extracted.If signature validation fails, the request is rejected.
Message object.