🔐 Signing Requests

All requests to the server must be signed with the private RSA key using the RSASSA_PSS_SHA_512 signing algorithm. The signature is passed in the X-Request-Signature header.

Paybis decodes and verifies the signature with the partner's public RSA key provided during onboarding. If the request signature validation fails, 403 HTTP error code is returned.

📘

Keys are generated by the Partner, and the public key is provided to Paybis integration manager.

❗️

If the private key is lost or compromised, inform your Paybis integration manager immediately. The corresponding public key will be revoked, and a new key pair should be generated.

🧾 Forming the Signature

📘

Note

  • A new signature must be generated whenever the request body changes.
  • Do not sign GET requests or any request with an empty body.
  • The signature must be passed in the X-Request-Signature header.
  1. Prepare the HTTP Request Body

Construct the full HTTP request body as a raw byte string (e.g., JSON). Ensure it matches the exact content sent in the request.

  1. Hash the Request Body
  • Compute a SHA-512 hash of the serialized request body.
  • Convert the resulting digest to a lowercase hexadecimal string (128 characters).
  • This hex string becomes the message to sign.
  1. Load the Private RSA Key
  • Load the 4096-bit private RSA key from a secure source (file, memory, KMS).
  • Ensure it is valid and in the correct format.
  1. Sign the Digest Using RSASSA-PSS with SHA-512 Use the following parameters:
  • Hash Function: SHA-512
  • MGF: MGF1 with SHA-512
  • Salt Length: 64 bytes (same as SHA-512 hash size)
  1. Encode the Signature in Base64
  • Convert the raw binary signature into a Base64-encoded string.
  • Pass this string as the value of X-Request-Signature header.

💡 Sample Code

PHP (using phpseclib):

<?php

use phpseclib\Crypt\RSA;

$privateKey = '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';
$requestBody = '{
  "partnerUserId": "1bc166bd-1808-4009-8454-aceb47ba8753",
  "cryptoWalletAddress": null,
  "email": "[email protected]",
  "applicantSumsubToken": "token_hash",
  "locale": "en"
}';

$requestBodyHash = hash('sha512', $requestBody);
$verifier = new RSA();

$verifier->setSignatureMode(RSA::SIGNATURE_PSS);
$verifier->setHash('sha512');
$verifier->setMGFHash('sha512');
$verifier->loadKey($privateKey);

$signature = base64_encode($verifier->sign($requestBodyHash));

echo $signature . PHP_EOL;
composer require phpseclib/phpseclib;

Node.js

const crypto = require("crypto")

const privateKey = `-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----`

// Request body to sign
const verifiableData = '{"cryptoWalletAddress":{"address":"valid_btc_crypto_wallet_address","currencyCode":"BTC"},"locale":"en","partnerUserId":"johnDoe1","email":"[email protected]","quoteId":"7604aac0-7938-4e54-8a80-c3172a043c44"}'

const hash = crypto.createHash('sha512');
const hashedData = hash.update(verifiableData, 'utf-8').digest('hex');

// The signature method takes the data we want to sign, the
// hashing algorithm, and the padding scheme, and generates
// a signature in the form of bytes
const signature = crypto.sign("sha512", Buffer.from(hashedData), {
    key: privateKey,
    padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
    saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST,
})

console.log(signature.toString("base64"))

Verify a signature sample code: Node.js

const crypto = require("crypto")

const publicKey = `-----BEGIN RSA PUBLIC KEY-----
MIICCgKCAgEAut917lnm3PFcVqbqmVGu+GPHfk5jeebukMekdrrJEuJnQf4Slu6Q
+wwAneHDoJVEKa3ecyLjYp9D1wxXCL2m3Cu+Ev4ueOwIKaAFv+ZHl5naHV6T114h
C3dIXW31qT9t8n6WvYVz7aAdhRLZBry4eVe0HDabFujM7g+N2eS+XM38zhJvHO1w
5TZcK7EQP25NuLSzyp/QDG/3vb8f4ZrYAgbtmXRnDfu5b88iB2noPfTJcruJ/rr0
+/yic8ytUi5dzOfXGbpDtpcry8DeeJeG9efLsNL7IoTcFgrgTvhKtxbLy3QmPHgA
xKvGdP4jSWLJJfMAlIFz8jPuUSxuPKSoKocXZaHb+Zpt6PQl6qotwkHH4Acb/uha
jb1sBLbZBbqyjaOZyuWt7yMcdkdkbV14h3S6eaBsLXeeZ/kI+y3j2MKajDQ7k8kM
q8wY7Ypco+TAP04EIySuNlrwZlUhZaDpGO77i8yreUCrLerAqikyGygo9jcbsJ93
JsYkvWG5f7qb0+/JsZ0YdoIs19aA4H46rz/eWJT7HPsMk9P+eX4S6xSY/Xjj873v
nKHyFsg3Ovq3sb5ovtmJfbLXeWFlvtn/EWXjvyuyf6wJWQK8TAPg3bdpS3DyNm/D
Ru94PjdPAQ2mX86dz0D7OWqrhlXHdwn1vopiTjmKtBGdAoR4lsZKTy0CAwEAAQ==
-----END RSA PUBLIC KEY-----`

