Create a Digital Signature Key {#wh-fg-key-dig-sig-intro}
=========================================================

Use the information in this section to create a *digital signature key*. The Digital Signature Key request uses Visa's key management service to store your credentials. The Webhooks platform retrieves your credentials from key management to digitally authenticate your notifications.  
You must create a digital signature key to enable `Cybersource` to send notifications to your servers. Replace the digital signature key every year. When you generate a new digital signature key, it overrides the old key and new transactions must use the new key.  
Notifications that use message-level encryption must also the digital signature key.

> IMPORTANT Store the created digital signature key in a secure location in your system.

Optional Notification Validation
--------------------------------

After you set up a webhook subscription, you can validate each notification you receive using your digital signature key. For more information, see [Validating a Notification with the Digital Signature Key](/docs/cybs/en-us/webhooks/implementation/all/rest/webhooks/wh-fg-key-dig-sig-intro/wh-fg-optional-validate-intro.md "").

Endpoints
---------

Send a POST request to one of these endpoints:

* **Test:** `POST ``https://apitest.cybersource.com``/kms/egress/v2/keys-sym`
* **Production:** `POST ``https://api.cybersource.com``/kms/egress/v2/keys-sym`
* **India Production:** `POST https://api.in.cybersource.com/kms/egress/v2/keys-sym`

Required Fields for Creating a Digital Signature Key {#wh-fg-key-dig-sig-req-fields}
====================================================================================

clientRequestAction
:
Set the value to `CREATE`.

keyInformation.expiryDuration
:
Set to a number of days. We recommend `365`.

keyInformation.keyType
:
Set the value to `sharedSecret`.

keyInformation.organizationId
:
Set the value to the organization ID of the organization requesting the key.

keyInformation.provider
:
Set the value to `nrtd`.

keyInformation.tenant
:
Set the value to the organization ID of the organization requesting the key.

Example: Creating a Digital Signature Key {#wh-fg-key-dig-sig-ex}
=================================================================

Digital Signature Key Request

```
{
  "clientRequestAction": "CREATE",
  "keyInformation": {
    "provider": "nrtd",
    "tenant": "merchantName",
    "keyType": "sharedSecret",
    "organizationId": "merchantName"
  }
}
```

Digital Signature Key Response

```
{
"submitTimeUtc": "2021-03-17T06:53:06+0000",
"status": "SUCCESS",
"keyInformation": {
"provider": "NRTD",
"tenant": "merchantName",
"organizationId": "merchantName",
"keyId": "bdc0fe52-091e-b0d6-e053-34b8d30a0504", //ID associated with the key in the key field
"key": "u3qgvoaJ73rLJdPLTU3moxrXyNZA4eo5dklKtIXhsAE=", //Base64 encoded key
"keyType": "sharedSecret",
"status": "Active",
"expirationDate": "2022-03-17T06:53:06+0000"
}
```

REST Interactive Example: Creating a Digital Signature Key {#wh-fg-key-dig-sig-dev-ex}
======================================================================================

Click this image to access the interactive code example for creating a digital signature key. The Live Console refers to this request as *Create Webhook Symmetric Key*.

#### Figure:

Creating a Digital Signature Key [![Image and link to the interactive code example for creating a digital signature key.](/content/dam/documentation/cybs/en-us/topics/payments-processing/payment-services/webhooks/images/dev-ex-create-digi-sig-key.png/jcr:content/renditions/original)](https://developer.cybersource.com/api-reference-assets/index.md#webhooks_create-new-webhooks_create-webhook-security-keys_samplerequests-dropdown_create-webhook-symmetric-key_liveconsole-tab-request-body "")

Validating a Notification with the Digital Signature Key {#wh-fg-optional-validate-intro}
=========================================================================================

You can use the digital signature key to verify that the webhook notifications you receive are from `Cybersource`. Verifying your webhook notifications validates their integrity and helps prevent replay attacks.  
When you receive a webhook notification from `Cybersource`, it contains a digital signature key. You can configure your system to compare the notification's digital signature to the digital signature you created. If the digital signatures match, the notification is validated.  
Complete these tasks to validate the webhook notifications that you receive:
1. Create a digital signature key by sending a *create a digital signature key* request to `Cybersource`. You may have already completed this requirement while setting up your first webhook subscription. For more information, see [Create a Digital Signature Key](/docs/cybs/en-us/webhooks/implementation/all/rest/webhooks/wh-fg-key-dig-sig-intro.md "").
2. Extract the digital signature from the digital signature key that you created.
3. Configure your system to compare your digital signature to the digital signatures in the notifications that you receive. A webhook notification is valid if the notification's digital signature matches your digital signature.

Digital Signature Format {#wh-fg-optional-validate-sig-format}
==============================================================

When you receive a webhook notification from `Cybersource`, it contains a `v-c-signature` header in this format:

```
v-c-signature: t=1617830804768;keyId=bf44c857-b182-bb05-e053-34b8d30a7a72;sig=CzHY47nzJgCSD/BREtSIb+9l/vfkaaL4qf9n8MNJ4CY=";
```

The header contains these three parameters concatenated with semicolons:

* t: The timestamp at which the digital signature key was created.
* keyId: The digital signature key ID.
* sig: The digital signature. It is encrypted using the HMAC-SHA256 algorithm.

Validating a Notification {#wh-fg-optional-validate}
====================================================

Follow these steps to validate the integrity of your webhook notifications.

1. Separate the signature parameters with a semicolon (;) and extract the t, keyId, and sig values.
2. Use the keyId value to fetch the digital signature key.
3. Generate the payload by concatenating the timestamp and the payload from the body of the notification, using a period (.) to join them.
4. Use the SHA-256 algorithm to encrypt the generated payload from Step 3 using the key from Step 2.
5. Verify that the encrypted value matches the value in sig parameter.

Example: Validating a Notification {#wh-fg-optional-validate-ex}
================================================================

This example shows a system script that validates webhook notifications by comparing the merchant's digital signature with the webhook notification's signature. Use this example as a reference for how to configure your system.

```
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;

public class Validator {
    public static void main(String[] args) {

        // Sample signature header
        String signatureHeader = "v-c-signature: t=1617830804768;keyId=bf44c857-b182-bb05-e053-34b8d30a7a72;sig=CzHY47nzJgCSD/BREtSIb+9l/vfkaaL4qf9n8MNJ4CY=";
        String payload = "this is a decrypted payload";
        // Convert the received signatureHeader into timestamp, keyId, and signature.
        DigitalSignature companySignature = new DigitalSignature(signatureHeader);
        // Check if the timestamp is within tolerance.
        if (companySignature.isValidTimestamp()) {
            // Client regenerates their signature using the timestamp from header and received payload.
            byte[] signature = regenerateSignature(companySignature.getTimestamp(), payload);
            // Check if the generated signature is same as signature received in header.
            if (isValidSignature(signature, companySignature.getSignature())) {
                System.out.println("Success - Signature is valid");
            } else {
                System.out.println("Error - Signatures do not match");
            }
        } else {
            System.out.println("Error - timestamp is outside of tolerance level");
        }
    }
    /**
     * Compute HMAC with the SHA256 hash function.
     * key is your private key.
     * message is timestamp.payload.
     * @return
     */
    public static byte[] regenerateSignature(long timestamp, String message) {
        String timestampedMessage = timestamp + "." + message;
        String key = getSecurityKey();
        // Generate the hash using key and message.
        return calcHmacSHA256(Base64.getDecoder().decode(key), timestampedMessage.getBytes(StandardCharsets.UTF_8));
    }
    /**
    * A mechanism to fetch the security key using keyId from a source. We're using Base64encoded version of
    (test_key).
    * @return
    */
    private static String getSecurityKey() {
        return "dGVzdF9rZXk="; //test_key
    }
    /**
     * Generate SHA256 using secretKey and a message.
     * Sample Hmacgenerator to test: https://8gwifi.org/hmacgen.jsp
     * @param secretKey
     * @param message
     * @return
     */
    private static byte[] calcHmacSHA256(byte[] secretKey, byte[] message) {
        byte[] hmacSha256 = null;
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, "HmacSHA256");
            mac.init(secretKeySpec);
            hmacSha256 = mac.doFinal(message);
        } catch (Exception e) {
            throw new RuntimeException("Failed to calculate hmac-sha256", e);
        }
        return hmacSha256;
    }
    /**
     * Compare the Base64 decoding of the signature with the signature received in the header.
     * Sample encoder/decoder to test:
     * https://www.base64encode.org/
     * https://www.base64decode.org/
     * @param bankSignature
     * @param companySignature
     * @return
     */
    private static boolean isValidSignature(byte[] bankSignature, String companySignature) {
        return Arrays.equals(bankSignature, Base64.getDecoder().decode(companySignature));
    }
}

import java.time.Clock;

public class DigitalSignature {
    private long timestamp;
    private String keyId;
    private String signature;

    public DigitalSignature(String digitalSignature) {
        try {
            //split the header by space. first part is the key "v-c-signature" second part is the actual signature
              String signature = digitalSignature.split(" ")[1];

            //separate the actual signature by semicolon. this creates 3 parts (timestamp, keyId, sig)
            String[] signatureParts = signature.split(";");

            //timestamp section is the first block. split timestamp section by = sign and actual timestamp is in the second block
            this.timestamp = Long.parseLong(signatureParts[0].split("=")[1]);

            //keyId section is the second block. split keyId section by = sign and actual keyId is in the second block
            this.keyId = signatureParts[1].split("=")[1];

            //digital signature is the third block. split digital signature by = sign and actual signature is in the second block. This is Base64 encoded
            this.signature = signatureParts[2].split("=")[1];

            if unable to format exactly like the above then would recommend the following edit:
            /* split the header by space. first part is the key "v-c-signature" second part is the actual signature */
              String signature = digitalSignature.split(" ")[1];

            /* separate the actual signature by semicolon. this creates 3 parts (timestamp, keyId, sig) */
            String[] signatureParts = signature.split(";");

            /* timestamp section is the first block. split timestamp section by = sign and actual timestamp is in the second block */
            this.timestamp = Long.parseLong(signatureParts[0].split("=")[1]);

            /* keyId section is the second block. split keyId section by = sign and actual keyId is in the second block */
            this.keyId = signatureParts[1].split("=")[1];

            /* digital signature is the third block. split digital signature by = sign and actual signature is in the second block. This is Base64 encoded */
            this.signature = signatureParts[2].split("=")[1];
        } catch (Exception e) {
            System.out.println("Invalid digital signature format");
        }
    }
    public long getTimestamp() {
        return timestamp;
    }
    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }
    public String getKeyId() {
        return keyId;
    }
    public void setKeyId(String keyId) {
        this.keyId = keyId;
    }
    public String getSignature() {
        return signature;
    }
    public void setSignature(String signature) {
        this.signature = signature;
    }
    /**
     * Using a tolerance of 60 mins
     * Compute the current time in UTC and make sure the timestamp was generated within the tolerance period.
     * UTC millis generator to test: https://currentmillis.com/
     *
     * @return
     */
    public boolean isValidTimestamp() {
        long tolerance = 60 * 60 * 1000L; //60 mins
        // return Clock.systemUTC().millis() - timestamp  &lt; tolerance; // Enable this if you want timestamp
        validation.
        return true;
    }
}
```

