Skip to main content
Welcome to the preview portal — This is an early release of the new Doc Hub experience, with more content coming soon.

Get Started with Webhooks


This tutorial guides you through setting up the Webhooks API and creating your first webhook subscription. Some steps are dependent on your system's security policy.

Setup Workflow

Before You Begin

Before you begin this tutorial, complete these set up tasks in Get Started with REST to enable your system to send and receive REST API messages:

  • Create a merchant test account
  • Create a REST API key
  • Create a custom integration or install the REST Client SDK to send and receive REST API messages
  • Verify that your system can send and receive API messages

Testing

A Postman collection is available for you to test the Webhooks REST API. Contact your account manager to request the collection.


 

Set Up Server

Set up a server with a URL to receive webhook notifications. When setting up the server, configure its security allowlist to permit these IP addresses:

Allowlist

  • 198.241.206.21
  • 198.241.207.21

Trust the Root Certificate

Download the Visa Corporate Root CA - G2 certificate from the Visa Public Key Infrastructure webpage, and add it to your Java keystore: https://enroll.visaca.com

OAuth Token Endpoint

If you use the OAuth or OAuth with JWT security policy, your server must also expose an OAuth token endpoint. You provide that endpoint URL when you create a subscription.

Create Shared Secret Key Pair

Every webhooks integration requires a shared secret key pair: a key pair used for HTTP signature authentication, consisting of a key and a shared secret key. API keys are used to authenticate the requests you send to . You must create separate keys for the testing and production environments.

Create Shared Secret Key

Follow these steps to create a shared secret key pair:

  1. Log in to the :
    • Test site when setting up REST:
    • Production site when approved to go live:
  1. On the left navigation panel, choose Payment Configuration > Key Management.

  2. Click + Generate key on the Key Management page.

  3. Under REST APIs, choose REST – Shared Secret, and then click Generate key.

    The Key Generation page appears.

    If you are using a portfolio account, the Key options window appears, giving you the choice to create a meta key. For more information about how to create a meta key, see Meta Keys .

    The Confirmation Key Generation window appears.

  4. Click Generate key after reviewing the key details.

    The Key Generation page appears.

  5. Click Download key .

    The .pem file downloads to your desktop.

    The Key value is your key ID and the Shared Secret value is your shared secret key.

To create or submit another key, click Generate another key. To view all of your created keys, go to the Key Management page.


Test Your Shared Secret Key Pair

After creating your key certificate, you must verify that it can successfully process API requests. This task explains how to test and validate your shared secret key pair in the Developer Center and the .

Follow these steps:

  1. Under Authentication and Sandbox Credentials, go to the Authentication Type drop-down menu and choose HTTP Signature.

  2. Enter your organization ID in the Organization ID field.

  3. Enter your key ID in the Key field.

  4. Enter your shared secret key in the Shared Secret Key field.

  5. Click Update Credentials.

    A confirmation message verifies that your credentials are successfully updated.

  6. On the left navigation panel, choose Payments > POST Process a Payment.

  7. Click Send.

    A message confirms that your request is successful with the status code 201.

  8. Log in to the .

  9. On the left navigation panel, choose Transaction Management > Transactions.

  10. Under Search Results, verify that the request ID from the test authorization response is listed in the Request ID column.

Request Digital Signature Key

You must create a digital signature key to enable to send notifications to your servers. The Webhooks platform retrieves your credentials from the key to digitally authenticate your notifications. Replace the digital signature key every year. When you generate a new digital signature key, your previous key becomes invalid and new transactions must use the new key.

Notifications that use message-level encryption (MLE) must also use the digital signature key.

After you set up a webhook subscription, you can validate each notification you receive using your digital signature key.

Endpoints

POST /kms/egress/v2/keys-sym

POST /kms/egress/v2/keys-sym

POST /kms/egress/v2/keys-sym

Example: Creating a Digital Signature Key

{  "clientRequestAction": "CREATE",  "keyInformation": {    "provider": "nrtd",    "tenant": "merchantName",    "keyType": "sharedSecret",    "organizationId": "merchantName"  }}
{"submitTimeUtc": "2021-03-17T06:53:06+0000","status": "SUCCESS","keyInformation": {"provider": "NRTD","tenant": "merchantName","organizationId": "merchantName","keyId": "bdc0fe52-091e-b0d6-e053-34b8d30a0504","key": "u3qgvoaJ73rLJdPLTU3moxrXyNZA4eo5dklKtIXhsAE=","keyType": "sharedSecret","status": "Active","expirationDate": "2022-03-17T06:53:06+0000"}}

Required Fields for Creating a Digital Signature Key

FieldDescription
clientRequestActionSet the value to CREATE.
keyInformation.expiryDurationSet to a number of days. We recommend 365.
keyInformation.keyTypeSet the value to sharedSecret.
keyInformation.organizationIdSet the value to the organization ID of the organization requesting the key.
keyInformation.providerSet the value to nrtd.
keyInformation.tenantSet the value to the organization ID of the organization requesting the key.
Validate Your Digital Signature Optional

