Response Validation
Every response from Nopan is digitally signed to guarantee authenticity and integrity.
By validating the signature with Nopan’s public key, you ensure that:
- The response came from Nopan (authenticity).
- It has not been tampered with in transit (integrity).
- It cannot later be repudiated (non-repudiation).
We highly recommend our clients to validate the response signatures.
Step 1: Extract Signature Headers
Every Nopan response includes:
SignatureSignature-InputExample response headers:
Signature: nopan_sig=:SflKxwRJSMe...abc123=:
Signature-Input: nopan_sig=("@status" "content-digest" "content-type" "content-length");keyid="nopan-sandbox";created=1766678793
Nopan uses an environment-specific keyid in response signatures: nopan-sandbox for the sandbox environment and nopan-production for the production environment. This value matches the kid field in Nopan's JWKS endpoint, so you can use it to select the correct public key for verification.
Step 2: Build the Response Signature Base
Just like for requests, you must reconstruct the signature base string.
For responses, the components typically include:
Example reconstructed base:
- Response with body
- Response with no body
"content-digest": sha-512=:Base64EncodedHashOfBody==:
"content-type": application/json
"content-length": 123
"@status": 200
"@signature-params": nopan_sig=("content-digest" "content-type" "content-length" "@status");keyid="nopan-key-id";created=1766678793
"@status": 204
"@signature-params": nopan_sig=("@status");keyid="nopan-key-id";created=1766678793
For responses without a body, content-digest, content-type, and content-length are omitted.
Nopan signs responses using sha-512 by default for the content-digest header.
When verifying, always read the algorithm prefix from the header value (e.g. sha-512=:...:
or sha-256=:...:) rather than assuming a fixed algorithm.
Supported algorithms: sha-256, sha-384, sha-512.
Step 3: Verify with Nopan’s Public Key
Nopan provides you with its public verification key that could be found here.
You must use this key to verify the Base64-decoded Signature against the signature base.
It is recommended to cache the public verification key and renew it every 24 hrs.
Examples verifying the signature:
- Java
- JavaScript
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class VerifyResponse {
public static void main(String[] args) throws Exception {
// The signature base — built from the response's components.
// See understanding-signatures for the canonicalization rules.
String signatureBase = ""
+ "\"content-digest\": sha-512=:abc123==:\n"
+ "\"content-type\": application/json\n"
+ "\"content-length\": 123\n"
+ "\"@status\": 200\n"
+ "\"@signature-params\": nopan_sig=(\"content-digest\" \"content-type\" \"content-length\" \"@status\");keyid=\"nopan-key-id\";created=1766678793";
// The verbatim value of the response's Signature header.
String signatureHeader = "nopan_sig=:SflKxwRJSMe...abc123=:";
// Strip the nopan_sig=: prefix and trailing : to get the raw base64 signature.
String signatureValue = signatureHeader
.replaceFirst("^nopan_sig=:", "")
.replaceFirst(":$", "");
// Load Nopan's public key (cached from the JWKS endpoint).
String pem = Files.readString(Paths.get("nopan_public_key.pem"));
byte[] keyBytes = Base64.getDecoder().decode(pem
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", ""));
PublicKey publicKey = KeyFactory.getInstance("EC")
.generatePublic(new X509EncodedKeySpec(keyBytes));
// Nopan signs responses with P-521, so use SHA512withECDSA.
Signature verifier = Signature.getInstance("SHA512withECDSA");
verifier.initVerify(publicKey);
verifier.update(signatureBase.getBytes(StandardCharsets.UTF_8));
boolean valid = verifier.verify(Base64.getDecoder().decode(signatureValue));
System.out.println("Signature valid: " + valid);
}
}
const fs = require('fs');
const { createVerify } = require('crypto');
// The signature base — built from the response's components.
const signatureBase =
'"content-digest": sha-512=:abc123==:\n' +
'"content-type": application/json\n' +
'"content-length": 123\n' +
'"@status": 200\n' +
'"@signature-params": nopan_sig=("content-digest" "content-type" "content-length" "@status");keyid="nopan-key-id";created=1766678793';
// The verbatim value of the response's Signature header.
const signatureHeader = 'nopan_sig=:SflKxwRJSMe...abc123=:';
// Strip the nopan_sig=: prefix and trailing : to get the raw base64 signature.
const signatureValue = signatureHeader
.replace(/^nopan_sig=:/, '')
.replace(/:$/, '');
// Load Nopan's public key (cached from the JWKS endpoint).
const publicKeyPem = fs.readFileSync('nopan_public_key.pem', 'utf8');
// Nopan signs responses with P-521, so use 'SHA512'.
const verifier = createVerify('SHA512');
verifier.update(signatureBase);
verifier.end();
const valid = verifier.verify(publicKeyPem, Buffer.from(signatureValue, 'base64'));
console.log(`Signature valid: ${valid}`);
Step 4: Handle Validation Results
If the verification succeeds, you can trust the response.
If it fails, treat the response as invalid and do not process it.
- Reject unsigned or invalidly signed responses.
- Log validation failures with enough detail for debugging.
- Check that your signature base construction matches canonicalization rules.
Error Handling
When validation fails, apply the following best practices:
Never process financial data or trigger business logic on unverified responses.