Request Signing
Every request to Nopan must be digitally signed. This ensures authenticity, integrity, and non-repudiation of the data you send. The process relies on an asymmetric key pair (private + public key). You use your private key to sign requests; Nopan uses your registered public key to verify them.
This guide outlines how to:
- Generate public and private keys
- Share your public key with Nopan
- Construct a valid signature base
- Include your
SignatureandSignature-Inputheaders and send them with your API requests
Step 1: Generate your key pair
Before you can sign requests, you need a key pair: a private key for signing and a public key for verification. The private key remains secure on your side; the public key is shared with Nopan.
Nopan supports well-known signing algorithms that provide strong security guarantees and are widely compatible with standard cryptographic libraries. Refer to this table to see what signing algorithms we support.
Keys submitted to Nopan are valid for up to 12 months.
After this period, they must be rotated to ensure continued ability to sign and verify requests.
Nopan will notify you in advance before any key expiration.
We recommend using at least 2048-bit keys for RSA and 256-bit keys for ECDSA.
Example using ECDSA with the NIST P-384 (secp384r1) curve (a high-level algorithm choice for request signing):
# Generate the private key
openssl ecparam -name secp384r1 -genkey -noout -out private_key.pem
# Extract the public key
openssl ec -in private_key.pem -pubout -out public_key.pem
Do not reuse the same private key you used for mTLS certificate. You must generate a new key pair for request signing.
Don’t store private keys unencrypted at rest. Use a secret manager or a key management service.
Alternatively, encrypt the private key:
openssl ec -in private_key.pem -aes256 -out encrypted_key.pem
Step 2: Submit your public key to Nopan
If this is your first time setting up encryption with Nopan, send public_key.pem via email to
support@nopan.com and include the following:
- Your Organization ID
- Company name
- Technical contact
- Signing algorithm you will use for requests
Send only the public key file.
Never send your private key.
We will respond with a unique keyid (used in the signature input)
Step 3: Build the Request Signature
First, understand what is involved in signature building by following the Understanding Signatures guide.
The signature base is a canonicalized string constructed from selected components of the HTTP request.
This base string is what gets signed and verified.
Order and formatting are critical - both sides must build the base identically.
- Each component follows the format:
"<component_name>": <value>. Note double quotes around the component name, colon and a single space after the component name, and that value has no additional characters around it. - Name (but not value) of each component is brought to lower case.
- Each component is put on a separate line.
- Each line is concatenated with a newline character (\n), hex value
0x0a. There is no trailing newline.
Components of request signature base
For requests without a body, content-digest, content-type, and content-length should be omitted.
The @signature-params field is always required and explicitly lists the inputs that were used to create the signature.
Read more about how to contruct it here.
Examples
Once the request signature base is contructed it should look like this:
- Request with body
- Request without body
"@authority": api.nopan.com
"@method": POST
"@request-target": /payments/initiate
"content-digest": sha-256=:Base64EncodedHashOfBody==:
"content-type": application/json
"content-length": 123
"@signature-params": nopan_sig=("@authority" "@method" "@request-target" "content-digest" "content-type" "content-length");keyid="your-key-id";created=1766678793
"@authority": api.nopan.com
"@method": GET
"@request-target": /payments/{id}/status
"@signature-params": nopan_sig=("@authority" "@method" "@request-target");keyid="your-key-id";created=1766678793
created value is a UNIX timestamp (seconds) in UTC when the signature was created.
Step 4: Sign the request
Use your private key to sign the base string.
- Java
- JavaScript
public PrivateKey loadPrivateKey(String pemFilePath) throws Exception {
byte[] keyBytes = Files.readAllBytes(Paths.get(pemFilePath));
String keyString = new String(keyBytes, StandardCharsets.UTF_8);
// Clean the PEM string: remove header, footer, and all newlines.
String pemFormattedKey = keyString
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] decodedKey = Base64.getDecoder().decode(pemFormattedKey);
try {
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new Exception("Failed to load private key.", e);
}
}
public String signWithPrivateKey(String dataToSign, PrivateKey privateKey) throws Exception {
// 1. Get a Signature instance for the SHA512withECDSA algorithm.
// This will hash the data with SHA-512 and then sign the hash with ECDSA.
Signature signature = Signature.getInstance("SHA512withECDSA");
// 2. Initialize the Signature object for signing.
signature.initSign(privateKey);
// 3. Load the data to be signed.
signature.update(dataToSign.getBytes(StandardCharsets.UTF_8));
// 4. Generate the signature.
byte[] rawSignature = signature.sign();
// 5. Encode the raw signature in Base64 for use in HTTP headers.
return Base64.getEncoder().encodeToString(rawSignature);
}
Support for Encrypted PEMs
This implementation assumes the private key is stored as an unencrypted PEM file.
We recommend storing encrypted PEM keys (e.g. protected with -aes256), Java's built-in KeyFactory will not be able to load them. You’ll need to use a library like BouncyCastle, which can:
- Parse encrypted PEM formats
- Programmatically decrypt keys using a passphrase
This is most useful if you load keys at runtime from a secure secrets manager or encrypted file system.
const privateKeyPem = fs.readFileSync('private_key.pem', 'utf8');
// 1. Create a Sign object using the SHA512 hashing algorithm.
const signer = createSign('SHA512');
// 2. Load the data to be signed.
signer.update(signatureBase);
signer.end();
// 3. Sign the data with the private key and return in Base64 format.
// The algorithm 'SHA512withECDSA' is inferred from the key type.
const signature = signer.sign(privateKeyPem, 'base64');
const signatureEncoded = Convert.ToBase64String(signature);
return signatureEncoded;
Step 5: Attach the Headers
Each request must include:
Signature: The Base64-encoded signature value.Signature-Input: The same string as@signature-params.
Examples
Signature: nopan_sig=:<signature>:
Signature-Input: nopan_sig=("@authority" "@method" "@request-target" "content-digest" "content-type" "content-length");keyid="your-key-id";created=1766678793
At the end you will have:
curl -X POST https://api.nopan.com/payments/initiate \
-H "Authorization: Bearer <access-token>" \
-H "Idempotency-Key: 63c2e3f0-12aa-41bb-ae62-f3d91fdbb762" \
-H "Signature: nopan_sig=:<signature>:" \
-H "Signature-Input: nopan_sig=('@authority' '@method' '@request-target' 'content-digest' 'content-type' 'content-length');keyid='<key>';created=1766678793" \
-H "Content-Type: application/json" \
-d '{"clientTransactionId":"order123","paymentDetails":{"amount":100,"currency":"EUR"}}'
If the signature is missing or invalid, Nopan will return 401 Unauthorized.
See Error Handling for more details.
Supported Signing Algorithms
Allowed signing algorithms
Key Lifecycle
Request signing keys follow a predictable lifecycle from generation to rotation. Nopan will proactively notify you before your keys expire (12 months), giving you time to rotate keys without disrupting your integration.