You can use the digital signature key to verify that the webhook notifications you receive are from . Verifying your webhook notifications validates their integrity and helps prevent replay attacks.

When you receive a webhook notification from , 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 . You may have already completed this requirement while setting up your first webhook subscription.
  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

When you receive a webhook notification from , 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

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

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.     * @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 {            String signature = digitalSignature.split(" ")[1];            String[] signatureParts = signature.split(";");            this.timestamp = Long.parseLong(signatureParts[0].split("=")[1]);            this.keyId = signatureParts[1].split("=")[1];            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 true;    }}

Enable Message-Level Encryption

Message-level encryption (MLE) encrypts the payload of a message to prevent tampering. This section explains how to create the key that is necessary to decrypt encrypted payloads.

The webhook notification service requires X.509 certificates instead of raw public keys for MLE. The service uses:

  • Symmetric Encryption: AES-GCM with 256-bit keys
  • Asymmetric Encryption: RSA-OAEP with 2048-bit keys
  • Format: JSON Web Encryption
  • Separate key pairs for request and response transactions

Before You Begin

Verify that you meet these requirements before you begin:

  • OpenSSL is installed in your system.
  • You have access to terminal-command line.
  • You have required permissions to create the asymmetric key.

Prepare

Open a terminal and navigate to a secure location for storing your key pairs:

mkdir -p ~/webhooks-keyscd ~/webhooks-keys

Generate a Key

Run these command with no line breaks, but change these values:

  • Replace RequestKey with the API key that you created in the API keys setup.
  • Replace YourOrg with your account's organization ID.
openssl req -x509 -newkey rsa:2048 -keyout request_private.pem -out request_certificate.pem -days 365 -nodes -subj "/CN=RequestKey/O=YourOrg/C=US" && cat request_certificate.pem | grep -v "BEGIN CERTIFICATE" | grep -v "END CERTIFICATE" | tr -d '\n' && echo

Register a Key

Use the REST API to register and store your certificate. Each webhook subscription requires its own unique asymmetric certificate.

Endpoints

POST /kms/egress/v2/keys-asym

POST /kms/egress/v2/keys-asym

POST /kms/egress/v2/keys-asym

Example: Registering Your Certificate

