Skip to main content

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).
tip

We highly recommend our clients to validate the response signatures.

Step 1: Extract Signature Headers

Every Nopan response includes:

Header
Purpose
Signature
The cryptographic signature value (Base64-encoded).
Signature-Input
Describes how the signature was built (components, key, timestamp).

Example response headers:

Signature: nopan_sig=:SflKxwRJSMe...abc123=:
Signature-Input: nopan_sig=("@status" "content-digest" "content-type" "content-length");keyid="nopan-sandbox";created=1766678793
Key identification

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:

Component
Description
Example
content-digest
Base64-encoded hash of body, wrapped in colons. Nopan responses use SHA-512 by default.
sha-512=:abc123=:
content-type
Media type of the body, without parameters (e.g. charset), lowercased.
application/json
content-length
Size of body in bytes.
123
@status
The HTTP status code.
200
@signature-params
List of inputs used to build the signature.
See Signature-Input

Example reconstructed base:

"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
tip

For responses without a body, content-digest, content-type, and content-length are omitted.

Content-Digest hash algorithm

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.

info

It is recommended to cache the public verification key and renew it every 24 hrs.

Examples verifying the signature:

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);
}
}

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:

Scenario
Recommended Action
Missing signature headers
Reject the response. Treat as untrusted.
Signature does not verify
Reject and log. Check canonicalization and use of correct public key.
Expired or revoked key
Reject. Ensure you have the latest Nopan public key.
warning

Never process financial data or trigger business logic on unverified responses.