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:
- Generate an RSA key pair
- Share your public key with Banked
- Sign each request using your private key
- Send the signature in your request header
- 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):
# 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:
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:
-----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:
request_url_with_query_params + request_method + request_body + idempotency_key_value
Important: No separators between components - they are concatenated directly.
Signature Formula
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:
curl --request GET \ --url https://api.banked.com/v2/payments/?mode=live \ --header 'X-Banked-Idempotency-Key: 1'
Signed string is:
"https://api.banked.com/v2/payments/?mode=liveGET1"
Example: POST Request
For the following request:
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:
"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:
Signature: <base64_signature>
Complete example using the GET request from Step 3:
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:
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:
{ "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:
# 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/
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);
- Add the following Postman environment variables to your Postman config:
- “idempotency”
- “signature”
- Add the idempotency and signature Postman variables to the header tab - See screenshot below:
Go Implementation
Here's a complete Go example that demonstrates the full signing process:
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:
- Generate a new RSA key pair
- Share the new public key with Banked via support@banked.com
- Wait for a new set of API credentials to be sent to you from Banked.
- Switch your application to use the new private key
- Notify Banked to deprecate the old key
Banked temporarily supports both old and new keys during the transition period to ensure zero downtime.