{  "clientRequestAction": "STORE",  "keyInformation": {    "provider": "<set this to your organization ID>",    "tenant": "nrtd",    "keyType": "publickey",    "organizationId": "<set this to your organization ID>",    "pub":  "//this must be one line with no breaks    MIIDbDCCAlQCCQD4lcSlmasmCTANBgkqhkiG9w0BAQsFADB4MQswCQYDVQQGEwJVUzELMAkGA1UECAwCVFgxDzANBgNVBAcMBkF1c3RpbjENMAsGA1UECgwEVGVzdDEOMAwGA1UECwwFVGVzdDIxDjAMBgNVBAMMBVRlc3QzMRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3QuY29tMB4XDTIxMDgwOTE0MTcxNFoXDTIyMDgwOTE0MTcxNFoweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlRYMQ8wDQYDVQQHDAZBdXN0aW4xDTALBgNVBAoMBFRlc3QxDjAMBgNVBAsMBVRlc3QyMQ4wDAYDVQQDDAVUZXN0MzEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMcHQWZRETqim3XzUQlAiujFEvsHIi1uJZKj+1lvPH36Ucqo3ORcoh/MM/zxVdahjhSyyp7MHuKBWnzft6bFeDEul6qKWGPAAzaxG/2xZSV3FggA9SyAZEDUpJ6mblwqm/EY4KmZi1FrNBUHfW2wwaqDexHPRDesRG6aI7Wuu4GdQUUqoTa2+Nv7kVgEDmGcfIjoWkGKHe+Yan95EITrq4jEFCE5Tg/vERnMvHfK2SovENZ13/pnwFYbeh1kfJSBzWW7yq8AyQAgAE9iqJXbJ/MAasir2vjUQ2+Hcl7WbkpoVjLqDt3rzV1T0Bsd4T9SC3wij9qjJSxa6vAgV4xn6bECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEADuMrtYW1Sf0IsZ4ZD9ipjUrFuTxqh+0M5Jk8h0QqAXEHA/MawedlU3JmE3NB/UR82/XUwdmtObGnFANuUQQ+8WMFpcNo/Sq2kg7juneHZroRh72o73UUMtHWHzo8s0fXElNal8h3SaAAnjMblCiN+gM1RvWMvhGrMTXp2XAcdIezXf8/FOZLlzOF9QylbSk1U4ayWBag6MydkxgHjkPKdShZROEm0oz/O7J/gNp/r7J8F42Rw9MmJh9qH3SFre13nQa8V7Kg+dJHZ/jpGtSlDHAxO0SSTrPXkwB+iBJ6hSkiL/J2Ep+lYHqVe3p5NXMOlTtJdbU4enHeLkD6PazKTw",    "expiryDuration": "365"  }}
{  "submitTimeUtc": "2026-01-22T14:36:31Z",  "status": "SUCCESS",  "keyInformation": {    "provider": "384259",    "tenant": "nrtd",    "organizationId": "384259",    "keyId": "937dfa75-acff-4f5b-9aab-66a49455ae04",    "pub": "MIIDbDCCAlQCCQD4lcSlmasmCTANBgkqhkiG9w0BAQsFADB4MQswCQYDVQQGEwJVUzELMAkGA1UECAwCVFgxDzANBgNVBAcMBkF1c3RpbjENMAsGA1UECgwEVGVzdDEOMAwGA1UECwwFVGVzdDIxDjAMBgNVBAMMBVRlc3QzMRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3QuY29tMB4XDTIxMDgwOTE0MTcxNFoXDTIyMDgwOTE0MTcxNFoweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlRYMQ8wDQYDVQQHDAZBdXN0aW4xDTALBgNVBAoMBFRlc3QxDjAMBgNVBAsMBVRlc3QyMQ4wDAYDVQQDDAVUZXN0MzEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMcHQWZRETqim3XzUQlAiujFEvsHIi1uJZKj+1lvPH36Ucqo3ORcoh/MM/zxVdahjhSyyp7MHuKBWnzft6bFeDEul6qKWGPAAzaxG/2xZSV3FggA9SyAZEDUpJ6mblwqm/EY4KmZi1FrNBUHfW2wwaqDexHPRDesRG6aI7Wuu4GdQUUqoTa2+Nv7kVgEDmGcfIjoWkGKHe+Yan95EITrq4jEFCE5Tg/vERnMvHfK2SovENZ13/pnwFYbeh1kfJSBzWW7yq8AyQAgAE9iqJXbJ/MAasir2vjUQ2+Hcl7WbkpoVjLqDt3rzV1T0Bsd4T9SC3wij9qjJSxa6vAgV4xn6bECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEADuMrtYW1Sf0IsZ4ZD9ipjUrFuTxqh+0M5Jk8h0QqAXEHA/MawedlU3JmE3NB/UR82/XUwdmtObGnFANuUQQ+8WMFpcNo/Sq2kg7juneHZroRh72o73UUMtHWHzo8s0fXElNal8h3SaAAnjMblCiN+gM1RvWMvhGrMTXp2XAcdIezXf8/FOZLlzOF9QylbSk1U4ayWBag6MydkxgHjkPKdShZROEm0oz/O7J/gNp/r7J8F42Rw9MmJh9qH3SFre13nQa8V7Kg+dJHZ/jpGtSlDHAxO0SSTrPXkwB+iBJ6hSkiL/J2Ep+lYHqVe3p5NXMOlTtJdbU4enHeLkD6PazKTw",    "keyType": "publickey",    "status": "active",    "expirationDate": "2022-08-09T14:17:14Z"  }}

Required Fields

FieldDescription
clientRequestActionSet the value to STORE.
keyInformation.providerSet the value to the name of the organization ID of the account.
keyInformation.tenantSet the value to nrtd.
keyInformation.keyTypeSet the value to publickey.
keyInformation.organizationIdSet the value to the name of the organization ID of the account.
keyInformation.pubSet the value to the X.509 certificate that you generated. IMPORTANT: The certificate is generated with line breaks. You must remove the line breaks before adding it to this field. Do not include the --BEGIN-- and --END-- lines.
keyInformation.expiryDurationSet the value to 365.

Secure Your Subscription

Follow these tips to ensure that your keys remain secure.

  • Store private keys in a secure location with restricted permissions.
  • Never commit private keys to version control.
  • Rotate certificates before expiration.
  • Use environment variables or secure vaults for production deployments.
  • Keep back-up copies of your private keys in a secure location.

Securing Your Private Key

Keep your request_private.pem file secure. This private key is required to decrypt incoming webhook notifications. To modify the permissions so that only the file owner can read and write to the files, use this command:

chmod 600 request_private.pem

View Your Key

To verify your certificate details, use this command.

openssl x509 -in request_certificate.pem -text -noout

Choose Your Security Policy

Decide which security policy your system uses. Your security policy determines whether you submit OAuth credentials and how you format your subscription request.

Every webhooks integration uses mutual trust. You can add the OAuth or OAuth with JWT security policy in addition to mutual trust.

FeatureMutual TrustOAuthOAuth with JWT
Best forIntegrations that do not require token-based authenticationApplications that authenticate to each other using client credentialsIntegrations that bypass domain headers and minimize server-side authentication checks
What you set upAdd to your allowlist and trust the Visa root certificateMutual trust, plus an OAuth token endpoint on your server and your OAuth credentials submitted to Mutual trust, plus a P12 certificate, an OAuth token endpoint on your server, and your OAuth credentials submitted to

Choose a Security Policy Method

Last published: October 28, 2026