Developer

Partner Docs

Merchant Docs

Request Signature Verification

Request signature verification, when enabled, adds an extra layer of security to your API calls, ensuring that requests haven't been tampered with in transit.

Banked supports request signing using an RSA key pair. Here's how to implement it step by step.

Overview

The process involves:

  1. Generate an RSA key pair
  2. Share your public key with Banked
  3. Sign each request using your private key
  4. Send the signature in your request header
  5. Troubleshoot any signature validation errors

Prerequisites:

  • Valid Banked account with API access
  • OpenSSL or SSH keygen available for key generation
  • Development environment with cryptographic libraries
  • Understanding of RSA signatures and JWS (JSON Web Signatures)

Implementation Steps


Step 1. Generating RSA Key Pair

You can generate the key pair using any method that suits your environment. Banked supports any RSA key length, though we recommend following NIST guidelines with a minimum key length of 2048 bits.

Here is an example approach:

Using OpenSSL (recommended):

bash
# Generate private key (2048-bit minimum, 4096-bit recommended)
openssl genrsa -out private_key.pem 4096

# Extract public key
openssl rsa -in private_key.pem -RSAPublicKey_out -out public_key.pem

Using SSH keygen:

bash
ssh-keygen -t rsa -b 4096 -m PEM -f banked_key

Store your private key securely and never share it. Only the public key gets shared with Banked.


Step 2. Share Your Public Key with Banked

Email your public key to your Banked Solution Architect or to the Banked Support Team support@banked.com

Requirement: The public key must be in PKCS#1 PEM format.

What PKCS#1 PEM format looks like:

json
-----BEGIN RSA PUBLIC KEY-----
MIICCgKCAgEA...
[base64 encoded key data]
...
-----END RSA PUBLIC KEY-----

Step 3. Generate Request Signatures

Each request must be signed using JWS with PS512.

What Gets Signed

The signed string is a concatenation of these components in this exact order:

text
request_url_with_query_params + request_method + request_body + idempotency_key_value

Important: No separators between components - they are concatenated directly.

Signature Formula

text
base64_encode(JWS_PS512_Sign(request_url_with_query_params + request_method + request_body + idempotency_header))

Important: The resulting JWS is then base64-encoded. This is on top of the base64 encoding of the JWS segments.

Examples

Example: GET Request

For the following request:

bash
curl --request GET \
     --url https://api.banked.com/v2/payments/?mode=live \
     --header 'X-Banked-Idempotency-Key: 1'

Signed string is:

text
"https://api.banked.com/v2/payments/?mode=liveGET1"

Example: POST Request

For the following request:

bash
curl --request POST \
     --url https://api.banked.com/v2/payments/?mode=live \
     --header 'X-Banked-Idempotency-Key: 1' \
     --data-raw '<payment request body payload>'

Signed string is:

text
"https://api.banked.com/v2/payments/?mode=livePOST<payment request body payload>1"

Step 4. Send the Request with Signature

Include the base64-encoded signature in the Signature HTTP header:

text
Signature: <base64_signature>

Complete example using the GET request from Step 3:

bash
curl --request GET \
     --url https://api.banked.com/v2/payments/?mode=live \
     --header 'X-Banked-Idempotency-Key: 1' \
     --header 'Signature: eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9...'

Complete example using the POST request from Step 3:

bash
curl --request POST \
     --url https://api.banked.com/v2/payments/?mode=live \
     --header 'X-Banked-Idempotency-Key: 1' \
     --header 'Signature: eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9...' \
     --data-raw '<payment request body payload>'

The signature value shown is truncated for readability. Your actual signature will be much longer.

Step 5. Troubleshooting Signature Issues

When implementing request signing, you might encounter validation errors. Here's how to handle and debug them:

Common Error Response

If signature validation fails, the Banked API responds with:

  • HTTP Status: 401 Unauthorized
  • Response Body:
json
{
  "errors": [
    {
      "code": "unauthorized",
      "source": {
        "parameter": "invalid"
      },
      "title": "Request Verification Failed"
    }
  ]
}

