> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fintoc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Generate JWS keys

> Generate a JWS key pair to sign requests that move money on Fintoc's legacy v2023-11-15 Transfers API, keeping outbound money flows secure across banks.

Requests to Transfers endpoints that move money require a JSON Web Signature (JWS). The signature verifies the integrity and authenticity of the request. Protected actions include creating outbound transfers and returning inbound transfers.

# Generate JWS keys

Generate a key pair, upload the public key, and use the private key to sign each protected request.

## Generate a pair of public and private JWS keys

Run these commands in your terminal:

```bash theme={null}
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -outform PEM -pubout -out public_key.pem.pub
```

This generates two files:

* `private_key.pem`, which contains your private key.
* `public_key.pem.pub`, which contains your public key.

Each key should look similar to the following examples:

```text public_key.pem theme={null}
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoPPSwkMAHrLy6ZY+cOIP
jl6PxkrKJBicwMBMgFPf0Vtqe6QWepeOWXQuLgW+cSDI0KBjk8eZQEVB7GY3OwOl
DcknxUkaVueEvsDiY74xeC1iN2Gfb6HXd2JqgDWdWy/HNv2eUe9kmsSPSSgruA8Y
DvR6lpjPvAEJHP4Sg/B+9c0gBTDmqadL8UD291D7JbHmG4lIBT5NbhpOVnSBN0aC
R6ioxWz+VJoz68qsxHQ69TYhl8/jG79ocvZsZEWCWc/Kv7SP6/cPJHu0oGWVZwa4
5BtPLeMQ9ZleHdV6RCUbxFXKzbZF5fKQ+z5NWk+hMz5TCs4jwmg1nodWyW+bL7K9
YQIDAQAB
-----END PUBLIC KEY-----
```

```text private_key.pem theme={null}
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDEbZHV0ODosbFn
BqcUYiRhlQWEJ9V9s5WfuFMv0n9hNWNFSBYiup+yuCNVW11vPQ7h6VUAWP8LFgmc
jsyjATlIsJDFDXXOQMyxAS2is2vZpot5QblcJMUxxMmAkUCJtpo7QSLKEPk2gRu4
tsQpzqnxCYDCnJOAHcClNrMIVJuczn/GMCm68IcAeNP8HQy9D20ER+w1tONoIzJ3
bQF0WkbtgJMYoQAq6UL3q0ws7ODvXo/ZcrFrY7q4skJopI+pz3qsGeSJX0PWjH4a
o+C9n5uYRXBxtH3nhnkSQdRZCk9fzId9+ObKrMKYuDxBeFqFIjZEh4pZ9lCPaHGK
qHwv3nLFAgMBAAECggEAHJnRpr7ryKX67UPoNw0VPAotS/Fa4hsweZmmrytotbhG
1JMq+fKPhz/NkUOk5qoOzTEi2dKbjDswuhWG0WM/uohPBAoyMY5433sK8IpMdVwN
KeI6gaKu/dCoAGrl6UdnzKHu1VpEVz3UUgB2rpmzX+/gyjVvOrPaVZQR3HApWlrr
qa/hmsiisCD1Bj7RreVAC6XfZCr8iyomNmX/Hz7Vx6s3HlBHzvQtxkwjjAPYu4SW
VsDAZESJNcOrZ8xNervaR3X+lLaF1DS/ntawPz6alAb46IyVgFU0K+rJn47MOTsi
gNSZqHV1TtTu7+jPkbF+LHHBYpYb4CSqyU41LKhDXwKBgQDk9smFRPjuSUiMGfI2
FCqwUmiYlAaEOHFLhvCapq2GfPjj1rIJMtqwox9fqKvHmTdiLjQ9+esyAbEG3CIW
njgsUsZspQOmCOqjuk0GsF9nsl/blzZrsytyN54xoRSECuecId6m31pQyJxoG1Y4
wqqAVObnepWb311xLIx/RI7NowKBgQDbn0WAuOv7JsXn/0CcZfzsbGRIMEkDLYe4
KiVOWALd6MijqtgyzRRwz2Qu7+AEfs0i4A0ZIOInZ2y5qkTeqpMNtqM9A4q5YClB
XTt/vUFxCLekL0s+zqXH9D87sTqEtTVNHpyJem3velXPDlSwR6MNyK0TRR7izBAg
mfYYSWV0dwKBgH6RfazSB9mRYS0xWpdSZpa5t2BA06lbmiVqHq8e3GWvx9YK5Lf5
CLMEOV+j2fGoXNlFOVPZR46JKNbl8WIXbG30BAQi4/VwkGSZo+LCtLqZ/CtjV44J
qUamQCinJrQnYwkIIBCW/1IQ04UpN2yBD8eJJ2tmdDWKMBlTywa/W0GJAoGBAJ12
WFKuQyNS7Vok/KIlzW2FWXEYjYClyEUWkqDVIVkRaalO+KuTtjAbweyVN7yBXXq/
wSRfG0a9NIr5tV8gVUbjx64bN/8pHusqeVpgyubMJT6mWgCyENKIID4gF6DGe2zL
odg/20p0H8nQsI+jDRj45H6IdFiPjpCRUoyfMwqJAoGBAJawh68qY9eQYWPwSq4g
S1RnrSmiamTm3zPqzeEZKcZOfrQz+W8q3Woq90HkupglXspL6bar6rZnuhcqY1xU
5m7IDq3fFRjfvsNDft5YJrtLFsJ/Uu6UHVnOa9K53aUJczMZ3CetqhJZmYvR9BPV
Yfo0J11kYXclnb1wH0svjZEA
-----END PRIVATE KEY-----
```

