API Key – Shared Secret (X-Pay Token)

Navigate to...
keyboard_arrow_down

Some Visa Developer APIs require an API Key-Shared Secret Authenticationwhich 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.

Tutorial Video

Get your API Key and Shared Secret

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

Set up Shared Secret Encryption

1. Generate Public and Private Key Pair

# 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
		

2. Format Public Key

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'

		

3. Upload to Visa Developer Center to encrypt Shared Secret

4. Decrypting Shared Secret with Private Key

# 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

		

Generating the X-Pay Token

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.

Generating the X-Pay Token


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.

Product

Part of the URL to be skipped while computing x-pay token

Example

Request URL

Path to be used in x-pay-token calculation

Visa Token Service

 

https://cert.api.visa.com/vts/provisionedTokens

vts/provisionedTokens

 

https://cert.api.visa.com/tokens/suspend

tokens/suspend

 

https://cert.api.visa.com/ics/v1/pan/lifecycle

ics/v1/pan/lifecycle

Visa Token Service Provisioning and Credential Management

 

https://cert.api.visa.com/vtis/v1/pan/lifecycle

vtis/v1/pan/lifecycle

VDP Hello World

/vdp/

https://cert.api.visa.com/vdp/helloworld

helloworld

All other Products

/one/

https://cert.api.visa.com/one/two/three 

two/three

Examples

  • Cybersource

/cybersource/

https://cert.api.visa.com/cybersource/payments/v1/authorizations

payments/v1/authorizations

  • Visa Checkout

/wallet-services-web/

https://cert.api.visa.com/wallet-services-web/payment/data/{callId} 

payment/data/{callId}

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.

Constructing the HTTP Header

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

  • XML Use text/xml
  • JSON Use application/json

If not specified, expects JSON.

Accept

Optional

Specify request format

  • XML Use text/xml
  • JSON Use application/json

If not specified, defaults to request format.

Testing X-Pay-Token Connectivity Using SOAPUI

Testing X-Pay Token Connectivity Using SOAPUI

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).

SOAPUI REST project

1.

Download SoapUI 5.4 from https://www.soapui.org.

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).

Image of SoapUI's application. Modal window titled New REST Project with URI input field.

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.

Image of Soap UI's application. Menu with focus on Generate TestSuite option.

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.

Image of Soap UI's application. Menu with focus on Generate TestSuite option.

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.

Sample Code for API Key – Shared Secret (X-Pay Token)

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);