# Notifications & Webhooks

You can use the **Notifications** sections in Verifone Central to search and filter your organization’s webhooks and email notifications. &#x20;

The Notification Service allows you to get status updates on certain events/actions you subscribe to, and to automate business processes like order management, accounting, or downloading reports. Data sent in the notifications will be pushed to your email/URL.

{% embed url="<https://player.vimeo.com/video/1145239195?h=90379a11f6&amp;badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479>" %}

## Availability <a href="#availability" id="availability"></a>

Only users with **Merchant Admin**, **Merchant Cashier**, or **Merchant Supervisor** roles can access this section.

## Events <a href="#c4" id="c4"></a>

Events are the topics a notification would be 'listening' to. The notifications will be triggered as soon as there is an event happening for one of the selected topics.

### Transaction Events <a href="#events" id="events"></a>

<table><thead><tr><th width="308.4444580078125">Event</th><th>Description</th></tr></thead><tbody><tr><td><p>TxnAccountVerificationApproved</p><p>TxnAccountVerificationDeclined</p></td><td>An account/card verification</td></tr><tr><td><p>TxnAuthorisationApproved</p><p>TxnAuthorisationDeclined</p></td><td>An authorisation or preauthorisation</td></tr><tr><td><p>TxnCaptureApproved</p><p>TxnCaptureDeclined</p></td><td>A capture or completion</td></tr><tr><td><p>TxnDelayedChargeApproved</p><p>TxnDelayedChargeDeclined</p></td><td>A single message sale (auth+capture) related to a preauth chain</td></tr><tr><td><p>TxnExtendApproved</p><p>TxnExtendDeclined</p></td><td>A preauth extend</td></tr><tr><td><p>TxnPreauthIncrementApproved</p><p>TxnPreauthIncrementDeclined</p></td><td>An increment for a preauth</td></tr><tr><td><p>TxnReauthorisationApproved</p><p>TxnReauthorisationDeclined</p></td><td>A preauth reauth</td></tr><tr><td><p>TxnRefundApproved</p><p>TxnRefundDeclined</p></td><td>A refund</td></tr><tr><td>TxnRefundPreviewCancelled</td><td>A refund preview cancelled transaction</td></tr><tr><td>TxnRefundPreviewCustomerApproved</td><td>A refund preview customer approved transaction</td></tr><tr><td><p>TxnSaleApproved</p><p>TxnSaleDeclined</p></td><td>A single message sale (auth+capture)</td></tr><tr><td>TxnSaleConfirmed</td><td>A confirmation of sale (specific for crypto transactions)</td></tr><tr><td><p>TxnVoidApproved</p><p>TxnVoidDeclined</p></td><td>A void</td></tr></tbody></table>

### Checkout Events <a href="#checkout-events" id="checkout-events"></a>

<table><thead><tr><th width="338.4443359375">Event</th><th>Description</th></tr></thead><tbody><tr><td>Checkout - Transaction succeeded</td><td>An successful checkout Transaction Success </td></tr><tr><td>Checkout - Transaction failed</td><td>Checkout transaction failed</td></tr><tr><td>Checkout - Card token succeeded</td><td>Successful card token transaction</td></tr><tr><td>Checkout - Card token failed</td><td>Failed card token transaction</td></tr><tr><td>Checkout - 3DS authentication succeeded</td><td>An successful checkout 3DS transaction</td></tr><tr><td>Checkout - 3DS authentication failed</td><td>A failed checkout 3DS authentication</td></tr><tr><td>Checkout - 3DS lookup failed</td><td>3DS lookup failed</td></tr><tr><td>Checkout - 3DS lookup succeeded</td><td>3DS lookup succeeded</td></tr><tr><td>Checkout - SMS delivery succeeded</td><td>PBL SMS delivery succeeded</td></tr><tr><td>Checkout - Email delivery succeeded</td><td>PBL Email delivery succeeded</td></tr><tr><td>Checkout - SMS delivery failed</td><td>PBL SMS delivery failed</td></tr><tr><td>Checkout - Email delivery failed</td><td>PBL Email delivery failed</td></tr></tbody></table>

## Create notifications in Verifone Central <a href="#create-notifications-in-verifone-central" id="create-notifications-in-verifone-central"></a>

To use the information in the Notifications section in Verifone Central, follow these steps:

