Skip to main content

Understanding Signatures

Nopan requires every request and response to be digitally signed.
This ensures three critical security guarantees:

  • Authenticity - only the holder of the private key could have created the signature.
  • Integrity - any modification of the request or response payload invalidates the signature.
  • Non-repudiation - a party cannot later deny having sent a signed message.

This guide explains the concepts behind signatures so that you can better understand how they are built and verified.


What is a Digital Signature?

A digital signature is a cryptographic proof attached to an HTTP message.
It is created by applying a private key to a canonicalized message string (the signature base) and sending it along with http headers.
The recipient verifies it with the matching public key.

  • Private key : Used only by the client, must be kept secret.
  • Public key : Shared with Nopan to allow verification.
  • Signature base : Canonical representation of important HTTP fields.
  • Signature : Base64-encoded output of signing the base with your private key.
Important

The signature doesn’t change the content - it proves who sent it and that it hasn’t been tampered with.

Anatomy of a Signature

A signature in Nopan is represented by two HTTP headers:

Header
Purpose
Signature
Contains the cryptographic signature value (Base64-encoded string).
Signature-Input
Describes how the signature was constructed (components, key, timestamp).
info

We follow the RFC-9421 specification recommendations for HTTP Message Signatures. For more details, refer to this document.

Example

Signature: nopan_sig=:lP3D...kQ==:
Signature-Input: nopan_sig=("@authority" "@method" "@request-target" "content-digest" "content-type" "content-length");keyid="your-key-id";created=1766678793
  • nopan_sig is the label for the signature instance.
  • @authority @method @request-target, etc. are the inputs.
  • keyid tells Nopan which key to use to verify the signature.
  • created is a UNIX timestamp (seconds) in UTC when the signature was created.

What is the Signature Base?

The signature base is the exact string that gets signed.
It is built by combining selected HTTP headers and pseudo-headers in a strict, ordered format, each on a new line.
It's crucial that both the client and the server construct this string in the exact same way.

  • Each component follows the format `"component-name": value`
  • Component names (but not values) are always lowercased
  • Each component is placed on its own line
  • Each line is concatenated with a newline character (\n), hex value 0x0a. There is no trailing newline.

Components of HTTP signature base

Component
Description
Example
@authority
The hostname of the API server
api.nopan.com
@method
The HTTP method in uppercase
GET, POST
@request-target
The path and query string of the API endpoint
`/payments/initiate`
content-digest
The Base64-encoded SHA-256 hash of the request body, wrapped between two colon (:) characters (e.g. sha-256=:CONTENT_DIGEST:).
sha-256=:Ccp+TqpuiunH0mEWcSkYSINkTQffuny/vEyKLgg2DVs=: for `hello, world`
content-type
The media type of the request body.
application/json
content-length
The size of the request body in bytes.
12 for UTF-8 encoded `hello, world`
@status
The HTTP response status (e.g. 200)
200, 404
@signature-params
List of signature inputs used to construct the signature

Constructing @signature-params

All components are taken either from headers or pseudo headers, except for @signature-params. To construct it, take the name of the signature (nopan_sig), add a = sign, and concatenate signature inputs using ; as a separator.

Example:

"@signature-params": nopan_sig=("@authority" "@method" "@request-target" "content-digest" "content-type" "content-length");keyid="your-key-id";created=1766678793

Once the signature base is constructed 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="key";created=1766678793

Request vs Response Signatures

While the principle is the same, the components included differ:

Signature component usage

Component
Request
Response
@authority
@method
@request-target
content-digest
✓ if body not empty
✓ if body not empty
content-type
✓ if body not empty
✓ if body not empty
content-length
✓ if body not empty
✓ if body not empty
@status
@signature-params

This ensures both request authenticity and response authenticity.


Why Strict Canonicalization Matters

Even a minor change - like an extra space, uppercase method name, or different header order - would produce a completely different hash.
That’s why canonicalization rules are strict: both client and server must generate the exact same signature base to validate each other.

info

If your integration fails signature validation, the first thing to check is whether your signature base string is built identically to Nopan’s expectations.

Putting it All Together

Here’s how the signing process fits conceptually:

  1. Select required components (headers & pseudo-headers).
  2. Build the signature base string.
  3. Sign it with your private key.
  4. Attach the Signature and Signature-Input headers.
  5. Nopan validates using your public key.

And in reverse for responses: Nopan signs the response, and you validate using Nopan’s public key.