<Warning>
  **Never share your JWS private key**

  Keep your JWS private key confidential. Fintoc never asks for this key. Anyone with access to the private key can sign transfer requests on your behalf.
</Warning>

## Upload your public key to the dashboard

1. Open [dashboard.fintoc.com](https://dashboard.fintoc.com).
2. Select **API Keys** in the sidebar.
3. If your organization has a product that requires JWS, find the **JWS Public Keys** section. Select **Add JWS Key**, and upload your JWS public key.

# Generate a signature

After you upload your public key to the Fintoc dashboard, use your private key to generate a signature. The signature verifies the integrity and authenticity of each Transfers API request that moves money.

## Use an SDK

The [Python SDK](https://github.com/fintoc-com/fintoc-python) and [Node SDK](https://github.com/fintoc-com/fintoc-node) generate signatures for you. Initialize the Fintoc client with your private key:

```python theme={null}
import os

from fintoc import Fintoc

# Provide a path to your PEM file
client = Fintoc("your_api_key", jws_private_key="private_key.pem")

# Or pass the PEM key directly as a string
client = Fintoc("your_api_key", jws_private_key=os.environ.get('JWS_PRIVATE_KEY'))

# You can now create transfers securely
```

```javascript Node theme={null}
const { Fintoc } = require('fintoc');

// Provide a path to your PEM file
const fintoc = new Fintoc('your_api_key', 'private_key.pem');

// Or pass the PEM key directly as a string
const fintoc = new Fintoc('your_api_key', process.env.JWS_PRIVATE_KEY);

// You can now create transfers securely
```

## Follow a step-by-step example

Follow these steps to write your own implementation or use a different programming language.

### Prepare the payload

Generate the JWS signature from the exact JSON string that you send in the HTTP request. Use your language's JSON serializer to convert the outbound transfer request body to a JSON string.

```python theme={null}

import json

body = { ... } # your request body

raw_body = json.dumps(body)
```

```javascript theme={null}
const body = { ... } // your request body

const rawBody = JSON.stringify(body)
```

```ruby theme={null}
require 'json'

body = { ... } # your request body

raw_body = body.to_json
```

<Info>
  **Use the same JSON string**

  Use the same serialized JSON string for both actions:

  1. Create the JWS signature.
  2. Send the payload in your HTTP request.

  Any difference between the signed JSON and the HTTP request payload can invalidate the signature.
</Info>

### Load the private key and configure headers

Load your private key from the PEM file and configure the JWS headers. Include the `RS256` signing algorithm, a unique [nonce](https://en.wikipedia.org/wiki/Nonce), the current Unix timestamp, and the critical header names.

```python theme={null}
# load the private key
with open('./private_key.pem', 'rb') as f:
    private_key = serialization.load_pem_private_key(
        f.read(),
        password=None
    )

# define jws headers
headers = {
    "alg": "RS256",                 # signing algorithm. must be "RS256"
    "nonce": secrets.token_hex(16), # unique string for each request
    "ts": int(time.time()),         # timestamp of the request
    "crit": ["ts", "nonce"]         # critical headers
}
```

```javascript theme={null}
// load the private key
const privateKey = readFileSync('./private_key.pem', 'utf8');

// define jws headers
const headers = {
  alg: 'RS256',                                  // signing algorithm. must be "RS256"
  nonce: crypto.randomBytes(16).toString('hex'), // unique string for each request
  ts: Math.floor(Date.now() / 1000),            // timestamp of the request
  crit: ['ts', 'nonce']                         // critical headers
};
```

```ruby theme={null}
# load the private key
private_key = OpenSSL::PKey::RSA.new(File.read('./private_key.pem'))

# define jws headers
headers = {
  'alg' => 'RS256',                # signing algorithm. must be "RS256"
  'nonce' => SecureRandom.hex(16), # unique string for each request
  'ts' => Time.now.to_i,           # timestamp of the request
  'crit' => ['ts', 'nonce']        # critical headers
}
```

#### Prevent a replay attack

Fintoc uses the `nonce` and `ts` headers to protect requests against [replay attacks](https://en.wikipedia.org/wiki/Replay_attack). The headers also help verify request integrity.

Set `nonce` to a unique, random value for every request. This value makes each signature distinct, even when you send the same data more than once. Fintoc rejects a request if its `nonce` was already used.

Set `ts` to the Unix timestamp when you create the request. Fintoc rejects requests outside a two-minute window.

Together, `nonce` and `ts` prevent an intercepted request from being reused.

### Generate the signing input

Create the JWS signing input by joining the base64url-encoded `headers` and `raw_body` with a period (`.`). Encode both values without padding:

```python theme={null}
# generate the procted section of the jws by base64 encoding the headers without padding
protected_base64 = base64.urlsafe_b64encode(
  json.dumps(headers).encode()
).rstrip(b'=').decode()

# generate the payload section of the jws by base64 encoding the raw_body without padding
payload_base64 = base64.urlsafe_b64encode(
  raw_body.encode()
).rstrip(b'=').decode()

# generate the signature input by concatenating the encoded protected and payload components
signing_input = f"{protected_base64}.{payload_base64}"
```

```javascript theme={null}
// generate the procted section of the jws by base64 encoding the headers without padding
const protectedBase64 = Buffer.from(JSON.stringify(headers))
  .toString('base64url');
  
// generate the payload section of the jws by base64 encoding the raw_body without padding
const payloadBase64 = Buffer.from(rawBody)
  .toString('base64url');

// generate the signature input by concatenating the encoded protected and payload components
const signingInput = `${protectedBase64}.${payloadBase64}`;
```

```ruby theme={null}
# generate the procted section of the jws by base64 encoding the headers without padding
protected_base64 = Base64.urlsafe_encode64(headers.to_json, padding: false)

# generate the payload section of the jws by base64 encoding the raw_body without padding
payload_base64 = Base64.urlsafe_encode64(raw_body, padding: false)

# generate the signature input by concatenating the encoded protected and payload components
signing_input = "#{protected}.#{payload_base64}"
```

### Generate the JWS token signature

Create the cryptographic signature from the signing input and your private key:

1. Sign the input using your private key with:

   1. RSA with PKCS1v15 padding
   2. SHA-256 hashing
2. Base64URL-encode the resulting signature (without padding)

```python theme={null}
# generate the raw signature by signing the signing_input 
signature_raw = private_key.sign(
     signing_input.encode(),
     padding.PKCS1v15(),
     hashes.SHA256()
)

# base64 encode the raw signature without padding
signature_base64 = base64.urlsafe_b64encode(signature_raw).rstrip(b'=').decode()
```

```javascript theme={null}
// generate the raw signature by signing the signing_input 
const signatureRaw = crypto.createSign('sha256')
  .update(signingInput)
  .sign({
    key: privateKey,
    padding: crypto.constants.RSA_PKCS1_PADDING
  });

// base64 encode the raw signature without padding
const signatureBase64 = Buffer.from(signatureRaw)
    .toString('base64url');
```

```ruby theme={null}
# generate the raw signature by signing the signing_input 
signature_raw = private_key.sign(OpenSSL::Digest::SHA256.new, signing_input)

# base64 encode the raw signature without padding
signature_base64 = Base64.urlsafe_encode64(signature_raw, padding: false)
```

### Optional: verify the JWS token

Some libraries re-encode JSON payloads with different spacing, key ordering, or character encoding. To debug a signature locally, decode the payload and compare it with the `raw_body` sent in the HTTP request. You can also inspect a token containing only non-sensitive test data at [jwt.io](https://jwt.io).

```python theme={null}
token = f"{protected_base64}.{payload_base64}.{signature_base64}"
print(token)

payload = base64.urlsafe_b64decode(
  payload_base64 + '=' * (4 - len(payload_base64) % 4)
)
print(payload) # should be equal to raw_body sent in the http request
```

```javascript theme={null}
const token = `${protectedBase64}.${payloadBase64}.${signatureBase64}`
console.log(token)

const payload = Buffer.from(payloadBase64, 'base64url').toString()
console.log(payload) // should be equal to raw_body sent in the http request
```

```ruby theme={null}
token = "#{protected}.#{payload_base64}.#{signature}"
puts token

payload = Base64.urlsafe_decode64(payload_base64 + '=' * (4 - payload_base64.length % 4))
puts payload # should be equal to raw_body sent in the http request
```

### Construct the `Fintoc-JWS-Signature` header

Construct the `Fintoc-JWS-Signature` header by joining the protected header and signature:

```python theme={null}
jws_signature_header = f"{protected_base64}.{signature_base64}"
```

```javascript theme={null}
const jwsSignatureHeader = `${protectedBase64}.${signatureBase64}`;
```

```ruby theme={null}
jws_signature_header = "#{protected_base64}.#{signature_base64}"
```

## Review the complete example

The following function combines the preceding steps. Install the imported libraries before running the example.

```python theme={null}
import base64
import json
import time
import secrets
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization

def generate_jws_signature_header(raw_body):
    # Read private key
    with open('./private_key.pem', 'rb') as f:
        private_key = serialization.load_pem_private_key(
            f.read(),
            password=None
        )

    # Create headers
    headers = {
        'alg': 'RS256',
        'nonce': secrets.token_hex(16),
        'ts': int(time.time()),
        'crit': ['ts', 'nonce']
    }

    # Base64url encode without padding
    protected_base64 = base64.urlsafe_b64encode(
        json.dumps(headers).encode()
    ).rstrip(b'=').decode()
    
    payload_base64 = base64.urlsafe_b64encode(
        raw_body.encode()
    ).rstrip(b'=').decode()
    signing_input = f"{protected_base64}.{payload_base64}"

    # Create signature
    signature_raw = private_key.sign(
            signing_input.encode(),
            padding.PKCS1v15(),
            hashes.SHA256()
        )
    
    signature_base64 = base64.urlsafe_b64encode(signature_raw).rstrip(b'=').decode()

    # Debug output
    print(f"Token: {protected_base64}.{payload_base64}.{signature_base64}")
    payload = base64.urlsafe_b64decode(
        payload_base64 + '=' * (4 - len(payload_base64) % 4)
    )
    print(f"Payload: {payload.decode()}")

    return f"{protected_base64}.{signature_base64}"


body = { ... }
raw_body = json.dumps(body) # the exact payload to be sent in the http request

# signature that must be included in the 'Fintoc-JWS-Signature' request header
jws_signature_header = generate_jws_signature_header(raw_body)
```

```javascript theme={null}
import crypto from 'crypto';
import { readFileSync } from 'fs';

function generateJwsSignatureHeader(rawBody) {
  // Read private key
  const privateKey = readFileSync('./private_key.pem', 'utf8');

  const headers = {
    alg: 'RS256',
    nonce: crypto.randomBytes(16).toString('hex'),
    ts: Math.floor(Date.now() / 1000),
    crit: ['ts', 'nonce']
  };

  const protectedBase64 = Buffer.from(JSON.stringify(headers))
    .toString('base64url');
  
  const payloadBase64 = Buffer.from(rawBody)
    .toString('base64url');

  const signingInput = `${protectedBase64}.${payloadBase64}`;
  const signatureRaw = crypto.createSign('sha256')
    .update(signingInput)
    .sign({
      key: privateKey,
      padding: crypto.constants.RSA_PKCS1_PADDING
    });

  const signatureBase64 = Buffer.from(signatureRaw)
    .toString('base64url');

  // Debug output
  console.log(`Token: ${protectedBase64}.${payloadBase64}.${signatureBase64}`);
  console.log(`Payload: ${Buffer.from(payloadBase64, 'base64url').toString()}`);

  return `${protectedBase64}.${signatureBase64}`;
}


const payload = { ... }
const rawBody = JSON.stringify(payload) // the exact payload to be sent in the http request

// signature that must be included in the 'Fintoc-JWS-Signature' request header
const jwsSignatureHeader = generateJwsSignatureHeader(rawBody)
```

```ruby theme={null}
require 'base64'
require 'openssl'
require 'securerandom'
require 'json'

class JwsSignatureHeaderGenerator
  def self.generate(raw_body)
    private_key = OpenSSL::PKey::RSA.new(File.read('./private_key.pem'))

    headers = {
      'alg' => 'RS256',
      'nonce' => SecureRandom.hex(16),
      'ts' => Time.now.to_i,
      'crit' => ['ts', 'nonce']
    }

    protected = Base64.urlsafe_encode64(headers.to_json, padding: false)
    payload_base64 = Base64.urlsafe_encode64(raw_body, padding: false)

    signing_input = "#{protected}.#{payload_base64}"
    signature_raw = private_key.sign(OpenSSL::Digest::SHA256.new, signing_input)
    signature = Base64.urlsafe_encode64(signature_raw, padding: false)

    "#{protected}.#{signature}"

  end
end

body = { ... }
raw_body = body.to_json # the exact payload to be sent in the http request

# signature that must be included in the 'Fintoc-JWS-Signature' request header
jws_signature_header = JwsSignatureHeaderGenerator.generate(raw_body) 
```