1. Log in to your Verifone Central account.
2. Navigate to **Administration** and click on **Notifications**.

   <div data-with-frame="true"><img src="https://verifone.cloud/sites/default/files/inline-images/1_52.JPG" alt=""></div>
3. The *Notifications* page will be displayed. Click on **Create new notification**.<br>

   <div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p>In the <strong>Notifications</strong> page, you can filter the already created notifications by name / email / URL and/or use the <strong>Event type</strong> or the <strong>Status</strong>  filters.</p></div>

   &#x20;&#x20;

   <div data-with-frame="true"><img src="https://verifone.cloud/sites/default/files/inline-images/2_42.JPG" alt=""></div>
4. On the **Create Notification** page, provide the following information:
   * **Notification name** – used to identify the notification configuration in the listing
   * **Organization(s)**&#x20;
     * By selecting an organization, you apply a scope to the configuration
     * By selecting the lowest level organization, you will receive messages for the selected organization only

       &#x20;

       <div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p>Multiple organizations can be selected when creating a notification, if the same notification must be sent to multiple organizations.</p></div>
   * **Events** – notifications will be triggered as soon as an event takes place for one of the selected transaction status updates (for more details regarding available events check the [Events](#c4) tables above)
   * **Delivery Method** – specifies how notifications will be delivered. Depending on the selected method, Verifone will send either an email with plain text describing the event or a webhook with the transaction payload. Possible values:
     * **Email** – accepts only one email address<br>

       <div data-with-frame="true"><figure><img src="https://verifone.cloud/sites/default/files/inline-images/3_33.JPG" alt=""><figcaption></figcaption></figure></div>
     * **URL Endpoint** – accepts only one webhook; selecting this option will provide two other possibilities:
       * **Event metadata only** - webhooks with a limited payload<br>

         <div data-with-frame="true"><figure><img src="https://verifone.cloud/sites/default/files/inline-images/4_23.JPG" alt=""><figcaption></figcaption></figure></div>
       * **Full event payload** - webhooks with full content payload (**applicable only for** **Transaction Events**)<br>

         <div data-with-frame="true"><figure><img src="https://verifone.cloud/sites/default/files/inline-images/5_16.JPG" alt=""><figcaption></figcaption></figure></div>

{% hint style="warning" %}
When a new webhook is added, it should be loaded within 60 seconds. However, updates to existing webhooks can take between 10 to 60 minutes depending on server load.&#x20;
{% endhint %}

## Notification payload variables <a href="#notification-payload-variables" id="notification-payload-variables"></a>

<table data-full-width="true"><thead><tr><th width="158.99993896484375">Parameter Name</th><th width="112.44439697265625">Type</th><th width="110.17767333984375">Format</th><th width="69.1112060546875">Email</th><th width="53.333251953125">Full payload</th><th width="63.888916015625">Metadata</th><th>Description</th></tr></thead><tbody><tr><td><code>eventType</code></td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>✅</td><td>The event type for which the notification was sent. E.g., <code>TxnSaleDeclined</code></td></tr><tr><td><code>objectType</code></td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>In a transaction event this would be <code>TransactionEvent</code>, and in a Checkout event this would be <code>StandardEvents</code>. </td></tr><tr><td><code>eventId</code></td><td>String</td><td>UUID</td><td>✅</td><td>✅</td><td>✅</td><td>In transactions events this would be the Transaction ID, and in Checkout events this would be the Checkout ID.</td></tr><tr><td><code>itemId</code></td><td>String</td><td>UUID</td><td>⛔</td><td>✅</td><td>⛔</td><td>In transactions events this would be the Transaction ID.<br>The itemId is not applicable to Checkout events.</td></tr><tr><td><code>recordId</code></td><td>String</td><td>UUID</td><td>✅</td><td>✅</td><td>✅</td><td>In transactions events this would be the Transaction ID, and in Checkout events this would be the Checkout ID.</td></tr><tr><td><code>entityUid</code></td><td>String</td><td>UUID</td><td>✅</td><td>✅</td><td>✅</td><td>The Verifone assigned organization identifier.</td></tr><tr><td><code>eventDateTime</code></td><td>Datetime</td><td>YYYY-MM-DDThh:mm:ss.msZ</td><td>✅</td><td>✅</td><td>✅</td><td>The date and time at which the event occurred.</td></tr><tr><td><code>source</code> </td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>✅</td><td>The source of the event information.</td></tr><tr><td><code>content</code></td><td>Object</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>The associated event data. This is optional and if specified will vary according to the event type.</td></tr><tr><td><code>content.id</code></td><td>String</td><td>UUID</td><td>✅</td><td>✅</td><td>⛔</td><td>The Transaction ID. </td></tr><tr><td><code>content.currency_code</code></td><td>String</td><td>three-letter ISO 4217 alphabetic currency codes</td><td>✅</td><td>✅</td><td>⛔</td><td>A three-letter alphabetic code that represents the currency used for the transaction.</td></tr><tr><td><code>content.country_code</code> </td><td>String</td><td>2-letter ISO 3166 alpha-2 country code</td><td>✅</td><td>✅</td><td>⛔</td><td>A 2-letter ISO 3166 alpha-2 country code representing the consumer's address.</td></tr><tr><td><code>content.created_at</code></td><td>Datetime</td><td>YYYY-MM-DDThh:mm:ss.msZ</td><td>✅</td><td>✅</td><td>⛔</td><td>The date and time the transaction creation.</td></tr><tr><td><code>content.customer_ip</code></td><td>String</td><td>32-bit number</td><td>✅</td><td>✅</td><td>⛔</td><td>A 32-bit number that identifies a host on a TCP/IP network of the consumer.</td></tr><tr><td><code>content.dynamic_descriptor</code></td><td>String</td><td>—</td><td>⛔</td><td>✅</td><td>⛔</td><td>A short transaction description that can be included when creating a transaction via the Virtual Terminal, the Checkout API, or the eCom API.<br>This description might be included in the bank statement issued to the consumer by some card issuers.</td></tr><tr><td><code>content.amount</code></td><td>Float</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>The transaction amount.</td></tr><tr><td><code>content.payment_product</code> </td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>The type of product used for payment. E.g., <code>CARD</code>, <code>KLARA</code>, <code>SWISH</code>, <code>CRYPTO</code>, etc.</td></tr><tr><td><code>content.payment_product_type</code></td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>The brand of the payment type used for payment. E.g., <code>VISA</code>, <code>MASTERCARD</code>, <code>AMEX</code>, etc.</td></tr><tr><td><code>content.processor_reference</code> </td><td>String</td><td>—</td><td>⛔</td><td>✅</td><td>⛔</td><td>Reference identifying the transaction, as provided by the processor.</td></tr><tr><td><code>content.transaction_type</code> </td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>The transaction type, such as <code>SALE</code>, <code>AUTHORIZATION</code>, <code>PREAUTH</code>, etc.</td></tr><tr><td><code>content.transaction_status</code> </td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>The current status of the transaction. E.g., <code>AUTHORISED</code>, <code>CAPTURED</code>, <code>SETTLED</code>, <code>CANCELLED</code>, etc.</td></tr><tr><td><code>content.reason_code</code></td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>A reason code assigned by the acquiring platform; '0000' in case of success.</td></tr><tr><td><code>content.arn</code></td><td>String</td><td>—</td><td>⛔</td><td>✅</td><td>⛔</td><td>The acquirer reference number (ARN), generated by the acquirer at the time of clearing for card transactions.</td></tr><tr><td><code>content.authorization_code</code></td><td>String</td><td>—</td><td>⛔</td><td>✅</td><td>⛔</td><td>The credit card authorization code represents the five or six numbers generated by the issuing bank.</td></tr><tr><td><code>content.shipping_information</code> </td><td>Object</td><td>—</td><td>⛔</td><td>✅</td><td>⛔</td><td>An optional object that includes the consumer's shipping information.</td></tr><tr><td><code>content.shipping_information.address</code></td><td>String</td><td>—</td><td>⛔</td><td>✅</td><td>⛔</td><td>The shipping (street) address.</td></tr><tr><td><code>content.shipping_information.city</code></td><td>String</td><td>—</td><td>⛔</td><td>✅</td><td>⛔</td><td>The shipping city.</td></tr><tr><td><code>content.shipping_information.country</code></td><td>String</td><td>A 2-letter ISO 3166 alpha-2 country code</td><td>⛔</td><td>✅</td><td>⛔</td><td>The shipping country.</td></tr><tr><td><code>content.shipping.phone</code></td><td>String</td><td>—</td><td>⛔</td><td>✅</td><td>⛔</td><td>The shipping phone number.</td></tr><tr><td><code>content.shipping.postal_code</code></td><td>String</td><td>—</td><td>⛔</td><td>✅</td><td>⛔</td><td>The shipping postal code.</td></tr><tr><td><code>content.shipping.state</code></td><td>String</td><td>—</td><td>⛔</td><td>✅</td><td>⛔</td><td>The shipping state.</td></tr><tr><td><code>content.user_agent</code></td><td>String</td><td>—</td><td>⛔</td><td>✅</td><td>⛔</td><td>The full user agent string of the device the customer used to submit the transaction.</td></tr><tr><td><code>content.cvv_present</code></td><td>Boolean</td><td>—</td><td>⛔</td><td>✅</td><td>⛔</td><td>True if the card was used with a CVV.</td></tr><tr><td><code>content.rrn</code></td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>The payment processor's retrieval reference number.</td></tr><tr><td><code>content.shopper_interaction</code></td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>The sales channel that was used to capture the transaction. E.g., <code>ECOMMERCE</code>, <code>MAIL</code>, <code>PHONE</code>, <code>POS</code>. etc.</td></tr><tr><td><code>content.stan</code></td><td>String</td><td>—</td><td>⛔</td><td>✅</td><td>⛔</td><td>A number assigned by a transaction initiator (originator) to assist in identifying a transaction uniquely. This property can be used to store the System Trace Audit Number (STAN) as used in the ISO 8583 and AS2805 specifications.</td></tr><tr><td><code>content.card_brand</code></td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>Same as the payment product type.</td></tr><tr><td><code>content.merchant_id</code></td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>The identifier assigned to the merchant entity under the Payment Provider Contract.</td></tr><tr><td><code>content.merchant_reference</code></td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>A reference specified by the merchant to identify the transaction.</td></tr><tr><td><code>content.poi_id</code></td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>The Verifone assigned ID to the point of the interaction used for the transaction, where applicable.</td></tr><tr><td><code>content.masked_card_number</code></td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>Masked number of the card used for payment.</td></tr><tr><td><code>content.payment_summary.captured_amount</code></td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>The amount that was captured out of the total transaction amount.</td></tr><tr><td><code>content.threed_authentication</code></td><td>Object</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>3DS authentication information, where applicable. Read our <a href="https://verifone.cloud/docs/online-payments/3dsecure">3-D Secure</a> article for additional information.</td></tr><tr><td><code>content.threed_authentication.eci_flag</code></td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>The Electronic Commerce Indicator (ECI).</td></tr><tr><td><code>content.threed_authentication.enrolled</code></td><td>Boolean</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>The payment card's 3DS enrollment status.</td></tr><tr><td><code>content.threed_authentication.cavv</code></td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>The Cardholder Authentication Verification Value (CAVV) cryptographic value.</td></tr><tr><td><code>content.threed_authentication.pares_status</code></td><td>String</td><td>—</td><td>✅</td><td>✅</td><td>⛔</td><td>The Payment Authentication Response (PARes) status.</td></tr><tr><td><code>content.threed_authentication.ds_transaction_id</code></td><td>String</td><td>—</td><td>⛔</td><td>✅</td><td>⛔</td><td>A unique transaction identifier assigned by the 3DS server.</td></tr><tr><td><code>content.threed_authentication.threeds_version</code></td><td>String</td><td>—</td><td>⛔</td><td>✅</td><td>⛔</td><td>The 3DS version used to authenticate the transaction.</td></tr></tbody></table>

## Email payload sample <a href="#email-payload-sample" id="email-payload-sample"></a>

```json
Subject: New event - Authorization approved

array (
    eventId: aefd58f2-7d4d-4afc-b3a8-b841aba5d833,
    eventDateTime : {dateTime={date={year=2021, month=11, day=16}, time={hour=14, minute=36, second=12, nano=921000000}}, offset={totalSeconds=0}},
    recordId : aefd58f2-7d4d-4afc-b3a8-b841aba5d833,
    entityUid :9aea8c9c-d54e-4811-b8d3-6d33aa3a702c,
    eoEntityUid:,
    eventType:Void Approved,
    estateOwner:,
    source:pdsp,
    component:,
    received:
    objectType:TransactionEvent
    content:
    UUID :aefd58f2-7d4d-4afc-b3a8-b841aba5d833,
    TRANSACTIONTYPE : VOID,
    INITIATORTRACEID : 002709,
    GATEWAYTRACEID : ,
    CREATEDDATETIME : 2021-11-16T14:36:12.921Z,
    POI : {},
    MERCHANT : {UUID=9aea8c9c-d54e-4811-b8d3-6d33aa3a702c, ID=87654, LOCALE={COUNTRYCODE=NL}, CONTRACTS=[{MCC=7654, MERCHANTID=87654}]},
    AMOUNT :{VALUE=10.03, CURRENCYCODE=EUR},
    INSTRUMENT :[{CARDBRAND=VISA, INSTRUMENTTYPE=CARD, MASKEDCARDNUMBER=411111******1111}, {INSTRUMENTTYPE=TOKEN}],
    OUTCOME:[{ACQUIRERRESPONSECODE=00}, {RESPONSE=SUCCESS, RESPONSECODE=0000}],
    CUSTOMER :,
    CONTEXT:
  )
```

## Webhook sample

### Event metadata only <a href="#event-metadata-only" id="event-metadata-only"></a>

```json
{
  "eventType": "TxnAuthorisationApproved",
  "eventId": "72d2da83-ac4f-11e8-a4d5-c2941f1b9e6a",
  "eventDateTime": "2020-08-07T15:47:37.391+12:00",
  "recordId": "ddb21f1f-a691-4a62-9083-4ab9155b9739"
}
```

### Full event payload <a href="#full-event-payload" id="full-event-payload"></a>

```json
{
  "eventType": "TxnAuthorisationApproved",
  "objectType": "TransactionEvent",
  "eventId": "5b5f42f6-db9f-4fd8-8396-1527ec621ab8",
  "eventDateTime": "2023-03-02T13:16:44.654Z",
  "recordId": "5b5f42f6-db9f-4fd8-8396-1527ec621ab8",
  "entityUid": "07652580-1037-4901-92f2-74676cb8aa7e",
  "source": "pdsp",
  "content": {
    "id": "5b5f42f6-db9f-4fd8-8396-1527ec621ab8",
    "currency_code": "EUR",
    "country_code": "NL",
    "created_at": "2023-03-02T13:16:44.654Z",
    "customer": "18c4694c-b498-40fa-ab9d-fbe671c0afca",
    "customer_ip": "127.0.0.1",
    "dynamic_descriptor": "Test Transaction",
    "payment_product": "CARD",
    "payment_product_type": "VISA",
    "transaction_type": "SALE",
    "transaction_status": "FAILED",
    "shipping_information": {
      "address": "Singel 250",
      "city": "Amsterdam",
      "country": "NL",
      "phone": "31633344455",
      "postal_code": "1012 AB",
      "state": "Noord-Holland"
    },
    "user_agent": "string",
    "cvv_present": false,
    "card_brand": "VISA",
    "masked_card_number": "430000******7107",
    "merchant_id": "290010026",
    "merchant_reference": "200001",
    "shopper_interaction": "ecommerce",
    "threed_authentication": {
      "eci_flag": "05",
      "enrolled": "Y",
      "cavv": "MTIzNDU2Nzg5MDEyMzQ1Njc4OTA=",
      "pares_status": "Y",
      "ds_transaction_id": "020100004317fdc3ad2454438000000000000891",
      "threeds_version": "2.1.0"
    },
    "amount": "3.0",
    "payment_summary": {}
  }
}
```

## Update notifications in Verifone Central <a href="#update-notifications-in-verifone-central" id="update-notifications-in-verifone-central"></a>

Any notification can be edited as desired. The updates below when the system fails to notify the merchant (e.g., the URL endpoint is unreachable or the  can be applied to a notification:

* Update the notification name
* Add/remove organisation(s)
* Add/remove events
* Change the delivery method
* Enable/disable/delete the notification

<div data-with-frame="true"><figure><img src="https://verifone.cloud/sites/default/files/inline-images/6_7.JPG" alt="" width="563"><figcaption></figcaption></figure></div>

{% hint style="warning" %}
A notification can be deleted only after being disabled.
{% endhint %}

## Notification failures <a href="#notification-failures" id="notification-failures"></a>

Notification failures can occur only for webhook [events](#c4) when the system fails to notify the merchant (e.g., the URL endpoint is unreachable or the service is down/times out).

The failed notifications can be viewed in Verifone Central under **Administration** > **Notifications** > select the notification > **Notification Failures**.

<div data-with-frame="true"><figure><img src="https://verifone.cloud/sites/default/files/inline-images/Screenshot%202023-10-24%20at%2014.14.10.png" alt=""><figcaption></figcaption></figure></div>

## Webhook signature verification <a href="#webhook-signature-verification" id="webhook-signature-verification"></a>

The authenticity and integrity of the webhook event can be verified by checking the signature provided in the header: x-vfi-jws. This signature is used to validate that Verifone is the sender and that the message has not been tampered with.

This signature is provided as a JSON web signature (JWS) using the webhook body as the unencoded payload, as described in <https://www.rfc-editor.org/rfc/rfc7797>.

The public keys used to verify the signatures are provided in a JWKS (JSON web key set) file, which can be downloaded from the following URLs.

| Environment                                                                                                    |
| -------------------------------------------------------------------------------------------------------------- |
| [Test](https://vf11gtostorage1.blob.core.windows.net/test-webhook-sign-keys/test-webhook-sign-keys.jwks)       |
| [Production](https://vf11gtostorage1.blob.core.windows.net/prod-webhook-sign-keys/prod-webhook-sign-keys.jwks) |

To verify the signature of the webhook payload, the following steps need to be performed.

41. Ensure that the application has loaded the keys from the JWKS file. This file should be cached locally and not downloaded from Verifone on each request.
42. When the request is received, convert the HTTP JSON body to canonicalized form: <https://www.rfc-editor.org/rfc/rfc8785>.
43. Select the correct key from the JWKS matching the key id from the x-vfi-jws header.
44. Use the x-vfi-jws header and canonicalized body to verify the signature with the selected key.<br>

    <div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p>If the key id is not found in Step 3, then refresh the JWKS file as a new key may have been added.</p></div>

### Webhook signature verification sample

The following Java code verifies a signature as described above and has dependencies on the following libraries:

* <https://github.com/erdtman/java-json-canonicalization>
* <https://bitbucket.org/b_c/jose4j/src/master/>

```java
/**
 * Step 1 - Load signing keys via jwks file
 */
 
String JWKS_URL = "https://vf11gtostorage1.blob.core.windows.net/test-webhook-sign-keys/test-webhook-sign-keys.jwks";
 
// on first startup we need to download the signing keys
// NOTE: this file should be cached locally and not downloaded each time
Path jwksLocalPath = Path.of("./test-webhook-sign-keys.jwks");
if (!Files.exists(jwksLocalPath)) {
    try (InputStream in = new URL(JWKS_URL).openStream()) {
        Files.copy(in, jwksLocalPath, StandardCopyOption.REPLACE_EXISTING);
    }
}
 
// load keys from file
String jkws = Files.readString(jwksLocalPath);
JsonWebKeySet jsonWebKeySet = new JsonWebKeySet(jkws);
 
/**
 * Step 2 - Convert webhook json body to canonicalized form
 */
 
String originalJsonBody = *JSON BODY FROM WEBHOOK*;
JsonCanonicalizer jsonCanonicalizer = new JsonCanonicalizer(originalJsonBody);
String canonicalizedJson = jsonCanonicalizer.getEncodedString();
 
/**
 * Step 3 and 4- Select matching key for x-vfi-jws and validate signature
 */
 
JsonWebSignature verifierJws = new JsonWebSignature();
 
// set contents from x-vfi-jws header
verifierJws.setCompactSerialization(detachedContentJws);
 
// The canonicalized content is the payload
verifierJws.setPayload(canonicalizedJson);
 
// Pick the key to use for checking the signature. The key selected
// should have the same key id "kid" as the x-vfi-jws header
VerificationJwkSelector jwkSelector = new VerificationJwkSelector();
JsonWebKey jwkSelected = jwkSelector.select(verifierJws, jsonWebKeySet.getJsonWebKeys());
 
if (jwkSelected == null) {
    // if key can't be found then refresh signing keys as
    // a new key id may have been added
    try (InputStream in = new URL(JWKS_URL).openStream()) {
        Files.copy(in, jwksLocalPath, StandardCopyOption.REPLACE_EXISTING);
    }
    jkws = Files.readString(jwksLocalPath);
    jsonWebKeySet = new JsonWebKeySet(jkws);
    jwkSelected = jwkSelector.select(verifierJws, jsonWebKeySet.getJsonWebKeys());
}
 
// set key based on keyid selected from jwks
verifierJws.setKey(jwkSelected.getKey());
 
// Check the signature
boolean signatureVerified = verifierJws.verifySignature();
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.verifone.com/verifone-central-getting-started/verifone-central/administration-tools/notifications-and-webhooks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