Debugging Checklist

If you receive this error, check the following:

1. String Construction

  • [ ] URL includes query parameters exactly as sent
  • [ ] HTTP method is uppercase (GET, POST, etc.)
  • [ ] Request body matches exactly (including whitespace)
  • [ ] Idempotency key value is correct
  • [ ] No separators between concatenated components

2. Signature Generation

  • [ ] Using JWS with PS512 algorithm
  • [ ] Private key is valid RSA format
  • [ ] JWS is in compact form
  • [ ] Final result is base64 encoded

3. Request Headers

  • [ ] Signature header contains the base64-encoded JWS
  • [ ] X-Banked-Idempotency-Key header matches the value used in signing
  • [ ] No extra whitespace in header values

Testing Your Implementation

Start with a simple GET request to verify your signing logic:

bash
# Test with minimal GET request
curl --request GET \
     --url https://api.banked.com/v2/payments/ \
     --header 'X-Banked-Idempotency-Key: test123' \
     --header 'Signature: [your_generated_signature]' \
     --verbose

Use --verbose flag to see the exact request headers being sent.


Code Examples

Javascript Example that can be used as a Postman script

Add the following code to your Postman pre-script.

Please note that this script is already included as a pre-script in the Postman Collection which can be downloaded here

Please note that you may need to install pmlib to your Postman environment, please follow the instructions in this link: https://joolfe.github.io/postman-util-lib/

Javascript
eval(pm.globals.get('pmlib_code')) 
var CryptoJS = require("crypto-js"); 

//Generate payload to sign for Signature
var method = pm.request.method; idempotency = "";

//set idempotency key for POST or PATCH
 if (method != "GET") { 
     //Use timestamp as a way to get a unique value
     idempotency = new Date().getTime();
     postman.setEnvironmentVariable("idempotency", idempotency); 
 }
 var url = pm.variables.replaceIn(pm.request.url.toString());
 var body = pm.request.body; payloadToSign = url + method + body + idempotency;
 
 //replace with your private key
 const keyDataBase64String = 
 "-----BEGIN PRIVATE KEY-----\n" +
 "MIIJQQIBADANBgkqhkiG9w0BAQEF................." + 
 "-----END PRIVATE KEY-----";

 var sig = new pmlib.rs.KJUR.crypto.Signature({"alg": "SHA512withRSAandMGF1"});
 sig.init(keyDataBase64String); sig.updateString(payloadToSign); 
 var hSigVal = sig.sign(); const signedEncoded = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(hSigVal));
 postman.setEnvironmentVariable("signature", signedEncoded);
  1. Add the following Postman environment variables to your Postman config:
  • “idempotency”
  • “signature”
  1. Add the idempotency and signature Postman variables to the header tab - See screenshot below: Postman Variables

Go Implementation

Here's a complete Go example that demonstrates the full signing process:

go
package main

import (
    "crypto/rand"
    "crypto/rsa"
    "encoding/base64"
    "fmt"

    "github.com/lestrrat-go/jwx/v2/jwa"
    "github.com/lestrrat-go/jwx/v2/jws"
)

func main() {
	privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
	if err != nil {
		panic(err)
	}

	msg := []byte("string to sign")

	jws, err := jws.Sign([]byte(msg), jws.WithKey(jwa.PS512, privateKey))
	if err != nil {
		panic(err)
	}

	fmt.Println(base64.StdEncoding.EncodeToString(jws))
}


Signing Key Rotation

API Clients are responsible for managing their key lifecycles. To rotate keys:

  1. Generate a new RSA key pair
  2. Share the new public key with Banked via support@banked.com
  3. Wait for a new set of API credentials to be sent to you from Banked.
  4. Switch your application to use the new private key
  5. Notify Banked to deprecate the old key

Banked temporarily supports both old and new keys during the transition period to ensure zero downtime.

© 2025 Banked Ltd.

Dark Theme
PrivacyTerms