const verifiableData = '{"cryptoWalletAddress":{"address":"valid_btc_crypto_wallet_address","currencyCode":"BTC"},"locale":"en","partnerUserId":"johnDoe1","email":"[email protected]","quoteId":"7604aac0-7938-4e54-8a80-c3172a043c44"}'

const hash = crypto.createHash('sha512');
const hashedData = hash.update(verifiableData, 'utf-8').digest('hex');

const signature = 'B+Dyfoaeb8YkTHPIAJ54baa8XD14fCdPUhOq1A+9togM2DsyfaYq7qnswy9/LS00x8JzXkIdb7WGGk90qEXEKWUpc8KJ2epHi/sVAMFtudAGulWf/ib2BvRt1us0bylBCtwcXfh9lB4EnjSdoMvCCejSG5Jc5CYIX+v72HCimTjVwaiizIE9/RyBDbe97+1kA/l1CfzQL2QOcX5MjQn7ED6A5r/7tAQNp1KJLLKb8yeTpqppEGvOj6HIe0dZs1lEs1lXWhmBFllQmChSsjf94BDuJK780PePwlIUDKDra/ntQnGOaDZd41ugAjlPnPCC9UhACHL+PG+yKH1a6hIVdA0ZldTzq0kh+Ft1jBY1g21AUS8/Tcqr0Tw3ETchdl2OdxrypOwsZS8VCc+22DSY7MYeKSTHC1xzyS+Arw4VBu08E8d8z9sb9XxiMk1kbBgTNS5OEkYRXwGWy9q76S1xqPZpU9PRpbj1DRfGrcm2sY4rkGrpIT0o78LfeXQ5BPXPdVmObR29s6mDqQ+vwWMX4UiH2o9I58GqDk4/dz3LZLmCzdgUdUmdg7V2FUdQ8/c7Gi5kXLyN5UIJSPfSguMtZZLHTpVorTUY5M6THuctFepGv14b0IlzH/VLgHLUAkONL8/qcK0lv4oubVsisTxr2082M6Bj7J+BLhSLPuAx1YI='
// To verify the data, we provide the same hashing algorithm and
// padding scheme we provided to generate the signature, along
// with the signature itself, the data that we want to
// verify against the signature, and the public key
const isVerified = crypto.verify(
    "sha512",
    Buffer.from(hashedData),
    {
        key: publicKey,
        padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
        saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST,
    },
    Buffer.from(signature, 'base64')
)

// isVerified should be `true` if the signature is valid
console.log("signature verified: ", isVerified)

Example signature:

mVuJJRvdDJMPWkDHMx0Fj0lOs021Usv6iEHpJVRzX/hNfEawOtJEIGU5hfu6GXrqwWj/+JuGjejNQo2pkoN+OUREv9gylD91GROaLA7zbaQLyiIch97UVi4iyg9d4mVHXtr3kbuxwYDBSOgHV+4mJuGM2jOOXY86bidKdrt+UdcdQlt62DkvmHTLIqfXrLQevTEEs0nzSTorfZkMLKwmXTu+1qJv3+R3QxWFhf2QqMi GR4Y8BiMp/URo7y6fVlI4dbVnqoOU37izXguheDR17R/KFe/SyhtltsAvhVBrbrTd9y34efbTCtNgDe9qdwTolgp7DnDSqHGW9zLrjQdbYdGlLzjbok5tfmtcQUcBhXiEOPmKO+s5ZSR46NAURxg9oh9BjscmG+moMQj244WtT3AAkJVPsTItwnUriZahYDplrppZz9BZXHGjcRf/nh/Xvd3aNv4HKyKPQnpYGNqV9ZvDXYkTdjFp57/vY5h48dg9wOd4BiLJ7FiOWpgtpwUUS1+gbzjvDvgpOPCFVRpgeYqq/LNIfcN1nfqcDSKSvjMzJlsX1xZN/ezinyqGZNaLRiil+wX02ihdMtVknco/ne4MsJvhNg2uTyjBv0VSji8rMDzeYLiDiapj0nm47lQHhH1vthd8BH83oosHBDLaJuPo4M4WuIfdgxfRjohHFKI=

This example is generated using the data and PHP sample code provided above.

🔑 Generating RSA Key Pair

To generate the key pair on the Partner's end and share the public key with Paybis, use the following commands:

  1. Generate a private RSA key.
openssl genrsa -out private.key 4096
  1. Create a public key from private one.
openssl rsa -in private.key -outform PEM -pubout -out public.pub

Share the public.pub with your Paybis integration manager.