Skip to main content

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 Signature and Signature-Input headers 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.

Key validity

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.

tip

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
Important

Do not reuse the same private key you used for mTLS certificate. You must generate a new key pair for request signing.

info

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:

  1. Your Organization ID
  2. Company name
  3. Technical contact
  4. Signing algorithm you will use for requests
warning

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

info

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

Component
Description
Example
@authority
The hostname of the API server.
api.nopan.com
@method
The HTTP method in uppercase.
POST
@request-target
Path and query string of the endpoint.
/payments/initiate
content-digest
Base64-encoded SHA-256 of body, wrapped in colons.
sha-256=:abc123=:
content-type
Media type of the body.
application/json
content-length
Body size in bytes.
123
@signature-params
List of inputs used to build the signature.
tip

For requests without a body, content-digest, content-type, and content-length should be omitted.

info

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:

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

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.

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.


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 - POST /payments/initiate
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"}}'
warning

If the signature is missing or invalid, Nopan will return 401 Unauthorized.

See Error Handling for more details.

Supported Signing Algorithms

Allowed signing algorithms

Algorithm name
Key family
Hash algorithm
Padding scheme
RSASSA_PKCS1_V1_5_SHA_256
RSA
SHA-256
PKCS #1 v1.5
RSASSA_PKCS1_V1_5_SHA_384
RSA
SHA-384
PKCS #1 v1.5
RSASSA_PKCS1_V1_5_SHA_512
RSA
SHA-512
PKCS #1 v1.5
ECDSA_SHA_256
EC
SHA-256
ECDSA_SHA_384
EC
SHA-384
ECDSA_SHA_512
EC
SHA-512

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.