Some Visa Developer APIs require an API Key-Shared Secret Authentication, which Visa refers to as X-Pay Token. To invoke an API using X-Pay Token, you will need an API Key and a Shared Secret, which is provided on the project details page.
1.
API Key: Can be found on the "Credentials" tab of your dashboard
2.
Shared Secret: Can be found on the Credentials tab of your dashboard
# generate private key
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
# generate public key
openssl rsa -pubout -in private_key.pem -out public_key.pem
when uploading public key to portal remove header and footer (-----BEGIN PUBLIC KEY---- and ----- END PUBLIC KEY-----) as well as new lines
# Show public key without new lines
cat public_key.pem | tr -d '\r\n'
# Save shared secret from portal into a file encrypted_shared_secret.txt
# Use base64 decode to convert shared secret into raw binary data
cat encrypted_shared_secret.txt | base64 --decode > decoded_data.bin
# decrypt shared secret using private key
openssl pkeyutl -decrypt -in decoded_data.bin -inkey private_key.pem -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 > decrypted_shared_secret.txt
To successfully invoke your Visa Developer APIs, which use X-Pay Token, your project must include the following:
1.
Add the API Key as the query parameter.
2.
Include the Accept and X-Pay Token in the request header as shown in the sample below.
Field | Value |
---|---|
Accept | application/json |
X-PAY-TOKEN | x-pay-token* |
Note: Refer to the section below on how to generate the x-pay-token value with SHA256 HMAC.
Sample Header
GET /vdp/helloworld?apikey=KSKDFJOP934ALSFDJP34 HTTP/1.0
Host: cert.api.visa.com
Accept: application/json
X-PAY-TOKEN: xv2:1455716783:f5d15ed23f825ac69cd42e6fa187a175ecf7e9566ce4f21e11bad49bed4cc363
3.
Request payload. Include any resource-specific request parameters in the request payload before you make a request.
4.
Test different scenarios using the test data provided on the Project dashboard.
To generate the X-Pay Token, follow these steps:
1.
Generate a message string by concatenating the following parameters:
message = timestamp + resource_path + query_string + request_body
Note: For some products the context path is skipped while computing x-pay-token:
Parameters |
Description |
||||||||||||||||||||||||||||||||||||||||
timestamp |
This is the current timestamp in UTC (in seconds). |
||||||||||||||||||||||||||||||||||||||||
resource_path |
This is the API endpoint you would like to invoke after the context path. For majority of the products, the context path that is used to compute the x-pay-token is the resource path excluding the first path parameter (also referred to as the base path). For the products in bold below, the entire resource path should be used to compute the x-pay-token.
|
||||||||||||||||||||||||||||||||||||||||
query_string |
The apikey is a required query parameter. Query parameters should be in lexicographical order. |
||||||||||||||||||||||||||||||||||||||||
request_body |
This is the API endpoint-specific request body string |
2.
Create the X-Pay Token, as shown below:
XPayToken = "xv2:" + timestamp + ":" + SHA256HMAC(shared_secret, message)
Note: The shared_secret is the shared secret from the Project Dashboard.
Depending on your system needs, Visa Developer APIs will allow you to setup one or more HTTP header variables as illustrated in the following table.
Variable Name | Value |
---|---|
Content-Type | Optional Specify request format
If not specified, expects JSON. |
Accept | Optional Specify request format
If not specified, defaults to request format. |
SOAPUI is a free and open source Web Service Functional Testing solution. With an easy-to-use graphical user interface, SOAP UI allows you to rapidly create and execute web service API functional tests. It is highly recommended that you use SOAP UI, or a similar connectivity tool, to establish your initial connection to the Visa Developer sandbox.
Note: Prior to working with SOAPUI, you must Register and create a project that uses x-pay-token as an authentication service. Make sure that you have a valid API key and shared secret (available on Project dashboard).
2.
Once installed, open SOAPUI and select File > New REST Project.
Use the following URI: https://cert.api.visa.com/vdp/helloworld?apikey=<value of your API key>
(replace the value of API key query string parameter with the actual API key that you received from the Visa Developer Platform).
3.
Once the project is created, in project navigator, right click the URI, and select Generate TestSuite. Specify name for your test suite and click OK.
4.
In the project navigator, fully expand the newly created test suite and locate the test steps.
5.
Right click Test Steps, select Add Step, then select Groovy Script as shown below. Specify name for your script and click OK.
6.
Paste the following contents into the script body. Make sure to replace the API key and the shared secret with your own values from the Visa Developer Center.
Run the script (by clicking the green chevron) and make sure that the value of x-pay-token is visible in the Log Output window.
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
def hmac(String secretKey, String data) {
Mac mac = Mac.getInstance("HmacSHA256")
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256")
mac.init(secretKeySpec)
byte[] digest = mac.doFinal(data.getBytes())
return digest
}
def APIKey = ‘VALUE_OF_YOUR_API_KEY’
def sharedSecret = ‘VALUE_OF_YOUR_SHARED_SECRET’
def URI = "helloworld"
def QS = "apikey="+APIKey
def timeStampUTC = String.valueOf(System.currentTimeMillis().intdiv(1000L))
def payload = ""
def HMACDigest = hmac(sharedSecret, timeStampUTC + URI + QS + payload)
def encodedDigest = HMACDigest.encodeHex().toString()
def XPayToken = "xv2:"+ timeStampUTC + ":" + encodedDigest
testRunner.testCase.setPropertyValue("xpayToken", XPayToken)
log.info(XPayToken)
7.
Double click Request 1 (the test step just above your new, for example: "groovy" script). Select Headers tab and click the green plus sign to add a new header.
8.
Add a custom header for the X-Pay Token. The value of this header is set dynamically, when you run the script.
Note: You will need to use the following as the header value: ${#TestCase#xpayToken}.
The header name will be static, and should be set to: x-pay-token. The resulting header setting will appear, as follows.
9.
Run your test suite by first executing the script (by clicking the green chevron), and secondly by sending the test request (green chevron on the request page). If all is correct, you should see the current timestamp in the response.
import java.math.BigInteger;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SignatureException;
public static String generateXpaytoken(String resourcePath, String queryString, String requestBody, String sharedSecret) throws SignatureException {
String timestamp = timeStamp();
String beforeHash = timestamp + resourcePath + queryString + requestBody;
String hash = hmacSha256Digest(beforeHash, sharedSecret);
String token = "xv2:" + timestamp + ":" + hash;
return token;
}
private static String timeStamp() {
return String.valueOf(System.currentTimeMillis()/ 1000L);
}
private static String hmacSha256Digest(String data, String sharedSecret)
throws SignatureException {
return getDigest("HmacSHA256", sharedSecret, data, true);
}
private static String getDigest(String algorithm, String sharedSecret, String data, boolean toLower) throws SignatureException {
try {
Mac sha256HMAC = Mac.getInstance(algorithm);
SecretKeySpec secretKey = new SecretKeySpec(sharedSecret.getBytes(StandardCharsets.UTF_8), algorithm);
sha256HMAC.init(secretKey);
byte[] hashByte = sha256HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8));
String hashString = toHex(hashByte);
return toLower ? hashString.toLowerCase() : hashString;
} catch (Exception e) {
throw new SignatureException(e);
}
}
private static String toHex(byte[] bytes) {
BigInteger bi = new BigInteger(1, bytes);
return String.format("%0" + (bytes.length << 1) + "X", bi);
}
from calendar import timegm
from datetime import datetime
from hashlib import sha256
import hmac
def _get_x_pay_token(shared_secret, resource_path, query_string, body):
timestamp = str(timegm(datetime.utcnow().timetuple()))
pre_hash_string = timestamp + resource_path + query_string + body
hash_string = hmac.new(shared_secret,
msg=pre_hash_string,
digestmod=sha256).hexdigest()
return 'xv2:' + timestamp + ':' + hash_string
var timestamp = Math.floor(Date.now() / 1000);
var preHashString = timestamp + resourcePath + queryParams + postBody;
var crypto = require('crypto');
var hashString = crypto.createHmac('SHA256', sharedSecret).update(preHashString).digest('hex');
var xPayToken = 'xv2:' + timestamp + ':' + hashString;
def get_xpay_token(shared_secret, resource_path, query_string, request_body)
require 'digest'
timestamp = Time.now.getutc.to_i.to_s
hash_input = timestamp + resource_path + query_string + request_body
hash_output = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), shared_secret, hash_input)
return "xv2:" + timestamp + ":" + hash_output
end
private static string getTimestamp() {
long timeStamp = ((long) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds) / 1000;
return timeStamp.ToString();
}
private static string getHash(string data) {
var hashString = new HMACSHA256(Encoding.ASCII.GetBytes(SHARED_SECRET));
var hashbytes = hashString.ComputeHash(Encoding.ASCII.GetBytes(data));
string digest = String.Empty;
foreach (byte b in hashbytes) {
digest += b.ToString("x2");
}
return digest;
}
private static string getXPayToken(string resourcePath, string queryString, string requestBody) {
string timestamp = getTimestamp();
string sourceString = timestamp + resourcePath + queryString + requestBody;
string hash = getHash(sourceString);
string token = "xv2:" + timestamp + ":" + hash;
return token;
}
$time = time();
$ pre_hash_string = $time.$resource_path.$query_string.$body;
$hashtoken = "xv2:".$time.":".hash_hmac('sha256', $ pre_hash_string, $secret);