Signing requests for SCA compliance
If you’re using a Volt Account with Connect, you need to sign each refund and payout request using a private key, to comply with Strong Customer Authentication guidelines, in order for your payment to be straight-through-processed (ie with no further interaction required).
If your Connect account is provided by one of our partners (eg Clear Junction or ISX) then you do not need to sign your request and you can stop reading here!
Why do you need to do this?
The Payment Services Directive (PSD2) requires that all payments are authenticated using two factors out of the following three:
- Something you know
- Something you have
- Something you are
When creating or approving payments (which includes refunds and payouts) in Fuzebox we use biometric authentication (something you are) on your hardware device (something you have). Payments created via the API are not exempt from this requirement.
How do you do this using the API?
Machine-to-machine communications via API are a little different but we still need to be compliant with PSD2. So we use the standard username and password authentication (as the “something you know” factor) and a private key to sign each request (the “something you have” factor).
We use JWS (JSON Web Signature) as the signature method for each request.
What happens if you don't sign each API request?
Your request won’t be processed for payment automatically, but will instead appear in a queue for processing in Fuzebox. You will need to login to Fuzebox and authorise the request using a passkey stored on your computer or mobile device. This can be done in bulk or for each individual payment.
Where can you test this?
You can test the signing on sandbox and production. However, we recommend testing it particularly on production before activating it for your shoppers.
Integration steps
First, you’ll need to set up the SCA signing. Second, for each request, create a JWT (JSON Web Token) which includes the signature and include that in the request header.
1) Setting up SCA signing
- Create private / public keys
- Store the private key in a safe place
- Share public key with Volt and get your assigned public key id
Volt works on providing these steps within Fuzebox. You will then instead receive a private key + public key id directly from Fuzebox. This means you can skip step 1. and 3. while you still have to store the private key in a safe space.
2) Signing the requests
Once you have the private key and the public key id you can proceed with signing the requests.
Create private / public keys
Technical specifications
- We support public keys in the following formats: PKCS1, PKCS8 and x509.
- The public key length should be between 2048 and 4096 bytes.
How to generate your keys
You can generate your private / public key pair using a terminal which supports the openssl
command, and by following the appropriate instructions below.
- X509
- PKCS1
- PKCS8
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 3650 -nodes -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=CommonNameOrHostname"
openssl genrsa -f4 -out private.txt 4096
openssl rsa -in private.txt -outform PEM -pubout -out public.txt
openssl genrsa -f4 -out private.txt 4096
openssl pkcs8 -topk8 -inform pem -in private.txt -outform PEM -nocrypt -out private8.txt
openssl rsa -in private8.txt -outform PEM -pubout -out public.txt
Create a JWT token for each request
To sign each request, you will need to provide a JWT token in the header of that request. To generate a signed JWT token, you’ll need your private key and the ID of your public key, plus the payload (the JSON body) of the POST request for your refund or payout.
Creating your JWT token
You need to create two parts for your JWT token, the header and the signature. The header tells us how to process the JWT token and the signature is the part that we’ll validate against the public key.
The token header segment
The header should be created first as a JSON object as below, where
alg
– represents the algorithm used to encode/decode the token, in this case it’sRS256
(the Rsa+Sha256 algorithm)typ
– should be “JWT
“kid
– theid
of the public key that we supplied you, which will be in UUID form
{
"alg": "RS256",
"typ": "JWT",
"kid": "ce161c49-4373-4b07-82fa-217998f6b3e8"
}
Encoding the header
Once you’ve created the JSON object as per the example above, you should remove any extra spaces or line breaks, then encode it using the base64UrlEncoded algorithm, as per the example below.
tokenHeader = '{"alg": "RS256","typ":"JWT","kid":"ce161c49-4373-4b07-82fa-217998f6b3e8"}'
encodedHeader = base64UrlEncode(tokenHeader)
… which should then look like the example below.
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImNlMTYxYzQ5LTQzNzMtNGIwNy04MmZhLTIxNzk5OGY2YjNlOCJ9
The signature segment
To create the signature segment, we need to use the algorithm that is specified in the header and the private key. This is where you’ll need a copy of the payload you’re sending in the POST request, because that’s what you’re actually signing here.
In this case we’re using the RSA+SHA256 algorithm and would use the sample code below, utilising the already encoded header plus a base64UrlEncoded version of the JSON payload you will be sending in the refund or payout request. You should not change the payload after generating the signature as this will invalidate the signed token.
Remove whitespace to improve validation
Please remove any line breaks or extra spaces in the request payload before using the base64UrlEncode function, this will ensure that your signature matches the way we’ll validate it. For example, in the case of refunds, you should base64UrlEncode('{"amount":1,"externalReference":"my-external-reference"}')
tokenHeader = '{"alg": "RS256","typ":"JWT","kid":"ce161c49-4373-4b07-82fa-217998f6b3e8"}'
requestPayload = '{"amount":1,"externalReference":"my-external-reference"}'
signature = RSASHA256( base64UrlEncode(tokenHeader) + "." + base64UrlEncode(requestPayload), privateKey )
If all is good you should get a signature which looks like this…
oUjLLtQigBniTiYswfE0JAjMiYXtIlNtVi1Lr1jqBx103vXgVtEdWApUMpG3wze3qVXD_APA2Sk8oLV4DGeBb5pN7yRGeCdxoV3IsikCVs6rn2Q2Jat-bReQMX39F7-Rpn7RznjHUsyWWcNbDKy1wRFcEnDJBVdb_1lKdFBPWaKMkB1Yd8t8X2va6mq7pJXPAMS36Gwc37vULZvdw4D-49r8mcbEGnNXwkcuZ08hMk4UsmM0kxLeNcrVD3wZtuU0N43u1trlPnuX9RDOOh9Gz0fEH1fwxdveAZaMOOWr7IPHBeV8nZXHxt1lpwJ-dpAsSDMvCFhr-MHuOQDDdqsfhQ
Build your JWT token
To build the final JWT token to sign your request, take the encoded header and join it to the signature as follows.
JWT = base64UrlEncode(header) + ".." + signature // Note the ".."
Important note
Please note the double dot ".."
is important when building the JWT token. Validation will fail if you miss one of the dots.
So, using the examples given in this page, your final token should look like the example below:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImNlMTYxYzQ5LTQzNzMtNGIwNy04MmZhLTIxNzk5OGY2YjNlOCJ9..oUjLLtQigBniTiYswfE0JAjMiYXtIlNtVi1Lr1jqBx103vXgVtEdWApUMpG3wze3qVXD_APA2Sk8oLV4DGeBb5pN7yRGeCdxoV3IsikCVs6rn2Q2Jat-bReQMX39F7-Rpn7RznjHUsyWWcNbDKy1wRFcEnDJBVdb_1lKdFBPWaKMkB1Yd8t8X2va6mq7pJXPAMS36Gwc37vULZvdw4D-49r8mcbEGnNXwkcuZ08hMk4UsmM0kxLeNcrVD3wZtuU0N43u1trlPnuX9RDOOh9Gz0fEH1fwxdveAZaMOOWr7IPHBeV8nZXHxt1lpwJ-dpAsSDMvCFhr-MHuOQDDdqsfhQ
Sign your request
Now you have your request payload and a JWT token which signs that payload, all you need to do is add a JWS header to each request, which contains the JWT token you created.
Header name : X-JWS-Signature
X-JWS-Signature: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImNlMTYxYzQ5LTQzNzMtNGIwNy04MmZhLTIxNzk5OGY2YjNlOCJ9..oUjLLtQigBniTiYswfE0JAjMiYXtIlNtVi1Lr1jqBx103vXgVtEdWApUMpG3wze3qVXD_APA2Sk8oLV4DGeBb5pN7yRGeCdxoV3IsikCVs6rn2Q2Jat-bReQMX39F7-Rpn7RznjHUsyWWcNbDKy1wRFcEnDJBVdb_1lKdFBPWaKMkB1Yd8t8X2va6mq7pJXPAMS36Gwc37vULZvdw4D-49r8mcbEGnNXwkcuZ08hMk4UsmM0kxLeNcrVD3wZtuU0N43u1trlPnuX9RDOOh9Gz0fEH1fwxdveAZaMOOWr7IPHBeV8nZXHxt1lpwJ-dpAsSDMvCFhr-MHuOQDDdqsfhQ
Test it with Postman
We have a test script in our Postman collection. It is part of the “Scripts > Pre-req” for the requests marked with “SCA”.
For testing you need
- Private key for sandbox (not your production private key)
- Public key id (kid) from Volt