Payment Overview
Webhook Notifications

Webhook notification

Webhook notification is a REST PUT call made to the backend system of the merchant at the URL specified in merchant's application configuration. It is sent for two final statuses of the payment: COMPLETED or FAILED.

Delivery rules

Volume Webhook will be sent until we receive a successful 200 response. In all other cases, we will treat it as a failure, and send it again.

Payload

{
  "paymentId": "d2799a79-bd76-4ba4-93b9-2f90bf2a1933",
  "merchantPaymentId": "d2799a79-bd76-4ba4-a3b9-2f90bf2a1934",
  "paymentStatus": "COMPLETED",
  "errorDescription": null,
  "paymentRequest": {
    "amount": 0.1,
    "currency": "GBP",
    "reference": "payment 2022-06-22/123"
  },
  "paymentMetadata": {
    "mydata": "myvalue"
  }
}
fieldtypedescription
paymentIdstring (UUID)payment id
merchantPaymentIdstring (UUID) [optional]payment id, if specified by the merchant when payment is initiated
paymentStatusstringpayment status : COMPLETED or FAILED
errorDescriptionstring [optional]failure description in case of paymentStatus is FAILED
paymentRequest:amountnumberpayment amount
paymentRequest:currencystringpayment currency
paymentRequest:referencestring [optional]payment reference
paymentMetadatastring [optional]payment metadata, if specified by the merchant when payment is initiated

Security

Request signature verification

All Volume webhook notifications come equipped with an Authorization header containing a digital signature of the payload sent in the request. It is signed with a Volume private key hence it can be verified using Volumes public key.

It is highly recommended verifying the signature as it provides two main benefits:

  • as it can be verified only using Volumes public key it proofs that Volume is the origin of the message

  • it proofs that the message payload was not altered, and it is exactly what Volume sent

Implementation

There are multiple options of implementing the signature verifying mechanism, but we recommend going through example implementations provided by Volume. All examples contain a sample web server which can consume a webhook notification and a service responsible for fetching Volumes public key and verifying the signature.

Generic description

Volume webhook security is done by taking the json string which will be sent as the payload of the message, digitally signing it with Volumes private key and sending it as an Authorization header in form of:

Authorization: {algorithm_used} {signature}

Example: SHA256withRSA psQ647Ru7aa43dtyj0nAgFcvKq7KuDL6qE9SjNAdMH+hqJ4md7gFqW6kaDIyyixoUjcMArRjr6l1MORvLCPUX8FeCRbG4/Igz48zNDy4n7oOrGrk2CpInHi7QeMof1XQT0GnXo3qqYkaZyxNaHdXS+MHfaFDsyKVBDereKqdPg+gPdoe8Ons1qpTaDdeJe5LQW8d4Gq2IZaEIgQSG9hcWdQEL69Pq9UrP/Agx1XZIToX2RIKZPCh4DNP5oJlXM+7ysBM0g/xvCsYK6ikBGFyNEyDOEHXKvkiqyJNkQiV6Cs4ZWBoswlQ6B+bJ5EvU2Mn2sVMDF3SSVIAFSJFBrfV3g==

To verify the signature, consume the original, unmodified payload sent from Volume and verify it using Volumes public key located here:

  • sandbox https://api.sandbox.volumepay.io/.well-known/signature/pem
  • live https://api.volumepay.io/.well-known/signature/pem

This public key exposed above is formatted in PKCS#8 .pem file format with trimmed pre- and postfixes:

-----BEGIN|END PUBLIC KEY-----

Adding static IP for additional security

To improve the security of your webhook integration, it's advisable to add our static IP address to IP whitelist in your firewall settings or within your application configuration.

Reasons for Adding the Static IP:

  1. Identity Verification: The static IP serves as an additional layer of verification, ensuring that incoming webhooks originate from our authorized server.

  2. Protection Against Spoofing: By whitelisting our static IP, you minimize the risk of receiving fake or unauthorized webhook requests from malicious sources.

Static IP Addresses for Different Environments:

Sandbox Environment (Testing): For testing purposes, you can use the static IP address:

52.30.246.188

This IP corresponds to our sandbox account and allows you to simulate webhook interactions in a controlled environment.

Live Environment (Production): When deploying your application in a live environment, it is essential to use the static IP address:

52.56.123.234

This ensures that your production environment only accepts legitimate webhook requests from our verified server. Use it for your firewall rules or application configuration.

IDEMPOTENCY

Webhook consumer must be able to successfully handle more than one webhook call for the same payment.
Payments may be recognised always by the paymentId and by merchantPaymentId, if you use unique merchantPaymentId per payment.

If your webhook consumer implementation will successfully handle a webhook, but we will not receive a response, we will send it again. So your endpoint must be ready to accept this payload multiple times - according to the delivery rules.

WARNING!

Remember not to alter the payload of the message before passing it to the verification function. This also includes automatic changes done by the deserialisation mechanisms. Some Dto implementations can produce a different byte array, hence failing the validation. A good example of a problem which can occur by using an Enum instead of a string is implemented here: ApiController.cs#L65 (opens in a new tab)

A complex Dto with a transfer status deserialised to an Enum produces a different byte representation. There is no one easy solution to this problem, as different languages can produce different results (f.ex. Java allows enum usage and does not change the byte array). This is why we recommend consuming either a byte array or the json string exactly as it was sent by Volume(no additional line break or text formatting) and then deserialising it to Dto programmatically: ApiController.cs#L34 (opens in a new tab)

Examples

1. ASP .NET C#

Github repository: https://github.com/getvolume/VolumeWebhookDotNetConsumer (opens in a new tab)

Key points:

We recommend importing the Postman collection mentioned above and as it already contains a proper url, port and sample data. After starting the project a useful Swagger UI will also be available under: https://localhost:7142/swagger/index.html (opens in a new tab)

The most important part of the project is the SignatureService. It contains two method which differ only by the first argument:

  • bool ValidateSignature(byte[] data, string signatureHeader);

  • bool ValidateSignature(string json, string signatureHeader);

First takes the original bytes sent by Volume in the message payload. The other takes a JSON string representation of the payload.

Propositions Naturally a good candidate to implement this kind of verification function is an interceptor or a filter. But this depends on the technology used.

2. Java, Spring

Github repository: https://github.com/getvolume/VolumeWebhookJavaConsumer (opens in a new tab)

Key points:

The most important part of the project is the SignatureService (opens in a new tab). It contains two method which differ only byt the first argument:

  • public boolean verify(byte[] data, String signature)

  • public boolean verify(String jsonString, String signature)

First takes the original bytes sent by Volume in the message payload and the other takes a JSON string representation of the payload.

2. Node.js, Express

Github repository: https://github.com/getvolume/volume-webhook-node-consumer (opens in a new tab)

Key points:

If using the LIVE environment change the public key URL in the .env file, to a correct one according to the implementation paragraph

Testing

In the course of implementing the webhook endpoint, it might be helpful to utilize a CURL statement.
The examples below include a header featuring a digital signature generated using the Volume private key within the SANDBOX environment.

Successful payment scenario

curl --request PUT 'ENDPOINT_URL' \
--header 'Authorization: SHA256withRSA hnHI6qoo7p37NwtBFj332TWC9UUHFiMlwgKsI2XV+L1xKbIK4Vp+3b3bczrdM+8bLXNTRMvJJJ+5zr5uBXBhl9enN3Sfq/4q3gmdq1pGd0Gz0YaRUZxhNG2tkVq7LGtKeeWzg5PxfCy7PeD3D71C+SnUYa7fwT+KzKyPCMqk+uWjLws6pKysinOzh3aYmVhaW9DhH6gZtV2LLGQFHUsqtYClzOkQRxDePhJU8kf8tu8FyTYxJgN4+CZ7vXrD162L0zrcsHXZX1VvVS0GbguHz/JHIFRzqu+o3QpHoidnU+reXPoCQOBV420NaWwVy3Op5o3rFSAZvSwjwAczoQRfnw==' \
--header 'Content-Type: application/json' \
--header 'Accept: */*' \
--data '{"paymentId":"3f2a2b69-6d42-4050-9c4f-7e8849bf683c","merchantPaymentId":"806","paymentStatus":"COMPLETED","errorDescription":null,"paymentRequest":{"amount":24.23,"currency":"GBP","reference":"payment-reference"},"paymentRefundData":null,"paymentMetadata":{"some-data":"some-value"}}'

Failed payment scenario

curl --request PUT 'ENDPOINT_URL' \
--header 'Authorization: SHA256withRSA Th+qWdYLLh436/L0pZOgFgjfNa8jcLE5VTMM4IYEQz1yVkljudt0XMgShhWjqhy2+f0puV+FXfh3PWP3DMAV8FlgYdciyPpLqijy5Ruo2ALz0LPgunT/o6Y+7NAfWCnVDfWT17yqokeN+70QCX/Waq+2Ox8nu8a7bJVj6noiiMUfq5pLKKiQMqb1t7ebznrKGGvt0IUdQzIxfFQz/lT4V5Oar4lQZ3hNhjm/Rmde8ctJ3g2sVuY6Mqt5OiUZzWQl/zqEl5OR4zlzTrxCzlqyep9Yu5E3TRgz1J82gZMeZVFY7yxt/wj5Fre/zK6SWJmpShbqzK5fJ5D++4CS8nY+GA==' \
--header 'Content-Type: application/json' \
--header 'Accept: */*' \
--data '{"paymentId":"183b5eee-0fbf-4863-b55a-7a72af84db1a","merchantPaymentId":"937","paymentStatus":"FAILED","errorDescription":"Payment was rejected","paymentRequest":{"amount":24.23,"currency":"GBP","reference":"payment-reference"},"paymentRefundData":null,"paymentMetadata":{"some-data":"some-value"}}'
parametersdescription
ENDPOINT_URLURL of the endpoint under implementation.
AuthorizationHeader containing the digital signature of this exact payload.
dataExample of a webhook payload sent for a payment.